diff --git a/src/land/ArcanistLandCommitSet.php b/src/land/ArcanistLandCommitSet.php --- a/src/land/ArcanistLandCommitSet.php +++ b/src/land/ArcanistLandCommitSet.php @@ -5,6 +5,7 @@ private $revisionRef; private $commits; + private $isPick; public function setRevisionRef(ArcanistRevisionRef $revision_ref) { $this->revisionRef = $revision_ref; @@ -49,4 +50,23 @@ return false; } + public function hasDirectSymbols() { + foreach ($this->commits as $commit) { + if ($commit->getDirectSymbols()) { + return true; + } + } + + return false; + } + + public function setIsPick($is_pick) { + $this->isPick = $is_pick; + return $this; + } + + public function getIsPick() { + return $this->isPick; + } + } diff --git a/src/land/engine/ArcanistGitLandEngine.php b/src/land/engine/ArcanistGitLandEngine.php --- a/src/land/engine/ArcanistGitLandEngine.php +++ b/src/land/engine/ArcanistGitLandEngine.php @@ -122,6 +122,7 @@ return; } + $min_commit = head($set->getCommits())->getHash(); $old_commit = last($set->getCommits())->getHash(); $new_commit = $into_commit; @@ -143,17 +144,38 @@ 'Rebasing "%s" onto landed state...', $branch_name)); + // If we used "--pick" to select this commit, we want to rebase branches + // that descend from it onto its ancestor, not onto the landed change. + + // For example, if the change sequence was "W", "X", "Y", "Z" and we + // landed "Y" onto "master" using "--pick", we want to rebase "Z" onto + // "X" (so "W" and "X", which it will often depend on, are still + // its ancestors), not onto the new "master". + + if ($set->getIsPick()) { + $rebase_target = $min_commit.'^'; + } else { + $rebase_target = $new_commit; + } + try { $api->execxLocal( 'rebase --onto %s -- %s %s', - $new_commit, + $rebase_target, $old_commit, $branch_name); } catch (CommandException $ex) { - // TODO: If we have a stashed state or are not running in incremental - // mode: abort the rebase, restore the local state, and pop the stash. - // Otherwise, drop the user out here. - throw $ex; + $api->execManualLocal('rebase --abort'); + $api->execManualLocal('reset --hard HEAD --'); + + $log->writeWarning( + pht('REBASE CONFLICT'), + pht( + 'Branch "%s" does not rebase cleanly from "%s" onto '. + '"%s", skipping.', + $branch_name, + $this->getDisplayHash($old_commit), + $this->getDisplayHash($rebase_target))); } } } 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 @@ -29,6 +29,7 @@ private $localState; private $hasUnpushedChanges; + private $pickArgument; final public function setOntoRemote($onto_remote) { $this->ontoRemote = $onto_remote; @@ -75,6 +76,15 @@ return $this->intoEmpty; } + final public function setPickArgument($pick_argument) { + $this->pickArgument = $pick_argument; + return $this; + } + + final public function getPickArgument() { + return $this->pickArgument; + } + final public function setIntoLocal($into_local) { $this->intoLocal = $into_local; return $this; @@ -1360,6 +1370,13 @@ $strategy = $this->selectMergeStrategy(); $this->setStrategy($strategy); + $is_pick = $this->getPickArgument(); + if ($is_pick && !$this->isSquashStrategy()) { + throw new PhutilArgumentUsageException( + pht( + 'You can not "--pick" changes under the "merge" strategy.')); + } + // Build the symbol ref here (which validates the format of the symbol), // but don't load the object until later on when we're sure we actually // need it, since loading it requires a relatively expensive Conduit call. @@ -1486,16 +1503,7 @@ continue; } - $symbols = null; - foreach ($set->getCommits() as $commit) { - $commit_symbols = $commit->getDirectSymbols(); - if ($commit_symbols) { - $symbols = $commit_symbols; - break; - } - } - - if ($symbols) { + if ($set->hasDirectSymbols()) { continue; } @@ -1524,6 +1532,18 @@ // set of commits, and try to confirm that the state we're about to land // is the current state in Differential. + $is_pick = $this->getPickArgument(); + if ($is_pick) { + foreach ($sets as $key => $set) { + if ($set->hasDirectSymbols()) { + $set->setIsPick(true); + continue; + } + + unset($sets[$key]); + } + } + return $sets; } diff --git a/src/workflow/ArcanistLandWorkflow.php b/src/workflow/ArcanistLandWorkflow.php --- a/src/workflow/ArcanistLandWorkflow.php +++ b/src/workflow/ArcanistLandWorkflow.php @@ -32,7 +32,9 @@ etc. When you provide a __ref__, all unpublished changes which are present in -ancestors of that __ref__ will be selected for publishing. +ancestors of that __ref__ will be selected for publishing. (With the +**--pick** flag, only the unpublished changes you directly reference will be +selected.) For example, if you provide local branch "feature3" as a __ref__ argument, that may also select the changes in "feature1" and "feature2" (if they are ancestors @@ -222,6 +224,11 @@ 'complicated changes by allowing you to make progress one '. 'step at a time.'), )), + $this->newWorkflowArgument('pick') + ->setHelp( + pht( + 'Land only the changes directly named by arguments, instead '. + 'of all reachable ancestors.')), $this->newWorkflowArgument('ref') ->setWildcard(true), ); @@ -308,6 +315,7 @@ $revision = $this->getArgument('revision'); $strategy = $this->getArgument('strategy'); + $pick = $this->getArgument('pick'); $land_engine ->setViewer($this->getViewer()) @@ -324,6 +332,7 @@ ->setIntoEmptyArgument($into_empty) ->setIntoLocalArgument($into_local) ->setIntoArgument($into) + ->setPickArgument($pick) ->setIsIncremental($is_incremental) ->setRevisionSymbol($revision); diff --git a/src/workflow/ArcanistWorkWorkflow.php b/src/workflow/ArcanistWorkWorkflow.php --- a/src/workflow/ArcanistWorkWorkflow.php +++ b/src/workflow/ArcanistWorkWorkflow.php @@ -38,7 +38,7 @@ switch to it. If it does not find one, it will attempt to create a new branch or bookmark. -When "arc work" creates a branch or bookmark, it will use "--start" as the +When "arc work" creates a branch or bookmark, it will use **--start** as the branchpoint if it is provided. Otherwise, the current working copy state will serve as the starting point. EOHELP