mirror of
https://we.phorge.it/source/arcanist.git
synced 2024-11-21 22:32:41 +01:00
Improve "arc land" behavior in the presence of merge conflicts and change sequences
Summary: Ref T13546. When we encounter a merge conflict, suggest "--incremental" if it's likely to help. When merging multiple changes, rebase ranges before merging them. This reduces conflicts when landing sequences of changes. Test Plan: Ran "arc land" to land multiple changes. Hit better merge conflict messaging, then survived merge conflicts entirely. Maniphest Tasks: T13546 Differential Revision: https://secure.phabricator.com/D21339
This commit is contained in:
parent
b003cf9310
commit
7ddaed9aba
2 changed files with 79 additions and 21 deletions
|
@ -38,7 +38,7 @@ final class ArcanistGitLandEngine
|
||||||
|
|
||||||
$log->writeStatus(
|
$log->writeStatus(
|
||||||
pht('CLEANUP'),
|
pht('CLEANUP'),
|
||||||
pht('Destroying branch "%s". To recover, run:', $branch_name));
|
pht('Cleaning up branch "%s". To recover, run:', $branch_name));
|
||||||
|
|
||||||
echo tsprintf(
|
echo tsprintf(
|
||||||
"\n **$** %s\n\n",
|
"\n **$** %s\n\n",
|
||||||
|
@ -238,11 +238,13 @@ final class ArcanistGitLandEngine
|
||||||
$into_commit = $api->writeRawCommit($empty_commit);
|
$into_commit = $api->writeRawCommit($empty_commit);
|
||||||
}
|
}
|
||||||
|
|
||||||
$api->execxLocal('checkout %s --', $into_commit);
|
|
||||||
|
|
||||||
$commits = $set->getCommits();
|
$commits = $set->getCommits();
|
||||||
|
|
||||||
|
$min_commit = head($commits);
|
||||||
|
$min_hash = $min_commit->getHash();
|
||||||
|
|
||||||
$max_commit = last($commits);
|
$max_commit = last($commits);
|
||||||
$source_commit = $max_commit->getHash();
|
$max_hash = $max_commit->getHash();
|
||||||
|
|
||||||
// NOTE: See T11435 for some history. See PHI1727 for a case where a user
|
// NOTE: See T11435 for some history. See PHI1727 for a case where a user
|
||||||
// modified their working copy while running "arc land". This attempts to
|
// modified their working copy while running "arc land". This attempts to
|
||||||
|
@ -252,7 +254,7 @@ final class ArcanistGitLandEngine
|
||||||
list($changes) = $api->execxLocal(
|
list($changes) = $api->execxLocal(
|
||||||
'diff --no-ext-diff %s..%s --',
|
'diff --no-ext-diff %s..%s --',
|
||||||
$into_commit,
|
$into_commit,
|
||||||
$source_commit);
|
$max_hash);
|
||||||
$changes = trim($changes);
|
$changes = trim($changes);
|
||||||
if (!strlen($changes)) {
|
if (!strlen($changes)) {
|
||||||
|
|
||||||
|
@ -263,7 +265,7 @@ final class ArcanistGitLandEngine
|
||||||
pht(
|
pht(
|
||||||
'Merging local "%s" into "%s" produces an empty diff. '.
|
'Merging local "%s" into "%s" produces an empty diff. '.
|
||||||
'This usually means these changes have already landed.',
|
'This usually means these changes have already landed.',
|
||||||
$this->getDisplayHash($source_commit),
|
$this->getDisplayHash($max_hash),
|
||||||
$this->getDisplayHash($into_commit)));
|
$this->getDisplayHash($into_commit)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -271,7 +273,7 @@ final class ArcanistGitLandEngine
|
||||||
pht('MERGING'),
|
pht('MERGING'),
|
||||||
pht(
|
pht(
|
||||||
'%s %s',
|
'%s %s',
|
||||||
$this->getDisplayHash($source_commit),
|
$this->getDisplayHash($max_hash),
|
||||||
$max_commit->getDisplaySummary()));
|
$max_commit->getDisplaySummary()));
|
||||||
|
|
||||||
$argv = array();
|
$argv = array();
|
||||||
|
@ -294,25 +296,44 @@ final class ArcanistGitLandEngine
|
||||||
}
|
}
|
||||||
|
|
||||||
$argv[] = '--';
|
$argv[] = '--';
|
||||||
$argv[] = $source_commit;
|
|
||||||
|
|
||||||
|
$is_rebasing = false;
|
||||||
|
$is_merging = false;
|
||||||
try {
|
try {
|
||||||
|
if ($this->isSquashStrategy() && !$is_empty) {
|
||||||
|
// If we're performing a squash merge, we're going to rebase the
|
||||||
|
// commit range first. We only want to merge the specific commits
|
||||||
|
// in the range, and merging too much can create conflicts.
|
||||||
|
|
||||||
|
$api->execxLocal('checkout %s --', $max_hash);
|
||||||
|
|
||||||
|
$is_rebasing = true;
|
||||||
|
$api->execxLocal(
|
||||||
|
'rebase --onto %s -- %s',
|
||||||
|
$into_commit,
|
||||||
|
$min_hash.'^');
|
||||||
|
$is_rebasing = false;
|
||||||
|
|
||||||
|
$merge_hash = $api->getCanonicalRevisionName('HEAD');
|
||||||
|
} else {
|
||||||
|
$merge_hash = $max_hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
$api->execxLocal('checkout %s --', $into_commit);
|
||||||
|
|
||||||
|
$argv[] = $merge_hash;
|
||||||
|
|
||||||
|
$is_merging = true;
|
||||||
$api->execxLocal('merge %Ls', $argv);
|
$api->execxLocal('merge %Ls', $argv);
|
||||||
|
$is_merging = false;
|
||||||
} catch (CommandException $ex) {
|
} catch (CommandException $ex) {
|
||||||
|
|
||||||
// TODO: If we previously succeeded with at least one merge, we could
|
|
||||||
// provide a hint that "--incremental" can do some of the work.
|
|
||||||
|
|
||||||
$api->execManualLocal('merge --abort');
|
|
||||||
$api->execManualLocal('reset --hard HEAD --');
|
|
||||||
|
|
||||||
$direct_symbols = $max_commit->getDirectSymbols();
|
$direct_symbols = $max_commit->getDirectSymbols();
|
||||||
$indirect_symbols = $max_commit->getIndirectSymbols();
|
$indirect_symbols = $max_commit->getIndirectSymbols();
|
||||||
if ($direct_symbols) {
|
if ($direct_symbols) {
|
||||||
$message = pht(
|
$message = pht(
|
||||||
'Local commit "%s" (%s) does not merge cleanly into "%s". '.
|
'Local commit "%s" (%s) does not merge cleanly into "%s". '.
|
||||||
'Merge or rebase local changes so they can merge cleanly.',
|
'Merge or rebase local changes so they can merge cleanly.',
|
||||||
$this->getDisplayHash($source_commit),
|
$this->getDisplayHash($max_hash),
|
||||||
$this->getDisplaySymbols($direct_symbols),
|
$this->getDisplaySymbols($direct_symbols),
|
||||||
$this->getDisplayHash($into_commit));
|
$this->getDisplayHash($into_commit));
|
||||||
} else if ($indirect_symbols) {
|
} else if ($indirect_symbols) {
|
||||||
|
@ -320,22 +341,47 @@ final class ArcanistGitLandEngine
|
||||||
'Local commit "%s" (reachable from: %s) does not merge cleanly '.
|
'Local commit "%s" (reachable from: %s) does not merge cleanly '.
|
||||||
'into "%s". Merge or rebase local changes so they can merge '.
|
'into "%s". Merge or rebase local changes so they can merge '.
|
||||||
'cleanly.',
|
'cleanly.',
|
||||||
$this->getDisplayHash($source_commit),
|
$this->getDisplayHash($max_hash),
|
||||||
$this->getDisplaySymbols($indirect_symbols),
|
$this->getDisplaySymbols($indirect_symbols),
|
||||||
$this->getDisplayHash($into_commit));
|
$this->getDisplayHash($into_commit));
|
||||||
} else {
|
} else {
|
||||||
$message = pht(
|
$message = pht(
|
||||||
'Local commit "%s" does not merge cleanly into "%s". Merge or '.
|
'Local commit "%s" does not merge cleanly into "%s". Merge or '.
|
||||||
'rebase local changes so they can merge cleanly.',
|
'rebase local changes so they can merge cleanly.',
|
||||||
$this->getDisplayHash($source_commit),
|
$this->getDisplayHash($max_hash),
|
||||||
$this->getDisplayHash($into_commit));
|
$this->getDisplayHash($into_commit));
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new PhutilArgumentUsageException($message);
|
echo tsprintf(
|
||||||
|
"\n%!\n%W\n\n",
|
||||||
|
pht('MERGE CONFLICT'),
|
||||||
|
$message);
|
||||||
|
|
||||||
|
if ($this->getHasUnpushedChanges()) {
|
||||||
|
echo tsprintf(
|
||||||
|
"%?\n\n",
|
||||||
|
pht(
|
||||||
|
'Use "--incremental" to merge and push changes one by one.'));
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($is_rebasing) {
|
||||||
|
$api->execManualLocal('rebase --abort');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($is_merging) {
|
||||||
|
$api->execManualLocal('merge --abort');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($is_merging || $is_rebasing) {
|
||||||
|
$api->execManualLocal('reset --hard HEAD --');
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new PhutilArgumentUsageException(
|
||||||
|
pht('Encountered a merge conflict.'));
|
||||||
}
|
}
|
||||||
|
|
||||||
list($original_author, $original_date) = $this->getAuthorAndDate(
|
list($original_author, $original_date) = $this->getAuthorAndDate(
|
||||||
$source_commit);
|
$max_hash);
|
||||||
|
|
||||||
$revision_ref = $set->getRevisionRef();
|
$revision_ref = $set->getRevisionRef();
|
||||||
$commit_message = $revision_ref->getCommitMessage();
|
$commit_message = $revision_ref->getCommitMessage();
|
||||||
|
@ -362,7 +408,7 @@ final class ArcanistGitLandEngine
|
||||||
if ($this->isSquashStrategy()) {
|
if ($this->isSquashStrategy()) {
|
||||||
$raw_commit->setParents(array());
|
$raw_commit->setParents(array());
|
||||||
} else {
|
} else {
|
||||||
$raw_commit->setParents(array($source_commit));
|
$raw_commit->setParents(array($merge_hash));
|
||||||
}
|
}
|
||||||
$new_cursor = $api->writeRawCommit($raw_commit);
|
$new_cursor = $api->writeRawCommit($raw_commit);
|
||||||
|
|
||||||
|
|
|
@ -28,6 +28,7 @@ abstract class ArcanistLandEngine
|
||||||
private $intoLocal;
|
private $intoLocal;
|
||||||
|
|
||||||
private $localState;
|
private $localState;
|
||||||
|
private $hasUnpushedChanges;
|
||||||
|
|
||||||
final public function setOntoRemote($onto_remote) {
|
final public function setOntoRemote($onto_remote) {
|
||||||
$this->ontoRemote = $onto_remote;
|
$this->ontoRemote = $onto_remote;
|
||||||
|
@ -228,6 +229,15 @@ abstract class ArcanistLandEngine
|
||||||
return $this->localState;
|
return $this->localState;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function setHasUnpushedChanges($unpushed) {
|
||||||
|
$this->hasUnpushedChanges = $unpushed;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
final protected function getHasUnpushedChanges() {
|
||||||
|
return $this->hasUnpushedChanges;
|
||||||
|
}
|
||||||
|
|
||||||
final protected function getOntoConfigurationKey() {
|
final protected function getOntoConfigurationKey() {
|
||||||
return 'arc.land.onto';
|
return 'arc.land.onto';
|
||||||
}
|
}
|
||||||
|
@ -1228,6 +1238,7 @@ abstract class ArcanistLandEngine
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
$into_commit = $this->executeMerge($set, $into_commit);
|
$into_commit = $this->executeMerge($set, $into_commit);
|
||||||
|
$this->setHasUnpushedChanges(true);
|
||||||
|
|
||||||
if ($is_hold) {
|
if ($is_hold) {
|
||||||
$should_push = false;
|
$should_push = false;
|
||||||
|
@ -1241,6 +1252,7 @@ abstract class ArcanistLandEngine
|
||||||
if ($should_push) {
|
if ($should_push) {
|
||||||
try {
|
try {
|
||||||
$this->pushChange($into_commit);
|
$this->pushChange($into_commit);
|
||||||
|
$this->setHasUnpushedChanges(false);
|
||||||
} catch (Exception $ex) {
|
} catch (Exception $ex) {
|
||||||
|
|
||||||
// TODO: If the push fails, fetch and retry if the remote ref
|
// TODO: If the push fails, fetch and retry if the remote ref
|
||||||
|
|
Loading…
Reference in a new issue