mirror of
https://we.phorge.it/source/arcanist.git
synced 2024-11-26 00:32:41 +01:00
Break arc land run() into smaller functions
Summary: Refactor the arc land code into several functions so it's easier to maintain. This is in preparation for adding hg support to arc land in my next commit. Without this refactor, adding hg support makes the run() function too big. Test Plan: Set up a git repo and clone with a branch scenario. Example: https://secure.phabricator.com/P614 Ran and verified: arc land arc land --keep-branch arc land --merge arc land --merge --keep-branch arc land --hold arc land --revision <another phabricator rev> Reviewers: epriestley Reviewed By: epriestley CC: dschleimer, sid0, aran, Korvin Differential Revision: https://secure.phabricator.com/D4056
This commit is contained in:
parent
b549f565c9
commit
3602d2aa15
1 changed files with 222 additions and 148 deletions
|
@ -6,6 +6,18 @@
|
|||
* @group workflow
|
||||
*/
|
||||
final class ArcanistLandWorkflow extends ArcanistBaseWorkflow {
|
||||
private $isGit;
|
||||
|
||||
private $oldBranch;
|
||||
private $branch;
|
||||
private $onto;
|
||||
private $ontoRemoteBranch;
|
||||
private $remote;
|
||||
private $useSquash;
|
||||
private $keepBranch;
|
||||
|
||||
private $revision;
|
||||
private $message;
|
||||
|
||||
public function getWorkflowName() {
|
||||
return 'land';
|
||||
|
@ -103,14 +115,52 @@ EOTEXT
|
|||
}
|
||||
|
||||
public function run() {
|
||||
$this->readArguments();
|
||||
$this->validate();
|
||||
$this->findRevision();
|
||||
$this->pullFromRemote();
|
||||
|
||||
if ($this->useSquash) {
|
||||
$this->rebase();
|
||||
$this->squash();
|
||||
} else {
|
||||
$this->merge();
|
||||
}
|
||||
|
||||
$this->push();
|
||||
if (!$this->keepBranch) {
|
||||
$this->cleanupBranch();
|
||||
}
|
||||
|
||||
// If we were on some branch A and the user ran "arc land B",
|
||||
// switch back to A.
|
||||
if ($this->oldBranch != $this->branch && $this->oldBranch != $this->onto) {
|
||||
$repository_api = $this->getRepositoryAPI();
|
||||
if (!($repository_api instanceof ArcanistGitAPI)) {
|
||||
$repository_api->execxLocal(
|
||||
'checkout %s',
|
||||
$this->oldBranch);
|
||||
echo phutil_console_format(
|
||||
"Switched back to branch **%s**.\n",
|
||||
$this->oldBranch);
|
||||
}
|
||||
|
||||
echo "Done.\n";
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private function readArguments() {
|
||||
$repository_api = $this->getRepositoryAPI();
|
||||
$this->isGit = $repository_api instanceof ArcanistGitAPI;
|
||||
|
||||
if (!$this->isGit) {
|
||||
throw new ArcanistUsageException("'arc land' only supports git.");
|
||||
}
|
||||
|
||||
$branch = $this->getArgument('branch');
|
||||
if (empty($branch)) {
|
||||
$branch = $repository_api->getBranchName();
|
||||
|
||||
if ($branch) {
|
||||
echo "Landing current branch '{$branch}'.\n";
|
||||
$branch = array($branch);
|
||||
|
@ -121,93 +171,60 @@ EOTEXT
|
|||
throw new ArcanistUsageException(
|
||||
"Specify exactly one branch to land changes from.");
|
||||
}
|
||||
$branch = head($branch);
|
||||
$this->branch = head($branch);
|
||||
|
||||
$onto_default = nonempty(
|
||||
$this->getWorkingCopy()->getConfigFromAnySource('arc.land.onto.default'),
|
||||
'master');
|
||||
|
||||
$remote = $this->getArgument('remote', 'origin');
|
||||
$onto = $this->getArgument('onto', $onto_default);
|
||||
$this->remote = $this->getArgument('remote', 'origin');
|
||||
$this->onto = $this->getArgument('onto', $onto_default);
|
||||
$this->keepBranch = $this->getArgument('keep-branch');
|
||||
|
||||
$is_immutable = $this->isHistoryImmutable();
|
||||
if ($this->getArgument('merge')) {
|
||||
$this->useSquash = false;
|
||||
} else if ($this->getArgument('squash')) {
|
||||
$this->useSquash = true;
|
||||
} else {
|
||||
$this->useSquash = !$this->isHistoryImmutable();
|
||||
}
|
||||
|
||||
if ($onto == $branch) {
|
||||
$this->ontoRemoteBranch = $this->remote.'/'.$this->onto;
|
||||
|
||||
$this->oldBranch = $repository_api->getBranchName();
|
||||
}
|
||||
|
||||
private function validate() {
|
||||
$repository_api = $this->getRepositoryAPI();
|
||||
|
||||
if ($this->onto == $this->branch) {
|
||||
$message =
|
||||
"You can not land a branch onto itself -- you are trying to land ".
|
||||
"'{$branch}' onto '{$onto}'. For more information on how to push ".
|
||||
"changes, see 'Pushing and Closing Revisions' in ".
|
||||
"'{$this->branch}' onto '{$this->onto}'. For more information on ".
|
||||
"how to push changes, see 'Pushing and Closing Revisions' in ".
|
||||
"'Arcanist User Guide: arc diff' in the documentation.";
|
||||
if (!$is_immutable) {
|
||||
if (!$this->isHistoryImmutable()) {
|
||||
$message .= " You may be able to 'arc amend' instead.";
|
||||
}
|
||||
throw new ArcanistUsageException($message);
|
||||
}
|
||||
|
||||
if ($this->getArgument('merge')) {
|
||||
$use_squash = false;
|
||||
} else if ($this->getArgument('squash')) {
|
||||
$use_squash = true;
|
||||
} else {
|
||||
$use_squash = !$is_immutable;
|
||||
}
|
||||
|
||||
list($err) = $repository_api->execManualLocal(
|
||||
'rev-parse --verify %s',
|
||||
$branch);
|
||||
$this->branch);
|
||||
|
||||
if ($err) {
|
||||
throw new ArcanistUsageException("Branch '{$branch}' does not exist.");
|
||||
throw new ArcanistUsageException(
|
||||
"Branch '{$this->branch}' does not exist.");
|
||||
}
|
||||
|
||||
$this->requireCleanWorkingCopy();
|
||||
$repository_api->parseRelativeLocalCommit(array($remote.'/'.$onto));
|
||||
|
||||
$old_branch = $repository_api->getBranchName();
|
||||
|
||||
$repository_api->execxLocal('checkout %s', $onto);
|
||||
|
||||
echo phutil_console_format(
|
||||
"Switched to branch **%s**. Updating branch...\n",
|
||||
$onto);
|
||||
|
||||
$repository_api->execxLocal('pull --ff-only');
|
||||
|
||||
list($out) = $repository_api->execxLocal(
|
||||
'log %s/%s..%s',
|
||||
$remote,
|
||||
$onto,
|
||||
$onto);
|
||||
if (strlen(trim($out))) {
|
||||
throw new ArcanistUsageException(
|
||||
"Local branch '{$onto}' is ahead of '{$remote}/{$onto}', so landing ".
|
||||
"a feature branch would push additional changes. Push or reset the ".
|
||||
"changes in '{$onto}' before running 'arc land'.");
|
||||
}
|
||||
|
||||
$repository_api->execxLocal(
|
||||
'checkout %s',
|
||||
$branch);
|
||||
private function findRevision() {
|
||||
$repository_api = $this->getRepositoryAPI();
|
||||
|
||||
echo phutil_console_format(
|
||||
"Switched to branch **%s**. Identifying and merging...\n",
|
||||
$branch);
|
||||
|
||||
if ($use_squash) {
|
||||
chdir($repository_api->getPath());
|
||||
$err = phutil_passthru('git rebase %s', $onto);
|
||||
if ($err) {
|
||||
throw new ArcanistUsageException(
|
||||
"'git rebase {$onto}' failed. You can abort with 'git rebase ".
|
||||
"--abort', or resolve conflicts and use 'git rebase --continue' to ".
|
||||
"continue forward. After resolving the rebase, run 'arc land' ".
|
||||
"again.");
|
||||
}
|
||||
|
||||
// Now that we've rebased, the merge-base of origin/master and HEAD may
|
||||
// be different. Reparse the relative commit.
|
||||
$repository_api->parseRelativeLocalCommit(array($remote.'/'.$onto));
|
||||
}
|
||||
$repository_api->parseRelativeLocalCommit(array($this->ontoRemoteBranch));
|
||||
|
||||
$revision_id = $this->getArgument('revision');
|
||||
if ($revision_id) {
|
||||
|
@ -230,65 +247,134 @@ EOTEXT
|
|||
|
||||
if (!count($revisions)) {
|
||||
throw new ArcanistUsageException(
|
||||
"arc can not identify which revision exists on branch '{$branch}'. ".
|
||||
"Update the revision with recent changes to synchronize the branch ".
|
||||
"name and hashes, or use 'arc amend' to amend the commit message at ".
|
||||
"HEAD, or use '--revision <id>' to select a revision explicitly.");
|
||||
"arc can not identify which revision exists on branch ".
|
||||
"'{$this->branch}'. Update the revision with recent changes ".
|
||||
"to synchronize the branch name and hashes, or use 'arc amend' ".
|
||||
"to amend the commit message at HEAD, or use '--revision <id>' ".
|
||||
"to select a revision explicitly.");
|
||||
} else if (count($revisions) > 1) {
|
||||
$message =
|
||||
"There are multiple revisions on feature branch '{$branch}' which are ".
|
||||
"not present on '{$onto}':\n\n".
|
||||
"There are multiple revisions on feature branch '{$this->branch}' ".
|
||||
"which are not present on '{$onto}':\n\n".
|
||||
$this->renderRevisionList($revisions)."\n".
|
||||
"Separate these revisions onto different branches, or use ".
|
||||
"'--revision <id>' to select one.";
|
||||
throw new ArcanistUsageException($message);
|
||||
}
|
||||
|
||||
$revision = head($revisions);
|
||||
$rev_id = $revision['id'];
|
||||
$rev_title = $revision['title'];
|
||||
$this->revision = head($revisions);
|
||||
|
||||
if ($revision['status'] != ArcanistDifferentialRevisionStatus::ACCEPTED) {
|
||||
$rev_status = $this->revision['status'];
|
||||
$rev_id = $this->revision['id'];
|
||||
$rev_title = $this->revision['title'];
|
||||
|
||||
if ($rev_status != ArcanistDifferentialRevisionStatus::ACCEPTED) {
|
||||
$ok = phutil_console_confirm(
|
||||
"Revision 'D{$rev_id}: {$rev_title}' has not been accepted. Continue ".
|
||||
"anyway?");
|
||||
"Revision 'D{$rev_id}: {$rev_title}' has not been ".
|
||||
"accepted. Continue anyway?");
|
||||
if (!$ok) {
|
||||
throw new ArcanistUserAbortException();
|
||||
}
|
||||
}
|
||||
|
||||
echo "Landing revision 'D{$rev_id}: {$rev_title}'...\n";
|
||||
|
||||
$message = $this->getConduit()->callMethodSynchronous(
|
||||
$this->message = $this->getConduit()->callMethodSynchronous(
|
||||
'differential.getcommitmessage',
|
||||
array(
|
||||
'revision_id' => $revision['id'],
|
||||
'revision_id' => $rev_id,
|
||||
));
|
||||
|
||||
$repository_api->execxLocal('checkout %s', $onto);
|
||||
echo "Landing revision 'D{$rev_id}: ".
|
||||
"{$rev_title}'...\n";
|
||||
}
|
||||
|
||||
private function pullFromRemote() {
|
||||
$repository_api = $this->getRepositoryAPI();
|
||||
$repository_api->execxLocal('checkout %s', $this->onto);
|
||||
|
||||
echo phutil_console_format(
|
||||
"Switched to branch **%s**. Updating branch...\n",
|
||||
$this->onto);
|
||||
|
||||
$repository_api->execxLocal('pull --ff-only');
|
||||
|
||||
list($out) = $repository_api->execxLocal(
|
||||
'log %s/%s..%s',
|
||||
$this->remote,
|
||||
$this->onto,
|
||||
$this->onto);
|
||||
if (strlen(trim($out))) {
|
||||
throw new ArcanistUsageException(
|
||||
"Local branch '{$this->onto}' is ahead of remote branch ".
|
||||
"'{$this->ontoRemoteBranch}', so landing a feature branch ".
|
||||
"would push additional changes. Push or reset the changes ".
|
||||
"in '{$this->onto}' before running 'arc land'.");
|
||||
}
|
||||
}
|
||||
|
||||
private function rebase() {
|
||||
$repository_api = $this->getRepositoryAPI();
|
||||
$repository_api->execxLocal(
|
||||
'checkout %s',
|
||||
$this->branch);
|
||||
|
||||
echo phutil_console_format(
|
||||
"Switched to branch **%s**. Identifying and merging...\n",
|
||||
$this->branch);
|
||||
|
||||
if ($this->useSquash) {
|
||||
chdir($repository_api->getPath());
|
||||
$err = phutil_passthru('git rebase %s', $this->onto);
|
||||
|
||||
if ($err) {
|
||||
throw new ArcanistUsageException(
|
||||
"'git rebase {$this->onto}' failed. ".
|
||||
"You can abort with 'git rebase --abort', ".
|
||||
"or resolve conflicts and use 'git rebase ".
|
||||
"--continue' to continue forward. After resolving the rebase, ".
|
||||
"run 'arc land' again.");
|
||||
}
|
||||
|
||||
// Now that we've rebased, the merge-base of origin/master and HEAD may
|
||||
// be different. Reparse the relative commit.
|
||||
$repository_api->parseRelativeLocalCommit(array($this->ontoRemoteBranch));
|
||||
}
|
||||
}
|
||||
|
||||
private function squash() {
|
||||
$repository_api = $this->getRepositoryAPI();
|
||||
$repository_api->execxLocal('checkout %s', $this->onto);
|
||||
|
||||
$repository_api->execxLocal(
|
||||
'merge --squash --ff-only %s',
|
||||
$this->branch);
|
||||
}
|
||||
|
||||
private function merge() {
|
||||
$repository_api = $this->getRepositoryAPI();
|
||||
|
||||
if (!$use_squash) {
|
||||
// In immutable histories, do a --no-ff merge to force a merge commit with
|
||||
// the right message.
|
||||
$repository_api->execxLocal('checkout %s', $this->onto);
|
||||
|
||||
chdir($repository_api->getPath());
|
||||
$err = phutil_passthru(
|
||||
'git merge --no-ff --no-commit %s',
|
||||
$branch);
|
||||
$this->branch);
|
||||
|
||||
if ($err) {
|
||||
throw new ArcanistUsageException(
|
||||
"'git merge' failed. Your working copy has been left in a partially ".
|
||||
"merged state. You can: abort with 'git merge --abort'; or follow ".
|
||||
"the instructions to complete the merge.");
|
||||
}
|
||||
} else {
|
||||
// In mutable histories, do a --squash merge.
|
||||
$repository_api->execxLocal(
|
||||
'merge --squash --ff-only %s',
|
||||
$branch);
|
||||
}
|
||||
|
||||
private function push() {
|
||||
$repository_api = $this->getRepositoryAPI();
|
||||
|
||||
$tmp_file = new TempFile();
|
||||
Filesystem::writeFile($tmp_file, $message);
|
||||
Filesystem::writeFile($tmp_file, $this->message);
|
||||
|
||||
$repository_api->execxLocal(
|
||||
'commit -F %s',
|
||||
$tmp_file);
|
||||
|
@ -296,18 +382,20 @@ EOTEXT
|
|||
if ($this->getArgument('hold')) {
|
||||
echo phutil_console_format(
|
||||
"Holding change in **%s**: it has NOT been pushed yet.\n",
|
||||
$onto);
|
||||
$this->onto);
|
||||
} else {
|
||||
echo "Pushing change...\n\n";
|
||||
|
||||
chdir($repository_api->getPath());
|
||||
|
||||
$err = phutil_passthru(
|
||||
'git push %s %s',
|
||||
$remote,
|
||||
$onto);
|
||||
$this->remote,
|
||||
$this->onto);
|
||||
|
||||
if ($err) {
|
||||
throw new ArcanistUsageException("'git push' failed.");
|
||||
$repo_command = $repository_api->getSourceControlSystemName();
|
||||
throw new ArcanistUsageException("'{$repo_command} push' failed.");
|
||||
}
|
||||
|
||||
$mark_workflow = $this->buildChildWorkflow(
|
||||
|
@ -315,34 +403,36 @@ EOTEXT
|
|||
array(
|
||||
'--finalize',
|
||||
'--quiet',
|
||||
$revision['id'],
|
||||
$this->revision['id'],
|
||||
));
|
||||
$mark_workflow->run();
|
||||
|
||||
echo "\n";
|
||||
}
|
||||
}
|
||||
|
||||
if (!$this->getArgument('keep-branch')) {
|
||||
private function cleanupBranch() {
|
||||
$repository_api = $this->getRepositoryAPI();
|
||||
|
||||
echo "Cleaning up feature branch...\n";
|
||||
list($ref) = $repository_api->execxLocal(
|
||||
'rev-parse --verify %s',
|
||||
$branch);
|
||||
$this->branch);
|
||||
$ref = trim($ref);
|
||||
$recovery_command = csprintf(
|
||||
'git checkout -b %s %s',
|
||||
$branch,
|
||||
$this->branch,
|
||||
$ref);
|
||||
|
||||
echo "Cleaning up feature branch...\n";
|
||||
echo "(Use `{$recovery_command}` if you want it back.)\n";
|
||||
$repository_api->execxLocal(
|
||||
'branch -D %s',
|
||||
$branch);
|
||||
$this->branch);
|
||||
|
||||
if ($this->getArgument('delete-remote')) {
|
||||
list($err, $ref) = $repository_api->execManualLocal(
|
||||
'rev-parse --verify %s/%s',
|
||||
$remote,
|
||||
$branch);
|
||||
$this->remote,
|
||||
$this->branch);
|
||||
|
||||
if ($err) {
|
||||
echo "No remote feature branch to clean up.\n";
|
||||
|
@ -356,28 +446,12 @@ EOTEXT
|
|||
echo "Cleaning up remote feature branch...\n";
|
||||
$repository_api->execxLocal(
|
||||
'push %s :%s',
|
||||
$remote,
|
||||
$branch);
|
||||
$this->remote,
|
||||
$this->branch);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If we were on some branch A and the user ran "arc land B", switch back
|
||||
// to A.
|
||||
if (($old_branch != $branch) && ($old_branch != $onto)) {
|
||||
$repository_api->execxLocal(
|
||||
'checkout %s',
|
||||
$old_branch);
|
||||
echo phutil_console_format(
|
||||
"Switched back to branch **%s**.\n",
|
||||
$old_branch);
|
||||
}
|
||||
|
||||
echo "Done.\n";
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
protected function getSupportedRevisionControlSystems() {
|
||||
return array('git');
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue