1
0
Fork 0
mirror of https://we.phorge.it/source/arcanist.git synced 2024-09-20 00:49:11 +02:00

Arcanist - stop arc land if local is ahead of remote

Summary: Fixes T4291. Also pht user-facing strings.

Test Plan:
made a branch `foo` off master. made a commit and a diff in `foo`. switched backed to master and cowboy committed some thing. went back to branch`foo`, did an arc land, and saw the error message. went back to master, did a git resert --hard HEAD^1, went back to branch `foo`, and then successfully arc landed.

also ran arc help land and things looked good

Reviewers: epriestley

Reviewed By: epriestley

Subscribers: epriestley, Korvin

Maniphest Tasks: T4291

Differential Revision: https://secure.phabricator.com/D8738
This commit is contained in:
Bob Trahan 2014-04-09 12:35:31 -07:00
parent ac528ab3a8
commit 02456c0aed

View file

@ -81,70 +81,76 @@ EOTEXT
return array( return array(
'onto' => array( 'onto' => array(
'param' => 'master', 'param' => 'master',
'help' => "Land feature branch onto a branch other than the default ". 'help' => pht('Land feature branch onto a branch other than the '.
"('master' in git, 'default' in hg). You can change the ". 'default (\'master\' in git, \'default\' in hg). You '.
"default by setting 'arc.land.onto.default' with ". 'can change the default by setting '.
"`arc set-config` or for the entire project in .arcconfig.", '\'arc.land.onto.default\' with `arc set-config` or '.
'for the entire project in .arcconfig.'),
), ),
'hold' => array( 'hold' => array(
'help' => "Prepare the change to be pushed, but do not actually ". 'help' => pht('Prepare the change to be pushed, but do not actually '.
"push it.", 'push it.'),
), ),
'keep-branch' => array( 'keep-branch' => array(
'help' => "Keep the feature branch after pushing changes to the ". 'help' => pht('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' => "Push to a remote other than the default ('origin' in git).", 'help' => pht('Push to a remote other than the default (\'origin\' '.
'in git).'),
), ),
'merge' => array( 'merge' => array(
'help' => 'Perform a --no-ff merge, not a --squash merge. If the '. 'help' => pht('Perform a --no-ff merge, not a --squash merge. If the '.
'project is marked as having an immutable history, this is '. 'project is marked as having an immutable history, '.
'the default behavior.', 'this is the default behavior.'),
'supports' => array( 'supports' => array(
'git', 'git',
), ),
'nosupport' => array( 'nosupport' => array(
'hg' => 'Use the --squash strategy when landing in mercurial.', 'hg' => pht('Use the --squash strategy when landing in mercurial.'),
), ),
), ),
'squash' => array( 'squash' => array(
'help' => 'Perform a --squash merge, not a --no-ff merge. If the '. 'help' => pht('Perform a --squash merge, not a --no-ff merge. If the '.
'project is marked as having a mutable history, this is '. 'project is marked as having a mutable history, this '.
'the default behavior.', 'is the default behavior.'),
'conflicts' => array( 'conflicts' => array(
'merge' => '--merge and --squash are conflicting merge strategies.', 'merge' => '--merge and --squash are conflicting merge strategies.',
), ),
), ),
'delete-remote' => array( 'delete-remote' => array(
'help' => 'Delete the feature branch in the remote after '. 'help' => pht('Delete the feature branch in the remote after '.
'landing it.', 'landing it.'),
'conflicts' => array( 'conflicts' => array(
'keep-branch' => true, 'keep-branch' => true,
), ),
), ),
'update-with-rebase' => array( 'update-with-rebase' => array(
'help' => 'When updating the feature branch, use rebase instead of '. 'help' => pht('When updating the feature branch, use rebase '.
'merge. This might make things work better in some cases.'. 'instead of merge. This might make things work '.
' Set arc.land.update.default to \'rebase\' to make this '. 'better in some cases. Set arc.land.update.default '.
'default.', 'to \'rebase\' to make this the default.'),
'conflicts' => array( 'conflicts' => array(
'merge' => 'The --merge strategy does not update the feature branch.', 'merge' => pht('The --merge strategy does not update the feature '.
'update-with-merge' => 'Cannot be used with --update-with-merge.', 'branch.'),
'update-with-merge' => pht('Cannot be used with '.
'--update-with-merge.'),
), ),
'supports' => array( 'supports' => array(
'git', 'git',
), ),
), ),
'update-with-merge' => array( 'update-with-merge' => array(
'help' => 'When updating the feature branch, use merge instead of '. 'help' => pht('When updating the feature branch, use merge instead '.
'rebase. This is the default behavior. '. 'of rebase. This is the default behavior. Setting '.
'Setting arc.land.update.default to \'merge\' can also '. 'arc.land.update.default to \'merge\' can also be '.
'be used to make this the default.', 'used to make this the default.'),
'conflicts' => array( 'conflicts' => array(
'merge' => 'The --merge strategy does not update the feature branch.', 'merge' => pht('The --merge strategy does not update the feature '.
'update-with-rebase' => 'Cannot be used with --update-with-rebase.', 'branch.'),
'update-with-rebase' => pht('Cannot be used with '.
'--update-with-rebase.'),
), ),
'supports' => array( 'supports' => array(
'git', 'git',
@ -152,12 +158,12 @@ EOTEXT
), ),
'revision' => array( 'revision' => array(
'param' => 'id', 'param' => 'id',
'help' => 'Use the message from a specific revision, rather than '. 'help' => pht('Use the message from a specific revision, rather than '.
'inferring the revision based on branch content.', 'inferring the revision based on branch content.'),
), ),
'preview' => array( 'preview' => array(
'help' => 'Prints the commits that would be landed. Does not actually '. 'help' => pht('Prints the commits that would be landed. Does not '.
'modify or land the commits.' 'actually modify or land the commits.'),
), ),
'*' => 'branch', '*' => 'branch',
); );
@ -204,7 +210,7 @@ EOTEXT
} }
} }
echo "Done.\n"; echo pht('Done.'), "\n";
return 0; return 0;
} }
@ -216,7 +222,7 @@ EOTEXT
if (!$this->isGit && !$this->isHg) { if (!$this->isGit && !$this->isHg) {
throw new ArcanistUsageException( throw new ArcanistUsageException(
"'arc land' only supports git and mercurial."); pht("'arc land' only supports git and mercurial."));
} }
if ($this->isGit) { if ($this->isGit) {
@ -234,14 +240,14 @@ EOTEXT
if ($branch) { if ($branch) {
$this->branchType = $this->getBranchType($branch); $this->branchType = $this->getBranchType($branch);
echo "Landing current {$this->branchType} '{$branch}'.\n"; echo pht("Landing current %s '%s'.", $this->branchType, $branch), "\n";
$branch = array($branch); $branch = array($branch);
} }
} }
if (count($branch) !== 1) { if (count($branch) !== 1) {
throw new ArcanistUsageException( throw new ArcanistUsageException(
"Specify exactly one branch or bookmark to land changes from."); pht('Specify exactly one branch or bookmark to land changes from.'));
} }
$this->branch = head($branch); $this->branch = head($branch);
$this->keepBranch = $this->getArgument('keep-branch'); $this->keepBranch = $this->getArgument('keep-branch');
@ -294,13 +300,16 @@ EOTEXT
$repository_api = $this->getRepositoryAPI(); $repository_api = $this->getRepositoryAPI();
if ($this->onto == $this->branch) { if ($this->onto == $this->branch) {
$message = $message = pht(
"You can not land a {$this->branchType} onto itself -- you are trying ". "You can not land a %s onto itself -- you are trying ".
"to land '{$this->branch}' onto '{$this->onto}'. For more ". "to land '%s' onto '%s'. For more information on how to push ".
"information on how to push changes, see 'Pushing and Closing ". "changes, see 'Pushing and Closing Revisions' in 'Arcanist User ".
"Revisions' in 'Arcanist User Guide: arc diff' in the documentation."; "Guide: arc diff' in the documentation.",
$this->branchType,
$this->branch,
$this->onto);
if (!$this->isHistoryImmutable()) { if (!$this->isHistoryImmutable()) {
$message .= " You may be able to 'arc amend' instead."; $message .= ' ' . pht("You may be able to 'arc amend' instead.");
} }
throw new ArcanistUsageException($message); throw new ArcanistUsageException($message);
} }
@ -309,18 +318,23 @@ EOTEXT
if ($this->useSquash) { if ($this->useSquash) {
if (!$repository_api->supportsRebase()) { if (!$repository_api->supportsRebase()) {
throw new ArcanistUsageException( throw new ArcanistUsageException(
"You must enable the rebase extension to use ". pht("You must enable the rebase extension to use the --squash ".
"the --squash strategy."); "strategy."));
} }
} }
if ($this->branchType != $this->ontoType) { if ($this->branchType != $this->ontoType) {
throw new ArcanistUsageException( throw new ArcanistUsageException(pht(
"Source {$this->branch} is a {$this->branchType} but destination ". "Source %s is a %s but destination %s is a %s. When landing a ".
"{$this->onto} is a {$this->ontoType}. When landing a ". "%s, the destination must also be a %s. Use --onto to specify a %s, ".
"{$this->branchType}, the destination must also be a ". "or set arc.land.onto.default in .arcconfig.",
"{$this->branchType}. Use --onto to specify a {$this->branchType}, ". $this->branch,
"or set arc.land.onto.default in .arcconfig."); $this->branchType,
$this->onto,
$this->ontoType,
$this->branchType,
$this->branchType,
$this->branchType));
} }
} }
@ -331,7 +345,7 @@ EOTEXT
if ($err) { if ($err) {
throw new ArcanistUsageException( throw new ArcanistUsageException(
"Branch '{$this->branch}' does not exist."); pht("Branch '%s' does not exist.", $this->branch));
} }
} }
@ -347,8 +361,10 @@ EOTEXT
} }
echo phutil_console_format( echo phutil_console_format(
"Switched to {$this->branchType} **%s**. Identifying and merging...\n", pht("Switched to %s **%s**. Identifying and merging...",
$this->branch); $this->branchType,
$this->branch).
"\n");
} }
private function printPendingCommits() { private function printPendingCommits() {
@ -380,10 +396,10 @@ EOTEXT
if (!trim($out)) { if (!trim($out)) {
$this->restoreBranch(); $this->restoreBranch();
throw new ArcanistUsageException( throw new ArcanistUsageException(
"No commits to land from {$this->branch}."); pht("No commits to land from %s.", $this->branch));
} }
echo "The following commit(s) will be landed:\n\n{$out}\n"; echo pht("The following commit(s) will be landed:\n\n%s", $out), "\n";
} }
private function findRevision() { private function findRevision() {
@ -400,7 +416,9 @@ EOTEXT
'ids' => array($revision_id), 'ids' => array($revision_id),
)); ));
if (!$revisions) { if (!$revisions) {
throw new ArcanistUsageException("No such revision 'D{$revision_id}'!"); throw new ArcanistUsageException(pht(
"No such revision '%s'!",
"D{$revision_id}"));
} }
} else { } else {
$revisions = $repository_api->loadWorkingCopyDifferentialRevisions( $revisions = $repository_api->loadWorkingCopyDifferentialRevisions(
@ -409,20 +427,26 @@ EOTEXT
} }
if (!count($revisions)) { if (!count($revisions)) {
throw new ArcanistUsageException( throw new ArcanistUsageException(pht(
"arc can not identify which revision exists on {$this->branchType} ". "arc can not identify which revision exists on %s '%s'. Update the '.
"'{$this->branch}'. Update the revision with recent changes ". 'revision with recent changes to synchronize the %s name and hashes, '.
"to synchronize the {$this->branchType} name and hashes, or use ". 'or use 'arc amend' to amend the commit message at HEAD, or use ".
"'arc amend' to amend the commit message at HEAD, or use ". "'--revision <id>' to select a revision explicitly.",
"'--revision <id>' to select a revision explicitly."); $this->branchType,
$this->branch,
$this->branchType));
} else if (count($revisions) > 1) { } else if (count($revisions) > 1) {
$message = $message = pht(
"There are multiple revisions on feature {$this->branchType} ". "There are multiple revisions on feature %s '%s' which are not ".
"'{$this->branch}' which are not present on '{$this->onto}':\n\n". "present on '%s':\n\n".
$this->renderRevisionList($revisions)."\n". "%s\n".
"Separate these revisions onto different {$this->branchType}s, or use ". "Separate these revisions onto different %s, or use --revision <id>' ".
"'--revision <id>' to use the commit message from <id> and land them ". "to use the commit message from <id> and land them all.",
"all."; $this->branchType,
$this->branch,
$this->onto,
$this->renderRevisionList($revisions),
$this->branchType.'s');
throw new ArcanistUsageException($message); throw new ArcanistUsageException($message);
} }
@ -441,18 +465,21 @@ EOTEXT
)); ));
$other_author = ipull($other_author, 'userName', 'phid'); $other_author = ipull($other_author, 'userName', 'phid');
$other_author = $other_author[$this->revision['authorPHID']]; $other_author = $other_author[$this->revision['authorPHID']];
$ok = phutil_console_confirm( $ok = phutil_console_confirm(pht(
"This {$this->branchType} has revision 'D{$rev_id}: {$rev_title}' ". "This %s has revision '%s' but you are not the author. Land this ".
"but you are not the author. Land this revision by {$other_author}?"); "revision by %s?",
$this->branchType,
"D{$rev_id}: {$rev_title}",
$other_author));
if (!$ok) { if (!$ok) {
throw new ArcanistUserAbortException(); throw new ArcanistUserAbortException();
} }
} }
if ($rev_status != ArcanistDifferentialRevisionStatus::ACCEPTED) { if ($rev_status != ArcanistDifferentialRevisionStatus::ACCEPTED) {
$ok = phutil_console_confirm( $ok = phutil_console_confirm(pht(
"Revision 'D{$rev_id}: {$rev_title}' has not been ". "Revision '%s' has not been accepted. Contine anyway?",
"accepted. Continue anyway?"); "D{$rev_id}: {$rev_title}"));
if (!$ok) { if (!$ok) {
throw new ArcanistUserAbortException(); throw new ArcanistUserAbortException();
} }
@ -483,11 +510,11 @@ EOTEXT
} }
$open_revs = implode("\n", $open_revs); $open_revs = implode("\n", $open_revs);
echo "Revision 'D{$rev_id}: {$rev_title}' depends ". echo pht("Revision '%s' depends on open revisions:\n\n%s",
"on open revisions:\n\n"; "D{$rev_id}: {$rev_title}",
echo $open_revs; $open_revs);
$ok = phutil_console_confirm("Continue anyway?"); $ok = phutil_console_confirm(pht("Continue anyway?"));
if (!$ok) { if (!$ok) {
throw new ArcanistUserAbortException(); throw new ArcanistUserAbortException();
} }
@ -504,8 +531,8 @@ EOTEXT
$this->messageFile = new TempFile(); $this->messageFile = new TempFile();
Filesystem::writeFile($this->messageFile, $message); Filesystem::writeFile($this->messageFile, $message);
echo "Landing revision 'D{$rev_id}: ". echo pht("Landing revision '%s'...",
"{$rev_title}'...\n"; "D{$rev_id}: {$rev_title}"), "\n";
} }
private function pullFromRemote() { private function pullFromRemote() {
@ -515,9 +542,9 @@ EOTEXT
if ($this->isGit) { if ($this->isGit) {
$repository_api->execxLocal('checkout %s', $this->onto); $repository_api->execxLocal('checkout %s', $this->onto);
echo phutil_console_format( echo phutil_console_format(pht(
"Switched to branch **%s**. Updating branch...\n", "Switched to branch **%s**. Updating branch...\n",
$this->onto); $this->onto));
try { try {
$repository_api->execxLocal('pull --ff-only --no-stat'); $repository_api->execxLocal('pull --ff-only --no-stat');
@ -525,32 +552,34 @@ EOTEXT
if (!$this->isGitSvn) { if (!$this->isGitSvn) {
throw $ex; throw $ex;
} }
list($out) = $repository_api->execxLocal( }
'log %s..%s', list($out) = $repository_api->execxLocal(
$this->ontoRemoteBranch, 'log %s..%s',
$this->onto); $this->ontoRemoteBranch,
if (strlen(trim($out))) { $this->onto);
$local_ahead_of_remote = true; if (strlen(trim($out))) {
} else { $local_ahead_of_remote = true;
$repository_api->execxLocal('svn rebase'); } else if ($this->isGitSvn) {
} $repository_api->execxLocal('svn rebase');
} }
} else if ($this->isHg) { } else if ($this->isHg) {
echo phutil_console_format( echo phutil_console_format(pht(
"Updating **%s**...\n", "Updating **%s**...",
$this->onto); $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( throw new ArcanistUsageException(phutil_console_format(pht(
"Local bookmark **{$this->onto}** has diverged from the ". "Local bookmark **%s** has diverged from the server's **%s** ".
"server's **{$this->onto}** (now labeled ". "(now labeled **%s**). Please resolve this divergence and run ".
"**{$divergedbookmark}**). Please resolve this divergence and ". "'arc land' again.",
"run 'arc land' again.")); $this->onto,
$this->onto,
$divergedbookmark)));
} }
} catch (CommandException $ex) { } catch (CommandException $ex) {
$err = $ex->getError(); $err = $ex->getError();
@ -601,11 +630,16 @@ EOTEXT
} }
if ($local_ahead_of_remote) { if ($local_ahead_of_remote) {
throw new ArcanistUsageException( throw new ArcanistUsageException(pht(
"Local {$this->ontoType} '{$this->onto}' is ahead of remote ". "Local %s '%s' is ahead of remote %s '%s', so landing a feature ".
"{$this->ontoType} '{$this->ontoRemoteBranch}', so landing a feature ". "%s would push additional changes. Push or reset the changes in '%s' ".
"{$this->ontoType} would push additional changes. Push or reset the ". "before running 'arc land'.",
"changes in '{$this->onto}' before running 'arc land'."); $this->ontoType,
$this->onto,
$this->ontoType,
$this->ontoRemoteBranch,
$this->ontoType,
$this->onto));
} }
} }
@ -615,33 +649,34 @@ EOTEXT
chdir($repository_api->getPath()); chdir($repository_api->getPath());
if ($this->isGit) { if ($this->isGit) {
if ($this->shouldUpdateWithRebase) { if ($this->shouldUpdateWithRebase) {
echo phutil_console_format( echo phutil_console_format(pht(
"Rebasing **%s** onto **%s**\n", "Rebasing **%s** onto **%s**",
$this->branch, $this->branch,
$this->onto); $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( throw new ArcanistUsageException(pht(
"'git rebase {$this->onto}' failed. ". "'git rebase %s' failed. You can abort with 'git rebase ".
"You can abort with 'git rebase --abort', ". "--abort', or resolve conflicts and use 'git rebase --continue' ".
"or resolve conflicts and use 'git rebase ". "to continue forward. After resolving the rebase, run 'arc land' ".
"--continue' to continue forward. After resolving the rebase, ". "again.",
"run 'arc land' again."); $this->onto));
} }
} else { } else {
echo phutil_console_format( echo phutil_console_format(pht(
"Merging **%s** into **%s**\n", "Merging **%s** into **%s**",
$this->branch, $this->branch,
$this->onto); $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,
"Automatic merge by 'arc land'"); pht("Automatic merge by 'arc land'"));
if ($err) { if ($err) {
throw new ArcanistUsageException( throw new ArcanistUsageException(pht(
"'git merge {$this->onto}' failed. ". "'git merge %s' failed. ".
"To continue: resolve the conflicts, commit the changes, then run ". "To continue: resolve the conflicts, commit the changes, then run ".
"'arc land' again. To abort: run 'git merge --abort'."); "'arc land' again. To abort: run 'git merge --abort'.",
$this->onto));
} }
} }
} else if ($this->isHg) { } else if ($this->isHg) {
@ -662,11 +697,13 @@ EOTEXT
$repository_api->execManualLocal( $repository_api->execManualLocal(
'rebase --abort'); 'rebase --abort');
$this->restoreBranch(); $this->restoreBranch();
throw new ArcanistUsageException( throw new ArcanistUsageException(pht(
"'hg rebase {$this->onto}' failed and the rebase was aborted. ". "'hg rebase %s' failed and the rebase was aborted. ".
"This is most likely due to conflicts. Manually rebase ". "This is most likely due to conflicts. Manually rebase %s onto ".
"{$this->branch} onto {$this->onto}, resolve the conflicts, ". "%s, resolve the conflicts, then run 'arc land' again.",
"then run 'arc land' again."); $this->onto,
$this->branch,
$this->onto));
} }
} }
} }
@ -816,12 +853,16 @@ EOTEXT
$alt_count = count($alt_branches); $alt_count = count($alt_branches);
if ($alt_count > 0) { if ($alt_count > 0) {
$input = phutil_console_prompt( $input = phutil_console_prompt(pht(
ucfirst($this->branchType)." '{$this->branch}' has {$alt_count} ". "%s '%s' has %s %s(s) forking off of it that would be deleted ".
"{$this->branchType}(s) forking off of it that would be deleted ".
"during a squash. Would you like to keep a non-squashed copy, rebase ". "during a squash. Would you like to keep a non-squashed copy, rebase ".
"them on top of '{$this->branch}', or abort and deal with them ". "them on top of '%s', or abort and deal with them yourself? ".
"yourself? (k)eep, (r)ebase, (a)bort:"); "(k)eep, (r)ebase, (a)bort:",
ucfirst($this->branchType),
$this->branch,
$alt_count,
$this->branchType,
$this->branch));
if ($input == 'k' || $input == 'keep') { if ($input == 'k' || $input == 'keep') {
$this->keepBranch = true; $this->keepBranch = true;
@ -834,11 +875,17 @@ EOTEXT
} }
} 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 "\nRemove the {$this->branchType}s starting at these revisions ". echo
"and run arc land again:\n{$branch_string}\n\n"; "\n",
pht("Remove the %s starting at these revisions and ".
"run arc land again:\n%s",
$this->branchType.'s',
$branch_string),
"\n\n";
throw new ArcanistUserAbortException(); throw new ArcanistUserAbortException();
} else { } else {
throw new ArcanistUsageException("Invalid choice. Aborting arc land."); throw new ArcanistUsageException(
pht("Invalid choice. Aborting arc land."));
} }
} }
} }
@ -857,10 +904,10 @@ EOTEXT
$this->branch); $this->branch);
if ($err) { if ($err) {
throw new ArcanistUsageException( throw new ArcanistUsageException(pht(
"'git merge' failed. Your working copy has been left in a partially ". "'git merge' 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 'git merge --abort'; or follow ".
"the instructions to complete the merge."); "the instructions to complete the merge."));
} }
} else if ($this->isHg) { } else if ($this->isHg) {
// HG arc land currently doesn't support --merge. // HG arc land currently doesn't support --merge.
@ -870,8 +917,8 @@ EOTEXT
// 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( throw new ArcanistUsageException(pht(
"--merge is not currently supported for hg repos."); "--merge is not currently supported for hg repos."));
} }
} }
@ -904,11 +951,11 @@ EOTEXT
} }
if ($this->getArgument('hold')) { if ($this->getArgument('hold')) {
echo phutil_console_format( echo phutil_console_format(pht(
"Holding change in **%s**: it has NOT been pushed yet.\n", "Holding change in **%s**: it has NOT been pushed yet.",
$this->onto); $this->onto). "\n");
} else { } else {
echo "Pushing change...\n\n"; echo pht('Pushing change...'), "\n\n";
chdir($repository_api->getPath()); chdir($repository_api->getPath());
@ -938,14 +985,17 @@ EOTEXT
} }
if ($err) { if ($err) {
echo phutil_console_format("<bg:red>** PUSH FAILED! **</bg>\n"); $failed_str = pht('PUSH FAILED!');
echo phutil_console_format("<bg:red>** %s **</bg>\n", $failed_str);
$this->executeCleanupAfterFailedPush(); $this->executeCleanupAfterFailedPush();
if ($this->isGit) { if ($this->isGit) {
throw new ArcanistUsageException( throw new ArcanistUsageException(pht(
"'{$cmd}' failed! Fix the error and run 'arc land' again."); "'%s' failed! Fix the error and run 'arc land' again.",
$cmd));
} }
throw new ArcanistUsageException( throw new ArcanistUsageException(pht(
"'{$cmd}' failed! Fix the error and push this change manually."); "'%s' failed! Fix the error and push this change manually.",
$cmd));
} }
$mark_workflow = $this->buildChildWorkflow( $mark_workflow = $this->buildChildWorkflow(
@ -977,7 +1027,7 @@ EOTEXT
private function cleanupBranch() { private function cleanupBranch() {
$repository_api = $this->getRepositoryAPI(); $repository_api = $this->getRepositoryAPI();
echo "Cleaning up feature {$this->branchType}...\n"; echo pht('Cleaning up feature %s...', $this->branchType), "\n";
if ($this->isGit) { if ($this->isGit) {
list($ref) = $repository_api->execxLocal( list($ref) = $repository_api->execxLocal(
'rev-parse --verify %s', 'rev-parse --verify %s',
@ -987,7 +1037,7 @@ EOTEXT
'git checkout -b %s %s', 'git checkout -b %s %s',
$this->branch, $this->branch,
$ref); $ref);
echo "(Use `{$recovery_command}` if you want it back.)\n"; echo pht('(Use `%s` if you want it back.)', $recovery_command), "\n";
$repository_api->execxLocal( $repository_api->execxLocal(
'branch -D %s', 'branch -D %s',
$this->branch); $this->branch);
@ -1022,7 +1072,8 @@ EOTEXT
$this->branch); $this->branch);
if ($err) { if ($err) {
echo "No remote feature {$this->branchType} to clean up.\n"; echo pht('No remote feature %s to clean up.',
$this->branchType), "\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
@ -1030,7 +1081,7 @@ EOTEXT
// //
// git push <remote> :<branch> // git push <remote> :<branch>
echo "Cleaning up remote feature branch...\n"; echo pht('Cleaning up remote feature %s...', $this->branchType), "\n";
$repository_api->execxLocal( $repository_api->execxLocal(
'push %s :%s', 'push %s :%s',
$this->remote, $this->remote,