4 // This is the minimalist denialist implementation that doesn't check it's
5 // pre-conditions and will screw up if you don't know what you're doing.
8 * Manage the current working directory as a stack.
13 function __construct($dirs = array()) {
18 $this->dirs
[] = getcwd();
20 throw new Exception("Failed to chdir($dir)");
25 $oldDir = array_pop($this->dirs
);
33 * @var string 'checkout', 'begin', 'help', etc
40 protected $baseBranch;
43 * @var array ($repoName => $gitRef)
50 protected $civiRoot = '.';
55 protected $drupalVersion = 7;
60 protected $dryRun = FALSE;
65 protected $fetch = FALSE;
70 protected $rebase = FALSE;
73 * @var array ($repoName => $relPath)
78 * @var array, non-hyphenated arguments after the basedir
83 * @var string, the name of this program
92 function __construct() {
93 $this->dirStack
= new DirStack();
98 'packages' => 'packages',
99 'wordpress' => 'WordPress',
103 function main($args) {
104 if (!$this->parseOptions($args)) {
105 printf("Error parsing arguments\n");
110 // All operations relative to civiRoot
111 $this->dirStack
->push($this->civiRoot
);
113 // Filter branch list based on what repos actually exist
114 foreach (array_keys($this->repos
) as $repo) {
115 if (!is_dir($this->repos
[$repo])) {
116 unset($this->repos
[$repo]);
119 if (!isset($this->repos
['core']) ||
!isset($this->repos
['packages'])) {
120 return $this->returnError("Root appears to be invalid -- missing too many repos. Try --root=<dir>\n");
124 switch ($this->action
) {
126 call_user_func_array(array($this, 'doCheckoutAll'), $this->arguments
);
129 call_user_func_array(array($this, 'doFetchAll'), $this->arguments
);
132 call_user_func_array(array($this, 'doStatusAll'), $this->arguments
);
135 call_user_func_array(array($this, 'doBegin'), $this->arguments
);
138 call_user_func_array(array($this, 'doResume'), $this->arguments
);
145 return $this->returnError("unrecognized action: {$this->action}\n");
148 $this->dirStack
->pop();
155 function parseOptions($args) {
156 $this->branches
= array();
157 $this->arguments
= array();
159 foreach ($args as $arg) {
160 if ($arg == '--fetch') {
163 elseif ($arg == '--rebase') {
164 $this->rebase
= TRUE;
166 elseif ($arg == '--dry-run' ||
$arg == '-n') {
167 $this->dryRun
= TRUE;
169 elseif (preg_match('/^--d([678])/', $arg, $matches)) {
170 $this->drupalVersion
= $matches[1];
172 elseif (preg_match('/^--root=(.*)/', $arg, $matches)) {
173 $this->civiRoot
= $matches[1];
175 elseif (preg_match('/^--(core|packages|joomla|drupal|wordpress)=(.*)/', $arg, $matches)) {
176 $this->branches
[$matches[1]] = $matches[2];
178 elseif (preg_match('/^-/', $arg)) {
179 printf("unrecognized argument: %s\n", $arg);
183 $this->arguments
[] = $arg;
187 $this->program
= @array_shift
($this->arguments
);
188 $this->action
= @array_shift
($this->arguments
);
193 $program = basename($this->program
);
194 echo "Givi - Coordinate git checkouts across CiviCRM repositories\n";
196 echo " $program [options] checkout <branch>\n";
197 echo " $program [options] fetch\n";
198 echo " $program [options] status\n";
199 echo " $program [options] begin <base-branch> [--core=<new-branch>|--drupal=<new-branch>|...] \n";
200 echo " $program [options] resume [--rebase] <base-branch> [--core=<custom-branch>|--drupal=<custom-branch>|...] \n";
202 echo " checkout: Checkout same branch name on all repos\n";
203 echo " fetch: Fetch remote changes on all repos\n";
204 echo " status: Display status on all repos\n";
205 echo " begin: Begin work on a new branch on some repo (and use base-branch for all others)\n";
206 echo " resume: Resume work on an existing branch on some repo (and use base-branch for all others)\n";
207 echo "Common options:\n";
208 echo " --dry-run: Don't do anything; only print commands that would be run\n";
209 echo " --d6: Specify that Drupal branches should use 6.x-* prefixes\n";
210 echo " --d7: Specify that Drupal branches should use 7.x-* prefixes (default)\n";
211 echo " --fetch: Fetch the latest code before creating, updating, or checking-out anything\n";
212 echo " --root=X: Specify CiviCRM root directory (default: .)\n";
213 echo "Special options:\n";
214 echo " --core=X: Specify the branch to use on the core repository\n";
215 echo " --packages=X: Specify the branch to use on the packages repository\n";
216 echo " --drupal=X: Specify the branch to use on the drupal repository\n";
217 echo " --joomla=X: Specify the branch to use on the joomla repository\n";
218 echo " --wordpress=X: Specify the branch to use on the wordpress repository\n";
219 echo " --rebase: Perform a rebase before starting work\n";
220 echo "Known repositories:\n";
221 foreach ($this->repos
as $repo => $relPath) {
222 printf(" %-12s: %s\n", $repo, realpath($this->civiRoot
. DIRECTORY_SEPARATOR
. $relPath));
224 echo "When using 'begin' or 'resume' with a remote base-branch, most repositories\n";
225 echo "will have a detached HEAD. Only repos with an explicit branch will be real,\n";
226 echo "local branches.\n";
229 function doCheckoutAll($baseBranch = NULL) {
231 return $this->returnError("Missing <branch>\n");
233 $branches = $this->resolveBranches($baseBranch, $this->branches
);
238 foreach ($this->repos
as $repo => $relPath) {
239 $filteredBranch = $this->filterBranchName($repo, $branches[$repo]);
240 $this->run($relPath, 'git', 'checkout', $filteredBranch);
245 function doStatusAll() {
246 foreach ($this->repos
as $repo => $relPath) {
247 $this->run($relPath, 'git', 'status');
252 function doBegin($baseBranch = NULL) {
254 return $this->returnError("Missing <base-branch>\n");
256 if (empty($this->branches
)) {
257 return $this->returnError("Must specify a custom branch for at least one repository.\n");
259 $branches = $this->resolveBranches($baseBranch, $this->branches
);
264 foreach ($this->repos
as $repo => $relPath) {
265 $filteredBranch = $this->filterBranchName($repo, $branches[$repo]);
266 $filteredBaseBranch = $this->filterBranchName($repo, $baseBranch);
267 if ($filteredBranch == $filteredBaseBranch) {
268 $this->run($relPath, 'git', 'checkout', $filteredBranch);
271 $this->run($relPath, 'git', 'checkout', '-b', $filteredBranch, $filteredBaseBranch);
276 function doResume($baseBranch = NULL) {
278 return $this->returnError("Missing <base-branch>\n");
280 if (empty($this->branches
)) {
281 return $this->returnError("Must specify a custom branch for at least one repository.\n");
283 $branches = $this->resolveBranches($baseBranch, $this->branches
);
288 foreach ($this->repos
as $repo => $relPath) {
289 $this->run($relPath, 'git', 'checkout', $branches[$repo]);
290 if ($branches[$repo] != $baseBranch && $this->rebase
) {
292 list ($baseRemoteRepo, $baseRemoteBranch) = $this->parseBranchRepo($baseBranch);
293 $this->run($relPath, 'git', 'pull', '--rebase', $baseRemoteRepo, $baseRemoteBranch);
299 * Given a ref name, determine the repo and branch
301 * FIXME: only supports $refs like "foo" (implicit origin) or "myremote/foo"
306 function parseBranchRepo($ref, $defaultRemote = 'origin') {
307 $parts = explode('/', $ref);
308 if (count($parts) == 1) {
309 return array($defaultRemote, $parts[1]);
311 elseif (count($parts) == 2) {
315 throw new Exception("Failed to parse branch name ($ref)");
322 * Any items after $command will be escaped and added to $command
324 * @param string $runDir
325 * @param string $command
328 function run($runDir, $command) {
329 $this->dirStack
->push($runDir);
331 $args = func_get_args();
334 foreach ($args as $arg) {
335 $command .= ' ' . escapeshellarg($arg);
337 printf("\nRUN [%s]: %s\n", $runDir, $command);
341 $r = system($command);
344 $this->dirStack
->pop();
348 function doFetchAll() {
349 foreach ($this->repos
as $repo => $relPath) {
350 $this->run($relPath, 'git', 'fetch', '--all');
355 * @param string $default branch to use by default
356 * @return array ($repoName => $gitRef)
358 function resolveBranches($default, $overrides) {
359 $branches = $overrides;
360 foreach ($this->repos
as $repo => $relPath) {
361 if (!isset($branches[$repo])) {
362 $branches[$repo] = $default;
368 function filterBranchName($repoName, $branchName) {
369 if ($repoName == 'drupal') {
370 $parts = explode('/', $branchName);
371 $last = $this->drupalVersion
. '.x-' . array_pop($parts);
372 array_push($parts, $last);
373 return implode('/', $parts);
378 function returnError($message) {
379 echo "ERROR: ", $message, "\n";