@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.)
hq.recaptime.dev/wiki/Phorge
phorge
phabricator
1<?php
2
3final class DiffusionGitBranch extends Phobject {
4
5 const DEFAULT_GIT_REMOTE = 'origin';
6
7 /**
8 * Parse the output of 'git branch -r --verbose --no-abbrev' or similar into
9 * a map. For instance:
10 *
11 * array(
12 * 'origin/master' => '99a9c082f9a1b68c7264e26b9e552484a5ae5f25',
13 * );
14 *
15 * If you specify $only_this_remote, branches will be filtered to only those
16 * on the given remote, **and the remote name will be stripped**. For example:
17 *
18 * array(
19 * 'master' => '99a9c082f9a1b68c7264e26b9e552484a5ae5f25',
20 * );
21 *
22 * @param string $stdout stdout of git branch command.
23 * @param string $only_this_remote (optional) Filter branches to those on a
24 * specific remote.
25 * @return map Map of 'branch' or 'remote/branch' to hash at HEAD.
26 */
27 public static function parseRemoteBranchOutput(
28 $stdout,
29 $only_this_remote = null) {
30 $map = array();
31
32 $lines = array_filter(explode("\n", $stdout));
33 foreach ($lines as $line) {
34 $matches = null;
35 if (preg_match('/^ (\S+)\s+-> (\S+)$/', $line, $matches)) {
36 // This is a line like:
37 //
38 // origin/HEAD -> origin/master
39 //
40 // ...which we don't currently do anything interesting with, although
41 // in theory we could use it to automatically choose the default
42 // branch.
43 continue;
44 }
45 if (!preg_match('/^ *(\S+)\s+([a-z0-9]{40})/', $line, $matches)) {
46 throw new Exception(
47 pht(
48 'Failed to parse %s!',
49 $line));
50 }
51
52 $remote_branch = $matches[1];
53 $branch_head = $matches[2];
54
55 if (strpos($remote_branch, 'HEAD') !== false) {
56 // let's assume that no one will call their remote or branch HEAD
57 continue;
58 }
59
60 if ($only_this_remote) {
61 $matches = null;
62 if (!preg_match('#^([^/]+)/(.*)$#', $remote_branch, $matches)) {
63 throw new Exception(
64 pht(
65 "Failed to parse remote branch '%s'!",
66 $remote_branch));
67 }
68 $remote_name = $matches[1];
69 $branch_name = $matches[2];
70 if ($remote_name != $only_this_remote) {
71 continue;
72 }
73 $map[$branch_name] = $branch_head;
74 } else {
75 $map[$remote_branch] = $branch_head;
76 }
77 }
78
79 return $map;
80 }
81
82 /**
83 * Parse the output of 'git branch --verbose --no-abbrev' or similar into a
84 * map. As parseRemoteBranchOutput but no `-r`. Used for bare repositories.
85 *
86 * @return map Map of branch name (string or int) and its hash (string).
87 */
88 public static function parseLocalBranchOutput($stdout) {
89 $map = array();
90
91 $lines = array_filter(explode("\n", $stdout));
92 $regex = '/^[* ]*(\(no branch\)|\S+)\s+([a-z0-9]{40})/';
93 foreach ($lines as $line) {
94 $matches = null;
95 if (!preg_match($regex, $line, $matches)) {
96 throw new Exception(
97 pht(
98 'Failed to parse %s!',
99 $line));
100 }
101
102 $branch = $matches[1];
103 $branch_head = $matches[2];
104 if ($branch == '(no branch)') {
105 continue;
106 }
107 // Note: If the $branch name string is numeric containing only decimal
108 // ints and does not start with 0, PHP will cast it from string to int:
109 // https://www.php.net/manual/en/language.types.array.php#language.types.array.syntax
110 $map[$branch] = $branch_head;
111 }
112
113 return $map;
114 }
115
116}