diff --git a/src/land/engine/ArcanistLandEngine.php b/src/land/engine/ArcanistLandEngine.php --- a/src/land/engine/ArcanistLandEngine.php +++ b/src/land/engine/ArcanistLandEngine.php @@ -1257,53 +1257,55 @@ $should_push = $is_last; } - if ($should_push) { - try { - $this->pushChange($into_commit); - $this->setHasUnpushedChanges(false); - } catch (ArcanistLandPushFailureException $ex) { - - // TODO: If the push fails, fetch and retry if the remote ref - // has moved ahead of us. - - if ($this->getIntoLocal()) { - $can_retry = false; - } else if ($this->getIntoEmpty()) { - $can_retry = false; - } else if ($this->getIntoRemote() !== $this->getOntoRemote()) { - $can_retry = false; - } else { - $can_retry = false; - } - - if ($can_retry) { - // New commit state here - $into_commit = '..'; - continue; - } - - throw new PhutilArgumentUsageException( - $ex->getMessage()); + if ($should_push === false) { + break; + } + + try { + $this->pushChange($into_commit); + $this->setHasUnpushedChanges(false); + } catch (ArcanistLandPushFailureException $ex) { + + // TODO: If the push fails, fetch and retry if the remote ref + // has moved ahead of us. + + if ($this->getIntoLocal()) { + $can_retry = false; + } else if ($this->getIntoEmpty()) { + $can_retry = false; + } else if ($this->getIntoRemote() !== $this->getOntoRemote()) { + $can_retry = false; + } else { + $can_retry = false; + } + + if ($can_retry) { + // New commit state here + $into_commit = '..'; + continue; } - if ($need_cascade) { + throw new PhutilArgumentUsageException( + $ex->getMessage()); + } - // NOTE: We cascade each set we've pushed, but we're going to - // cascade them from most recent to least recent. This way, - // branches which descend from more recent changes only cascade - // once, directly in to the correct state. + if ($need_cascade) { - $need_cascade = array_reverse($need_cascade); - foreach ($need_cascade as $cascade_set) { - $this->cascadeState($set, $into_commit); - } - $need_cascade = array(); - } + // NOTE: We cascade each set we've pushed, but we're going to + // cascade them from most recent to least recent. This way, + // branches which descend from more recent changes only cascade + // once, directly in to the correct state. - if (!$is_keep) { - $this->pruneBranches($need_prune); - $need_prune = array(); + $need_cascade = array_reverse($need_cascade); + foreach ($need_cascade as $cascade_set) { + $this->cascadeState($cascade_set, $into_commit); } + $need_cascade = array(); + } + + if (!$is_keep) { + $this->pruneBranches($need_prune); + $need_prune = array(); } break; diff --git a/src/land/engine/ArcanistMercurialLandEngine.php b/src/land/engine/ArcanistMercurialLandEngine.php --- a/src/land/engine/ArcanistMercurialLandEngine.php +++ b/src/land/engine/ArcanistMercurialLandEngine.php @@ -6,6 +6,7 @@ private $ontoBranchMarker; private $ontoMarkers; + private $rebasedCommitMap = array(); private $rebasedActiveCommit; protected function getDefaultSymbols() { @@ -771,8 +772,12 @@ protected function executeMerge(ArcanistLandCommitSet $set, $into_commit) { $api = $this->getRepositoryAPI(); + $log = $this->getLogEngine(); if ($this->getStrategy() !== 'squash') { + $log->writeError( + pht('TODO'), + pht('Merge strategy is not yet supported on Mercurial repositories.')); throw new Exception(pht('TODO: Support merge strategies')); } @@ -783,10 +788,7 @@ // we continue. $bookmark_refs = $api->newMarkerRefQuery() - ->withMarkerTypes( - array( - ArcanistMarkerRef::TYPE_BOOKMARK, - )) + ->withMarkerTypes(array(ArcanistMarkerRef::TYPE_BOOKMARK)) ->execute(); // TODO: Add a Mercurial version check requiring 2.1.1 or newer. @@ -842,6 +844,14 @@ $future->resolvex(); } catch (CommandException $ex) { + $log->writeError( + pht('REBASE CONFLICT'), + pht( + 'Commits for %s do not rebase cleanly onto %s. Manually rebase and '. + 'resolve conflicts before landing again.', + $revision_ref->getRefDisplayName(), + $api->getDisplayHash($into_commit))); + // Aborting the rebase should restore the same state prior to running the // rebase command. $api->execManualLocalWithExtension( @@ -875,6 +885,10 @@ list($stdout) = $api->execxLocal('log --rev tip --template %s', '{node}'); $new_cursor = trim($stdout); + foreach ($set->getCommits() as $commit) { + $this->rebasedCommitMap[$commit->getHash()] = $new_cursor; + } + // If any of the commits that were rebased was the active commit before the // workflow started, track the new commit so it can be used as the working // directory after the land has succeeded. @@ -1053,6 +1067,12 @@ $old_commit = last($set->getCommits())->getHash(); $new_commit = $into_commit; + // Rebase the child commits onto the commit which $old_commit was rebased + // into. The $into_commit will be the last set's final rebased commit but + // this set may be an intermediate set that was landed. + if (isset($this->rebasedCommitMap[$old_commit])) { + $new_commit = $this->rebasedCommitMap[$old_commit]; + } list($output) = $api->execxLocal( 'log --rev %s --template %s', @@ -1065,8 +1085,11 @@ continue; } - // TODO: If the only heads which are descendants of this child will - // be deleted, we can skip this rebase? + // If this child was rebased as part of landing then it shouldn't be + // rebased now. Doing so will result in rebase conflicts. + if (isset($this->rebasedCommitMap[$child_hash])) { + continue; + } try { $api->execxLocalWithExtension( @@ -1075,6 +1098,14 @@ $child_hash, $new_commit); } catch (CommandException $ex) { + $log->writeError( + pht('REBASE CONFLICT'), + pht( + 'Failed to rebase %s cleanly onto %s. Manually rebase and '. + 'resolve conflicts.', + $api->getDisplayHash($child_hash), + $api->getDisplayHash($new_commit))); + // Aborting the rebase should restore the same state prior to running // the rebase command. $api->execManualLocalWithExtension(