Changeset View
Changeset View
Standalone View
Standalone View
src/workflow/ArcanistLandWorkflow.php
| Show First 20 Lines • Show All 77 Lines • ▼ Show 20 Lines | EOTEXT | ||||
| public function getArguments() { | public function getArguments() { | ||||
| return array( | return array( | ||||
| 'onto' => array( | 'onto' => array( | ||||
| 'param' => 'master', | 'param' => 'master', | ||||
| 'help' => pht( | 'help' => pht( | ||||
| "Land feature branch onto a branch other than the default ". | "Land feature branch onto a branch other than the default ". | ||||
| "('master' in git, 'default' in hg). You can change the default ". | "('master' in git, 'default' in hg). You can change the default ". | ||||
| "by setting 'arc.land.onto.default' with `arc set-config` or ". | "by setting '%s' with `%s` or for the entire project in %s.", | ||||
| "for the entire project in .arcconfig."), | 'arc.land.onto.default', | ||||
| 'arc set-config', | |||||
| '.arcconfig'), | |||||
| ), | ), | ||||
| 'hold' => array( | 'hold' => array( | ||||
| 'help' => pht( | 'help' => pht( | ||||
| 'Prepare the change to be pushed, but do not actually push it.'), | 'Prepare the change to be pushed, but do not actually push it.'), | ||||
| ), | ), | ||||
| 'keep-branch' => array( | 'keep-branch' => array( | ||||
| 'help' => pht( | 'help' => pht( | ||||
| 'Keep the feature branch after pushing changes to the '. | 'Keep the feature branch after pushing changes to the '. | ||||
| 'remote (by default, it is deleted).'), | 'remote (by default, it is deleted).'), | ||||
| ), | ), | ||||
| 'remote' => array( | 'remote' => array( | ||||
| 'param' => 'origin', | 'param' => 'origin', | ||||
| 'help' => pht( | 'help' => pht( | ||||
| "Push to a remote other than the default ('origin' in git)."), | "Push to a remote other than the default ('origin' in git)."), | ||||
| ), | ), | ||||
| 'merge' => array( | 'merge' => array( | ||||
| 'help' => pht( | 'help' => pht( | ||||
| 'Perform a --no-ff merge, not a --squash merge. If the project '. | 'Perform a %s merge, not a %s merge. If the project '. | ||||
| 'is marked as having an immutable history, this is the default '. | 'is marked as having an immutable history, this is the default '. | ||||
| 'behavior.'), | 'behavior.', | ||||
| '--no-ff', | |||||
| '--squash'), | |||||
| 'supports' => array( | 'supports' => array( | ||||
| 'git', | 'git', | ||||
| ), | ), | ||||
| 'nosupport' => array( | 'nosupport' => array( | ||||
| 'hg' => pht('Use the --squash strategy when landing in mercurial.'), | 'hg' => pht( | ||||
| 'Use the %s strategy when landing in mercurial.', | |||||
| '--squash'), | |||||
| ), | ), | ||||
| ), | ), | ||||
| 'squash' => array( | 'squash' => array( | ||||
| 'help' => pht( | 'help' => pht( | ||||
| 'Perform a --squash merge, not a --no-ff merge. If the project is '. | 'Perform a %s merge, not a %s merge. If the project is '. | ||||
| 'marked as having a mutable history, this is the default behavior.'), | 'marked as having a mutable history, this is the default behavior.', | ||||
| '--squash', | |||||
| '--no-ff'), | |||||
| 'conflicts' => array( | 'conflicts' => array( | ||||
| 'merge' => '--merge and --squash are conflicting merge strategies.', | 'merge' => pht( | ||||
| '%s and %s are conflicting merge strategies.', | |||||
| '--merge', | |||||
| '--squash'), | |||||
| ), | ), | ||||
| ), | ), | ||||
| 'delete-remote' => array( | 'delete-remote' => array( | ||||
| 'help' => pht( | 'help' => pht( | ||||
| 'Delete the feature branch in the remote after landing it.'), | 'Delete the feature branch in the remote after landing it.'), | ||||
| 'conflicts' => array( | 'conflicts' => array( | ||||
| 'keep-branch' => true, | 'keep-branch' => true, | ||||
| ), | ), | ||||
| ), | ), | ||||
| 'update-with-rebase' => array( | 'update-with-rebase' => array( | ||||
| 'help' => pht( | 'help' => pht( | ||||
| "When updating the feature branch, use rebase instead of merge. ". | "When updating the feature branch, use rebase instead of merge. ". | ||||
| "This might make things work better in some cases. Set ". | "This might make things work better in some cases. Set ". | ||||
| "arc.land.update.default to 'rebase' to make this the default."), | "%s to '%s' to make this the default.", | ||||
| 'arc.land.update.default', | |||||
| 'rebase'), | |||||
| 'conflicts' => array( | 'conflicts' => array( | ||||
| 'merge' => pht( | 'merge' => pht( | ||||
| 'The --merge strategy does not update the feature branch.'), | 'The %s strategy does not update the feature branch.', | ||||
| '--merge'), | |||||
| 'update-with-merge' => pht( | 'update-with-merge' => pht( | ||||
| 'Cannot be used with --update-with-merge.'), | 'Cannot be used with %s.', | ||||
| '--update-with-merge'), | |||||
| ), | ), | ||||
| 'supports' => array( | 'supports' => array( | ||||
| 'git', | 'git', | ||||
| ), | ), | ||||
| ), | ), | ||||
| 'update-with-merge' => array( | 'update-with-merge' => array( | ||||
| 'help' => pht( | 'help' => pht( | ||||
| "When updating the feature branch, use merge instead of rebase. ". | "When updating the feature branch, use merge instead of rebase. ". | ||||
| "This is the default behavior. Setting arc.land.update.default to ". | "This is the default behavior. Setting %s to '%s' can also be ". | ||||
| "'merge' can also be used to make this the default."), | "used to make this the default.", | ||||
| 'arc.land.update.default', | |||||
| 'merge'), | |||||
| 'conflicts' => array( | 'conflicts' => array( | ||||
| 'merge' => pht( | 'merge' => pht( | ||||
| 'The --merge strategy does not update the feature branch.'), | 'The %s strategy does not update the feature branch.', | ||||
| '--merge'), | |||||
| 'update-with-rebase' => pht( | 'update-with-rebase' => pht( | ||||
| 'Cannot be used with --update-with-rebase.'), | 'Cannot be used with %s.', | ||||
| '--update-with-rebase'), | |||||
| ), | ), | ||||
| 'supports' => array( | 'supports' => array( | ||||
| 'git', | 'git', | ||||
| ), | ), | ||||
| ), | ), | ||||
| 'revision' => array( | 'revision' => array( | ||||
| 'param' => 'id', | 'param' => 'id', | ||||
| 'help' => pht( | 'help' => pht( | ||||
| ▲ Show 20 Lines • Show All 161 Lines • ▼ Show 20 Lines | if ($this->onto == $this->branch) { | ||||
| "You can not land a %s onto itself -- you are trying ". | "You can not land a %s onto itself -- you are trying ". | ||||
| "to land '%s' onto '%s'. For more information on how to push ". | "to land '%s' onto '%s'. For more information on how to push ". | ||||
| "changes, see 'Pushing and Closing Revisions' in 'Arcanist User ". | "changes, see 'Pushing and Closing Revisions' in 'Arcanist User ". | ||||
| "Guide: arc diff' in the documentation.", | "Guide: arc diff' in the documentation.", | ||||
| $this->branchType, | $this->branchType, | ||||
| $this->branch, | $this->branch, | ||||
| $this->onto); | $this->onto); | ||||
| if (!$this->isHistoryImmutable()) { | if (!$this->isHistoryImmutable()) { | ||||
| $message .= ' '.pht("You may be able to 'arc amend' instead."); | $message .= ' '.pht("You may be able to '%s' instead.", 'arc amend'); | ||||
| } | } | ||||
| throw new ArcanistUsageException($message); | throw new ArcanistUsageException($message); | ||||
| } | } | ||||
| if ($this->isHg) { | if ($this->isHg) { | ||||
| if ($this->useSquash) { | if ($this->useSquash) { | ||||
| if (!$repository_api->supportsRebase()) { | if (!$repository_api->supportsRebase()) { | ||||
| throw new ArcanistUsageException( | throw new ArcanistUsageException( | ||||
| pht( | pht( | ||||
| 'You must enable the rebase extension to use the --squash '. | 'You must enable the rebase extension to use the %s strategy.', | ||||
| 'strategy.')); | '--squash')); | ||||
| } | } | ||||
| } | } | ||||
| if ($this->branchType != $this->ontoType) { | if ($this->branchType != $this->ontoType) { | ||||
| throw new ArcanistUsageException(pht( | throw new ArcanistUsageException(pht( | ||||
| 'Source %s is a %s but destination %s is a %s. When landing a '. | 'Source %s is a %s but destination %s is a %s. When landing a '. | ||||
| '%s, the destination must also be a %s. Use --onto to specify a %s, '. | '%s, the destination must also be a %s. Use %s to specify a %s, '. | ||||
| 'or set arc.land.onto.default in .arcconfig.', | 'or set %s in %s.', | ||||
| $this->branch, | $this->branch, | ||||
| $this->branchType, | $this->branchType, | ||||
| $this->onto, | $this->onto, | ||||
| $this->ontoType, | $this->ontoType, | ||||
| $this->branchType, | $this->branchType, | ||||
| $this->branchType, | $this->branchType, | ||||
| $this->branchType)); | '--onto', | ||||
| $this->branchType, | |||||
| 'arc.land.onto.default', | |||||
| '.arcconfig')); | |||||
| } | } | ||||
| } | } | ||||
| if ($this->isGit) { | if ($this->isGit) { | ||||
| list($err) = $repository_api->execManualLocal( | list($err) = $repository_api->execManualLocal( | ||||
| 'rev-parse --verify %s', | 'rev-parse --verify %s', | ||||
| $this->branch); | $this->branch); | ||||
| if ($err) { | if ($err) { | ||||
| throw new ArcanistUsageException( | throw new ArcanistUsageException( | ||||
| pht("Branch '%s' does not exist.", $this->branch)); | pht("Branch '%s' does not exist.", $this->branch)); | ||||
| } | } | ||||
| } | } | ||||
| $this->requireCleanWorkingCopy(); | $this->requireCleanWorkingCopy(); | ||||
| } | } | ||||
| private function checkoutBranch() { | private function checkoutBranch() { | ||||
| $repository_api = $this->getRepositoryAPI(); | $repository_api = $this->getRepositoryAPI(); | ||||
| if ($this->getBranchOrBookmark() != $this->branch) { | if ($this->getBranchOrBookmark() != $this->branch) { | ||||
| $repository_api->execxLocal( | $repository_api->execxLocal('checkout %s', $this->branch); | ||||
| 'checkout %s', | |||||
| $this->branch); | |||||
| } | } | ||||
| echo phutil_console_format( | echo phutil_console_format( | ||||
| pht('Switched to %s **%s**. Identifying and merging...', | "%s\n", | ||||
| pht( | |||||
| 'Switched to %s **%s**. Identifying and merging...', | |||||
| $this->branchType, | $this->branchType, | ||||
| $this->branch). | $this->branch)); | ||||
| "\n"); | |||||
| } | } | ||||
| private function printPendingCommits() { | private function printPendingCommits() { | ||||
| $repository_api = $this->getRepositoryAPI(); | $repository_api = $this->getRepositoryAPI(); | ||||
| if ($repository_api instanceof ArcanistGitAPI) { | if ($repository_api instanceof ArcanistGitAPI) { | ||||
| list($out) = $repository_api->execxLocal( | list($out) = $repository_api->execxLocal( | ||||
| 'log --oneline %s %s --', | 'log --oneline %s %s --', | ||||
| Show All 15 Lines | if ($repository_api instanceof ArcanistGitAPI) { | ||||
| 'log -r %s --template %s', | 'log -r %s --template %s', | ||||
| $branch_range, | $branch_range, | ||||
| '{node|short} {desc|firstline}\n'); | '{node|short} {desc|firstline}\n'); | ||||
| } | } | ||||
| if (!trim($out)) { | if (!trim($out)) { | ||||
| $this->restoreBranch(); | $this->restoreBranch(); | ||||
| throw new ArcanistUsageException( | throw new ArcanistUsageException( | ||||
| pht('No commits to land from %s.', $this->branch)); | pht('No commits to land from %s.', $this->branch)); | ||||
| } | } | ||||
| echo pht("The following commit(s) will be landed:\n\n%s", $out), "\n"; | echo pht("The following commit(s) will be landed:\n\n%s", $out), "\n"; | ||||
| } | } | ||||
| private function findRevision() { | private function findRevision() { | ||||
| $repository_api = $this->getRepositoryAPI(); | $repository_api = $this->getRepositoryAPI(); | ||||
| Show All 17 Lines | if ($revision_id) { | ||||
| $this->getConduit(), | $this->getConduit(), | ||||
| array()); | array()); | ||||
| } | } | ||||
| if (!count($revisions)) { | if (!count($revisions)) { | ||||
| throw new ArcanistUsageException(pht( | throw new ArcanistUsageException(pht( | ||||
| "arc can not identify which revision exists on %s '%s'. Update the ". | "arc can not identify which revision exists on %s '%s'. Update the ". | ||||
| "revision with recent changes to synchronize the %s name and hashes, ". | "revision with recent changes to synchronize the %s name and hashes, ". | ||||
| "or use 'arc amend' to amend the commit message at HEAD, or use ". | "or use '%s' to amend the commit message at HEAD, or use ". | ||||
| "'--revision <id>' to select a revision explicitly.", | "'%s' to select a revision explicitly.", | ||||
| $this->branchType, | $this->branchType, | ||||
| $this->branch, | $this->branch, | ||||
| $this->branchType)); | $this->branchType, | ||||
| 'arc amend', | |||||
| '--revision <id>')); | |||||
| } else if (count($revisions) > 1) { | } else if (count($revisions) > 1) { | ||||
| $message = pht( | $message = pht( | ||||
| "There are multiple revisions on feature %s '%s' which are not ". | "There are multiple revisions on feature %s '%s' which are not ". | ||||
| "present on '%s':\n\n". | "present on '%s':\n\n". | ||||
| "%s\n". | "%s\n". | ||||
| "Separate these revisions onto different %s, or use --revision <id>' ". | "Separate these revisions onto different %s, or use --revision <id>' ". | ||||
| "to use the commit message from <id> and land them all.", | "to use the commit message from <id> and land them all.", | ||||
| $this->branchType, | $this->branchType, | ||||
| ▲ Show 20 Lines • Show All 59 Lines • ▼ Show 20 Lines | if ($rev_auxiliary) { | ||||
| if (!empty($open_dep_revs)) { | if (!empty($open_dep_revs)) { | ||||
| $open_revs = array(); | $open_revs = array(); | ||||
| foreach ($open_dep_revs as $id => $title) { | foreach ($open_dep_revs as $id => $title) { | ||||
| $open_revs[] = ' - D'.$id.': '.$title; | $open_revs[] = ' - D'.$id.': '.$title; | ||||
| } | } | ||||
| $open_revs = implode("\n", $open_revs); | $open_revs = implode("\n", $open_revs); | ||||
| echo pht("Revision '%s' depends on open revisions:\n\n%s", | echo pht( | ||||
| "Revision '%s' depends on open revisions:\n\n%s", | |||||
| "D{$rev_id}: {$rev_title}", | "D{$rev_id}: {$rev_title}", | ||||
| $open_revs); | $open_revs); | ||||
| $ok = phutil_console_confirm(pht('Continue anyway?')); | $ok = phutil_console_confirm(pht('Continue anyway?')); | ||||
| if (!$ok) { | if (!$ok) { | ||||
| throw new ArcanistUserAbortException(); | throw new ArcanistUserAbortException(); | ||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| $message = $this->getConduit()->callMethodSynchronous( | $message = $this->getConduit()->callMethodSynchronous( | ||||
| 'differential.getcommitmessage', | 'differential.getcommitmessage', | ||||
| array( | array( | ||||
| 'revision_id' => $rev_id, | 'revision_id' => $rev_id, | ||||
| )); | )); | ||||
| $this->messageFile = new TempFile(); | $this->messageFile = new TempFile(); | ||||
| Filesystem::writeFile($this->messageFile, $message); | Filesystem::writeFile($this->messageFile, $message); | ||||
| echo pht("Landing revision '%s'...", | echo pht( | ||||
| "D{$rev_id}: {$rev_title}"), "\n"; | "Landing revision '%s'...", | ||||
| "D{$rev_id}: {$rev_title}")."\n"; | |||||
| $diff_phid = idx($this->revision, 'activeDiffPHID'); | $diff_phid = idx($this->revision, 'activeDiffPHID'); | ||||
| if ($diff_phid) { | if ($diff_phid) { | ||||
| $this->checkForBuildables($diff_phid); | $this->checkForBuildables($diff_phid); | ||||
| } | } | ||||
| } | } | ||||
| private function pullFromRemote() { | private function pullFromRemote() { | ||||
| Show All 20 Lines | if ($this->isGit) { | ||||
| $this->onto); | $this->onto); | ||||
| if (strlen(trim($out))) { | if (strlen(trim($out))) { | ||||
| $local_ahead_of_remote = true; | $local_ahead_of_remote = true; | ||||
| } else if ($this->isGitSvn) { | } else if ($this->isGitSvn) { | ||||
| $repository_api->execxLocal('svn rebase'); | $repository_api->execxLocal('svn rebase'); | ||||
| } | } | ||||
| } else if ($this->isHg) { | } else if ($this->isHg) { | ||||
| echo phutil_console_format(pht( | echo phutil_console_format(pht('Updating **%s**...', $this->onto)."\n"); | ||||
| 'Updating **%s**...', | |||||
| $this->onto)."\n"); | |||||
| try { | try { | ||||
| list($out, $err) = $repository_api->execxLocal('pull'); | list($out, $err) = $repository_api->execxLocal('pull'); | ||||
| $divergedbookmark = $this->onto.'@'.$repository_api->getBranchName(); | $divergedbookmark = $this->onto.'@'.$repository_api->getBranchName(); | ||||
| if (strpos($err, $divergedbookmark) !== false) { | if (strpos($err, $divergedbookmark) !== false) { | ||||
| throw new ArcanistUsageException(phutil_console_format(pht( | throw new ArcanistUsageException(phutil_console_format(pht( | ||||
| "Local bookmark **%s** has diverged from the server's **%s** ". | "Local bookmark **%s** has diverged from the server's **%s** ". | ||||
| "(now labeled **%s**). Please resolve this divergence and run ". | "(now labeled **%s**). Please resolve this divergence and run ". | ||||
| "'arc land' again.", | "'%s' again.", | ||||
| $this->onto, | $this->onto, | ||||
| $this->onto, | $this->onto, | ||||
| $divergedbookmark))); | $divergedbookmark, | ||||
| 'arc land'))); | |||||
| } | } | ||||
| } catch (CommandException $ex) { | } catch (CommandException $ex) { | ||||
| $err = $ex->getError(); | $err = $ex->getError(); | ||||
| $stdout = $ex->getStdOut(); | $stdout = $ex->getStdOut(); | ||||
| // Copied from: PhabricatorRepositoryPullLocalDaemon.php | // Copied from: PhabricatorRepositoryPullLocalDaemon.php | ||||
| // NOTE: Between versions 2.1 and 2.1.1, Mercurial changed the | // NOTE: Between versions 2.1 and 2.1.1, Mercurial changed the | ||||
| // behavior of "hg pull" to return 1 in case of a successful pull | // behavior of "hg pull" to return 1 in case of a successful pull | ||||
| Show All 37 Lines | if ($this->isGit) { | ||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| if ($local_ahead_of_remote) { | if ($local_ahead_of_remote) { | ||||
| throw new ArcanistUsageException(pht( | throw new ArcanistUsageException(pht( | ||||
| "Local %s '%s' is ahead of remote %s '%s', so landing a feature ". | "Local %s '%s' is ahead of remote %s '%s', so landing a feature ". | ||||
| "%s would push additional changes. Push or reset the changes in '%s' ". | "%s would push additional changes. Push or reset the changes in '%s' ". | ||||
| "before running 'arc land'.", | "before running '%s'.", | ||||
| $this->ontoType, | $this->ontoType, | ||||
| $this->onto, | $this->onto, | ||||
| $this->ontoType, | $this->ontoType, | ||||
| $this->ontoRemoteBranch, | $this->ontoRemoteBranch, | ||||
| $this->ontoType, | $this->ontoType, | ||||
| $this->onto)); | $this->onto, | ||||
| 'arc land')); | |||||
| } | } | ||||
| } | } | ||||
| private function rebase() { | private function rebase() { | ||||
| $repository_api = $this->getRepositoryAPI(); | $repository_api = $this->getRepositoryAPI(); | ||||
| chdir($repository_api->getPath()); | chdir($repository_api->getPath()); | ||||
| if ($this->isGit) { | if ($this->isGit) { | ||||
| if ($this->shouldUpdateWithRebase) { | if ($this->shouldUpdateWithRebase) { | ||||
| echo phutil_console_format(pht( | echo phutil_console_format(pht( | ||||
| 'Rebasing **%s** onto **%s**', | 'Rebasing **%s** onto **%s**', | ||||
| $this->branch, | $this->branch, | ||||
| $this->onto)."\n"); | $this->onto)."\n"); | ||||
| $err = phutil_passthru('git rebase %s', $this->onto); | $err = phutil_passthru('git rebase %s', $this->onto); | ||||
| if ($err) { | if ($err) { | ||||
| throw new ArcanistUsageException(pht( | throw new ArcanistUsageException(pht( | ||||
| "'git rebase %s' failed. You can abort with 'git rebase ". | "'%s' failed. You can abort with '%s', or resolve conflicts ". | ||||
| "--abort', or resolve conflicts and use 'git rebase --continue' ". | "and use '%s' to continue forward. After resolving the rebase, ". | ||||
| "to continue forward. After resolving the rebase, run 'arc land' ". | "run '%s' again.", | ||||
| "again.", | sprintf('git rebase %s', $this->onto), | ||||
| $this->onto)); | 'git rebase --abort', | ||||
| 'git rebase --continue', | |||||
| 'arc land')); | |||||
| } | } | ||||
| } else { | } else { | ||||
| echo phutil_console_format(pht( | echo phutil_console_format(pht( | ||||
| 'Merging **%s** into **%s**', | 'Merging **%s** into **%s**', | ||||
| $this->branch, | $this->branch, | ||||
| $this->onto)."\n"); | $this->onto)."\n"); | ||||
| $err = phutil_passthru( | $err = phutil_passthru( | ||||
| 'git merge --no-stat %s -m %s', | 'git merge --no-stat %s -m %s', | ||||
| $this->onto, | $this->onto, | ||||
| pht("Automatic merge by 'arc land'")); | pht("Automatic merge by '%s'", 'arc land')); | ||||
| if ($err) { | if ($err) { | ||||
| throw new ArcanistUsageException(pht( | throw new ArcanistUsageException(pht( | ||||
| "'git merge %s' failed. ". | "'%s' failed. To continue: resolve the conflicts, commit ". | ||||
| "To continue: resolve the conflicts, commit the changes, then run ". | "the changes, then run '%s' again. To abort: run '%s'.", | ||||
| "'arc land' again. To abort: run 'git merge --abort'.", | sprintf('git merge %s', $this->onto), | ||||
| $this->onto)); | 'arc land', | ||||
| 'git merge --abort')); | |||||
| } | } | ||||
| } | } | ||||
| } else if ($this->isHg) { | } else if ($this->isHg) { | ||||
| $onto_tip = $repository_api->getCanonicalRevisionName($this->onto); | $onto_tip = $repository_api->getCanonicalRevisionName($this->onto); | ||||
| $common_ancestor = $repository_api->getCanonicalRevisionName( | $common_ancestor = $repository_api->getCanonicalRevisionName( | ||||
| hgsprintf('ancestor(%s, %s)', | hgsprintf('ancestor(%s, %s)', $this->onto, $this->branch)); | ||||
| $this->onto, | |||||
| $this->branch)); | |||||
| // Only rebase if the local branch is not at the tip of the onto branch. | // Only rebase if the local branch is not at the tip of the onto branch. | ||||
| if ($onto_tip != $common_ancestor) { | if ($onto_tip != $common_ancestor) { | ||||
| // keep branch here so later we can decide whether to remove it | // keep branch here so later we can decide whether to remove it | ||||
| $err = $repository_api->execPassthru( | $err = $repository_api->execPassthru( | ||||
| 'rebase -d %s --keepbranches', | 'rebase -d %s --keepbranches', | ||||
| $this->onto); | $this->onto); | ||||
| if ($err) { | if ($err) { | ||||
| echo phutil_console_format("Aborting rebase\n"); | echo phutil_console_format("%s\n", pht('Aborting rebase')); | ||||
| $repository_api->execManualLocal( | $repository_api->execManualLocal('rebase --abort'); | ||||
| 'rebase --abort'); | |||||
| $this->restoreBranch(); | $this->restoreBranch(); | ||||
| throw new ArcanistUsageException(pht( | throw new ArcanistUsageException(pht( | ||||
| "'hg rebase %s' failed and the rebase was aborted. ". | "'%s' failed and the rebase was aborted. This is most ". | ||||
| "This is most likely due to conflicts. Manually rebase %s onto ". | "likely due to conflicts. Manually rebase %s onto %s, resolve ". | ||||
| "%s, resolve the conflicts, then run 'arc land' again.", | "the conflicts, then run '%s' again.", | ||||
| $this->onto, | sprintf('hg rebase %s', $this->onto), | ||||
| $this->branch, | $this->branch, | ||||
| $this->onto)); | $this->onto, | ||||
| 'arc land')); | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| $repository_api->reloadWorkingCopy(); | $repository_api->reloadWorkingCopy(); | ||||
| } | } | ||||
| private function squash() { | private function squash() { | ||||
| ▲ Show 20 Lines • Show All 43 Lines • ▼ Show 20 Lines | if ($this->isGit) { | ||||
| // Leave its children on the original branch. | // Leave its children on the original branch. | ||||
| $err = $repository_api->execPassthru( | $err = $repository_api->execPassthru( | ||||
| 'rebase --collapse --keep --logfile %s -r %s -d %s', | 'rebase --collapse --keep --logfile %s -r %s -d %s', | ||||
| $this->messageFile, | $this->messageFile, | ||||
| $branch_range, | $branch_range, | ||||
| $this->onto); | $this->onto); | ||||
| if ($err) { | if ($err) { | ||||
| $repository_api->execManualLocal( | $repository_api->execManualLocal('rebase --abort'); | ||||
| 'rebase --abort'); | |||||
| $this->restoreBranch(); | $this->restoreBranch(); | ||||
| throw new ArcanistUsageException( | throw new ArcanistUsageException( | ||||
| "Squashing the commits under {$this->branch} failed. ". | pht( | ||||
| "Manually squash your commits and run 'arc land' again."); | "Squashing the commits under %s failed. ". | ||||
| "Manually squash your commits and run '%s' again.", | |||||
| $this->branch, | |||||
| 'arc land')); | |||||
| } | } | ||||
| if ($repository_api->isBookmark($this->branch)) { | if ($repository_api->isBookmark($this->branch)) { | ||||
| // a bug in mercurial means bookmarks end up on the revision prior | // a bug in mercurial means bookmarks end up on the revision prior | ||||
| // to the collapse when using --collapse with --keep, | // to the collapse when using --collapse with --keep, | ||||
| // so we manually move them to the correct spots | // so we manually move them to the correct spots | ||||
| // see: http://bz.selenic.com/show_bug.cgi?id=3716 | // see: http://bz.selenic.com/show_bug.cgi?id=3716 | ||||
| $repository_api->execxLocal( | $repository_api->execxLocal( | ||||
| ▲ Show 20 Lines • Show All 92 Lines • ▼ Show 20 Lines | if ($alt_count > 0) { | ||||
| 'rebase --keep --keepbranches -d %s -s %s', | 'rebase --keep --keepbranches -d %s -s %s', | ||||
| $this->branch, | $this->branch, | ||||
| $alt_branch); | $alt_branch); | ||||
| } | } | ||||
| } else if ($input == 'a' || $input == 'abort') { | } else if ($input == 'a' || $input == 'abort') { | ||||
| $branch_string = implode("\n", $alt_branches); | $branch_string = implode("\n", $alt_branches); | ||||
| echo | echo | ||||
| "\n", | "\n", | ||||
| pht("Remove the %s starting at these revisions and ". | pht( | ||||
| "run arc land again:\n%s", | "Remove the %s starting at these revisions and run %s again:\n%s", | ||||
| $this->branchType.'s', | $this->branchType.'s', | ||||
| $branch_string), | $branch_string, | ||||
| 'arc land'), | |||||
| "\n\n"; | "\n\n"; | ||||
| throw new ArcanistUserAbortException(); | throw new ArcanistUserAbortException(); | ||||
| } else { | } else { | ||||
| throw new ArcanistUsageException( | throw new ArcanistUsageException( | ||||
| pht('Invalid choice. Aborting arc land.')); | pht('Invalid choice. Aborting arc land.')); | ||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| private function merge() { | private function merge() { | ||||
| $repository_api = $this->getRepositoryAPI(); | $repository_api = $this->getRepositoryAPI(); | ||||
| // In immutable histories, do a --no-ff merge to force a merge commit with | // In immutable histories, do a --no-ff merge to force a merge commit with | ||||
| // the right message. | // the right message. | ||||
| $repository_api->execxLocal('checkout %s', $this->onto); | $repository_api->execxLocal('checkout %s', $this->onto); | ||||
| chdir($repository_api->getPath()); | chdir($repository_api->getPath()); | ||||
| if ($this->isGit) { | if ($this->isGit) { | ||||
| $err = phutil_passthru( | $err = phutil_passthru( | ||||
| 'git merge --no-stat --no-ff --no-commit %s', | 'git merge --no-stat --no-ff --no-commit %s', | ||||
| $this->branch); | $this->branch); | ||||
| if ($err) { | if ($err) { | ||||
| throw new ArcanistUsageException(pht( | throw new ArcanistUsageException(pht( | ||||
| "'git merge' failed. Your working copy has been left in a partially ". | "'%s' failed. Your working copy has been left in a partially ". | ||||
| "merged state. You can: abort with 'git merge --abort'; or follow ". | "merged state. You can: abort with '%s'; or follow the ". | ||||
| "the instructions to complete the merge.")); | "instructions to complete the merge.", | ||||
| 'git merge', | |||||
| 'git merge --abort')); | |||||
| } | } | ||||
| } else if ($this->isHg) { | } else if ($this->isHg) { | ||||
| // HG arc land currently doesn't support --merge. | // HG arc land currently doesn't support --merge. | ||||
| // When merging a bookmark branch to a master branch that | // When merging a bookmark branch to a master branch that | ||||
| // hasn't changed since the fork, mercurial fails to merge. | // hasn't changed since the fork, mercurial fails to merge. | ||||
| // Instead of only working in some cases, we just disable --merge | // Instead of only working in some cases, we just disable --merge | ||||
| // until there is a demand for it. | // until there is a demand for it. | ||||
| // The user should never reach this line, since --merge is | // The user should never reach this line, since --merge is | ||||
| // forbidden at the command line argument level. | // forbidden at the command line argument level. | ||||
| throw new ArcanistUsageException(pht( | throw new ArcanistUsageException( | ||||
| '--merge is not currently supported for hg repos.')); | pht('%s is not currently supported for hg repos.', '--merge')); | ||||
| } | } | ||||
| } | } | ||||
| private function push() { | private function push() { | ||||
| $repository_api = $this->getRepositoryAPI(); | $repository_api = $this->getRepositoryAPI(); | ||||
| // these commands can fail legitimately (e.g. commit hooks) | // These commands can fail legitimately (e.g. commit hooks) | ||||
| try { | try { | ||||
| if ($this->isGit) { | if ($this->isGit) { | ||||
| $repository_api->execxLocal( | $repository_api->execxLocal('commit -F %s', $this->messageFile); | ||||
| 'commit -F %s', | |||||
| $this->messageFile); | |||||
| if (phutil_is_windows()) { | if (phutil_is_windows()) { | ||||
| // Occasionally on large repositories on Windows, Git can exit with | // Occasionally on large repositories on Windows, Git can exit with | ||||
| // an unclean working copy here. This prevents reverts from being | // an unclean working copy here. This prevents reverts from being | ||||
| // pushed to the remote when this occurs. | // pushed to the remote when this occurs. | ||||
| $this->requireCleanWorkingCopy(); | $this->requireCleanWorkingCopy(); | ||||
| } | } | ||||
| } else if ($this->isHg) { | } else if ($this->isHg) { | ||||
| // hg rebase produces a commit earlier as part of rebase | // hg rebase produces a commit earlier as part of rebase | ||||
| Show All 22 Lines | if ($this->getArgument('hold')) { | ||||
| echo pht('Pushing change...'), "\n\n"; | echo pht('Pushing change...'), "\n\n"; | ||||
| chdir($repository_api->getPath()); | chdir($repository_api->getPath()); | ||||
| if ($this->isGitSvn) { | if ($this->isGitSvn) { | ||||
| $err = phutil_passthru('git svn dcommit'); | $err = phutil_passthru('git svn dcommit'); | ||||
| $cmd = 'git svn dcommit'; | $cmd = 'git svn dcommit'; | ||||
| } else if ($this->isGit) { | } else if ($this->isGit) { | ||||
| $err = phutil_passthru( | $err = phutil_passthru('git push %s %s', $this->remote, $this->onto); | ||||
| 'git push %s %s', | |||||
| $this->remote, | |||||
| $this->onto); | |||||
| $cmd = 'git push'; | $cmd = 'git push'; | ||||
| } else if ($this->isHgSvn) { | } else if ($this->isHgSvn) { | ||||
| // hg-svn doesn't support 'push -r', so we do a normal push | // hg-svn doesn't support 'push -r', so we do a normal push | ||||
| // which hg-svn modifies to only push the current branch and | // which hg-svn modifies to only push the current branch and | ||||
| // ancestors. | // ancestors. | ||||
| $err = $repository_api->execPassthru( | $err = $repository_api->execPassthru('push %s', $this->remote); | ||||
| 'push %s', | |||||
| $this->remote); | |||||
| $cmd = 'hg push'; | $cmd = 'hg push'; | ||||
| } else if ($this->isHg) { | } else if ($this->isHg) { | ||||
| $err = $repository_api->execPassthru( | $err = $repository_api->execPassthru( | ||||
| 'push -r %s %s', | 'push -r %s %s', | ||||
| $this->onto, | $this->onto, | ||||
| $this->remote); | $this->remote); | ||||
| $cmd = 'hg push'; | $cmd = 'hg push'; | ||||
| } | } | ||||
| if ($err) { | if ($err) { | ||||
| $failed_str = pht('PUSH FAILED!'); | echo phutil_console_format( | ||||
| echo phutil_console_format("<bg:red>** %s **</bg>\n", $failed_str); | "<bg:red>** %s **</bg>\n", | ||||
| pht('PUSH FAILED!')); | |||||
| $this->executeCleanupAfterFailedPush(); | $this->executeCleanupAfterFailedPush(); | ||||
| if ($this->isGit) { | if ($this->isGit) { | ||||
| throw new ArcanistUsageException(pht( | throw new ArcanistUsageException(pht( | ||||
| "'%s' failed! Fix the error and run 'arc land' again.", | "'%s' failed! Fix the error and run '%s' again.", | ||||
| $cmd)); | $cmd, | ||||
| 'arc land')); | |||||
| } | } | ||||
| throw new ArcanistUsageException(pht( | throw new ArcanistUsageException(pht( | ||||
| "'%s' failed! Fix the error and push this change manually.", | "'%s' failed! Fix the error and push this change manually.", | ||||
| $cmd)); | $cmd)); | ||||
| } | } | ||||
| $this->askForRepositoryUpdate(); | $this->askForRepositoryUpdate(); | ||||
| Show All 32 Lines | if ($this->isGit) { | ||||
| 'rev-parse --verify %s', | 'rev-parse --verify %s', | ||||
| $this->branch); | $this->branch); | ||||
| $ref = trim($ref); | $ref = trim($ref); | ||||
| $recovery_command = csprintf( | $recovery_command = csprintf( | ||||
| 'git checkout -b %s %s', | 'git checkout -b %s %s', | ||||
| $this->branch, | $this->branch, | ||||
| $ref); | $ref); | ||||
| echo pht('(Use `%s` if you want it back.)', $recovery_command), "\n"; | echo pht('(Use `%s` if you want it back.)', $recovery_command), "\n"; | ||||
| $repository_api->execxLocal( | $repository_api->execxLocal('branch -D %s', $this->branch); | ||||
| 'branch -D %s', | |||||
| $this->branch); | |||||
| } else if ($this->isHg) { | } else if ($this->isHg) { | ||||
| $common_ancestor = $repository_api->getCanonicalRevisionName( | $common_ancestor = $repository_api->getCanonicalRevisionName( | ||||
| hgsprintf('ancestor(%s,%s)', | hgsprintf('ancestor(%s,%s)', $this->onto, $this->branch)); | ||||
| $this->onto, | |||||
| $this->branch)); | |||||
| $branch_root = $repository_api->getCanonicalRevisionName( | $branch_root = $repository_api->getCanonicalRevisionName( | ||||
| hgsprintf('first((%s::%s)-%s)', | hgsprintf('first((%s::%s)-%s)', | ||||
| $common_ancestor, | $common_ancestor, | ||||
| $this->branch, | $this->branch, | ||||
| $common_ancestor)); | $common_ancestor)); | ||||
| $repository_api->execxLocal( | $repository_api->execxLocal( | ||||
| '--config extensions.mq= strip -r %s', | '--config extensions.mq= strip -r %s', | ||||
| $branch_root); | $branch_root); | ||||
| if ($repository_api->isBookmark($this->branch)) { | if ($repository_api->isBookmark($this->branch)) { | ||||
| $repository_api->execxLocal( | $repository_api->execxLocal('bookmark -d %s', $this->branch); | ||||
| 'bookmark -d %s', | |||||
| $this->branch); | |||||
| } | } | ||||
| } | } | ||||
| if ($this->getArgument('delete-remote')) { | if ($this->getArgument('delete-remote')) { | ||||
| if ($this->isGit) { | if ($this->isGit) { | ||||
| list($err, $ref) = $repository_api->execManualLocal( | list($err, $ref) = $repository_api->execManualLocal( | ||||
| 'rev-parse --verify %s/%s', | 'rev-parse --verify %s/%s', | ||||
| $this->remote, | $this->remote, | ||||
| $this->branch); | $this->branch); | ||||
| if ($err) { | if ($err) { | ||||
| echo pht('No remote feature %s to clean up.', | echo pht( | ||||
| $this->branchType), "\n"; | 'No remote feature %s to clean up.', | ||||
| $this->branchType); | |||||
| echo "\n"; | |||||
| } else { | } else { | ||||
| // NOTE: In Git, you delete a remote branch by pushing it with a | // NOTE: In Git, you delete a remote branch by pushing it with a | ||||
| // colon in front of its name: | // colon in front of its name: | ||||
| // | // | ||||
| // git push <remote> :<branch> | // git push <remote> :<branch> | ||||
| echo pht('Cleaning up remote feature %s...', $this->branchType), "\n"; | echo pht('Cleaning up remote feature %s...', $this->branchType), "\n"; | ||||
| ▲ Show 20 Lines • Show All 42 Lines • ▼ Show 20 Lines | |||||
| } | } | ||||
| /** | /** | ||||
| * Restore the original branch, e.g. after a successful land or a failed | * Restore the original branch, e.g. after a successful land or a failed | ||||
| * pull. | * pull. | ||||
| */ | */ | ||||
| private function restoreBranch() { | private function restoreBranch() { | ||||
| $repository_api = $this->getRepositoryAPI(); | $repository_api = $this->getRepositoryAPI(); | ||||
| $repository_api->execxLocal( | $repository_api->execxLocal('checkout %s', $this->oldBranch); | ||||
| 'checkout %s', | |||||
| $this->oldBranch); | |||||
| if ($this->isGit) { | if ($this->isGit) { | ||||
| $repository_api->execxLocal( | $repository_api->execxLocal('submodule update --init --recursive'); | ||||
| 'submodule update --init --recursive'); | |||||
| } | } | ||||
| echo phutil_console_format( | echo pht( | ||||
| "Switched back to {$this->branchType} **%s**.\n", | "Switched back to %s %s.\n", | ||||
| $this->oldBranch); | $this->branchType, | ||||
| phutil_console_format('**%s**', $this->oldBranch)); | |||||
| } | } | ||||
| /** | /** | ||||
| * Check if a diff has a running or failed buildable, and prompt the user | * Check if a diff has a running or failed buildable, and prompt the user | ||||
| * before landing if it does. | * before landing if it does. | ||||
| */ | */ | ||||
| private function checkForBuildables($diff_phid) { | private function checkForBuildables($diff_phid) { | ||||
| Show All 20 Lines | private function checkForBuildables($diff_phid) { | ||||
| $console = PhutilConsole::getConsole(); | $console = PhutilConsole::getConsole(); | ||||
| $buildable = head($buildables['data']); | $buildable = head($buildables['data']); | ||||
| if ($buildable['buildableStatus'] == 'passed') { | if ($buildable['buildableStatus'] == 'passed') { | ||||
| $console->writeOut( | $console->writeOut( | ||||
| "**<bg:green> %s </bg>** %s\n", | "**<bg:green> %s </bg>** %s\n", | ||||
| pht('BUILDS PASSED'), | pht('BUILDS PASSED'), | ||||
| pht( | pht('Harbormaster builds for the active diff completed successfully.')); | ||||
| 'Harbormaster builds for the active diff completed successfully.')); | |||||
| return; | return; | ||||
| } | } | ||||
| switch ($buildable['buildableStatus']) { | switch ($buildable['buildableStatus']) { | ||||
| case 'building': | case 'building': | ||||
| $message = pht( | $message = pht( | ||||
| 'Harbormaster is still building the active diff for this revision:'); | 'Harbormaster is still building the active diff for this revision:'); | ||||
| $prompt = pht('Land revision anyway, despite ongoing build?'); | $prompt = pht('Land revision anyway, despite ongoing build?'); | ||||
| ▲ Show 20 Lines • Show All 48 Lines • Show Last 20 Lines | |||||