Changeset View
Changeset View
Standalone View
Standalone View
src/land/engine/ArcanistGitLandEngine.php
| Show All 32 Lines | protected function pruneBranches(array $sets) { | ||||
| foreach ($branch_map as $branch_name => $branch_hash) { | foreach ($branch_map as $branch_name => $branch_hash) { | ||||
| $recovery_command = csprintf( | $recovery_command = csprintf( | ||||
| 'git checkout -b %s %s', | 'git checkout -b %s %s', | ||||
| $branch_name, | $branch_name, | ||||
| $this->getDisplayHash($branch_hash)); | $this->getDisplayHash($branch_hash)); | ||||
| $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", | ||||
| $recovery_command); | $recovery_command); | ||||
| $api->execxLocal('branch -D -- %s', $branch_name); | $api->execxLocal('branch -D -- %s', $branch_name); | ||||
| $this->deletedBranches[$branch_name] = true; | $this->deletedBranches[$branch_name] = true; | ||||
| } | } | ||||
| ▲ Show 20 Lines • Show All 183 Lines • ▼ Show 20 Lines | protected function executeMerge(ArcanistLandCommitSet $set, $into_commit) { | ||||
| $is_empty = ($into_commit === null); | $is_empty = ($into_commit === null); | ||||
| if ($is_empty) { | if ($is_empty) { | ||||
| $empty_commit = ArcanistGitRawCommit::newEmptyCommit(); | $empty_commit = ArcanistGitRawCommit::newEmptyCommit(); | ||||
| $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 | ||||
| // resist incorrectly detecting simultaneous working copy modifications | // resist incorrectly detecting simultaneous working copy modifications | ||||
| // as changes. | // as changes. | ||||
| 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)) { | ||||
| // TODO: We could make a more significant effort to identify the | // TODO: We could make a more significant effort to identify the | ||||
| // human-readable symbol which led us to try to land this ref. | // human-readable symbol which led us to try to land this ref. | ||||
| throw new PhutilArgumentUsageException( | throw new PhutilArgumentUsageException( | ||||
| 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))); | ||||
| } | } | ||||
| $log->writeStatus( | $log->writeStatus( | ||||
| 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(); | ||||
| $argv[] = '--no-stat'; | $argv[] = '--no-stat'; | ||||
| $argv[] = '--no-commit'; | $argv[] = '--no-commit'; | ||||
| // When we're merging into the empty state, Git refuses to perform the | // When we're merging into the empty state, Git refuses to perform the | ||||
| // merge until we tell it explicitly that we're doing something unusual. | // merge until we tell it explicitly that we're doing something unusual. | ||||
| if ($is_empty) { | if ($is_empty) { | ||||
| $argv[] = '--allow-unrelated-histories'; | $argv[] = '--allow-unrelated-histories'; | ||||
| } | } | ||||
| if ($this->isSquashStrategy()) { | if ($this->isSquashStrategy()) { | ||||
| // NOTE: We're explicitly specifying "--ff" to override the presence | // NOTE: We're explicitly specifying "--ff" to override the presence | ||||
| // of "merge.ff" options in user configuration. | // of "merge.ff" options in user configuration. | ||||
| $argv[] = '--ff'; | $argv[] = '--ff'; | ||||
| $argv[] = '--squash'; | $argv[] = '--squash'; | ||||
| } else { | } else { | ||||
| $argv[] = '--no-ff'; | $argv[] = '--no-ff'; | ||||
| } | } | ||||
| $argv[] = '--'; | $argv[] = '--'; | ||||
| $argv[] = $source_commit; | |||||
| $is_rebasing = false; | |||||
| $is_merging = false; | |||||
| try { | try { | ||||
| $api->execxLocal('merge %Ls', $argv); | if ($this->isSquashStrategy() && !$is_empty) { | ||||
| } catch (CommandException $ex) { | // 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. | |||||
| // TODO: If we previously succeeded with at least one merge, we could | $api->execxLocal('checkout %s --', $max_hash); | ||||
| // provide a hint that "--incremental" can do some of the work. | |||||
| $api->execManualLocal('merge --abort'); | $is_rebasing = true; | ||||
| $api->execManualLocal('reset --hard HEAD --'); | $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); | |||||
| $is_merging = false; | |||||
| } catch (CommandException $ex) { | |||||
| $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) { | ||||
| $message = pht( | $message = pht( | ||||
| '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(); | ||||
| $future = $api->execFutureLocal( | $future = $api->execFutureLocal( | ||||
| 'commit --author %s --date %s -F - --', | 'commit --author %s --date %s -F - --', | ||||
| $original_author, | $original_author, | ||||
| $original_date); | $original_date); | ||||
| Show All 10 Lines | if ($is_empty) { | ||||
| // Create a new commit which is the same as the current HEAD, except that | // Create a new commit which is the same as the current HEAD, except that | ||||
| // it doesn't have the extra parent. | // it doesn't have the extra parent. | ||||
| $raw_commit = $api->readRawCommit($new_cursor); | $raw_commit = $api->readRawCommit($new_cursor); | ||||
| 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); | ||||
| $api->execxLocal('checkout %s --', $new_cursor); | $api->execxLocal('checkout %s --', $new_cursor); | ||||
| } | } | ||||
| return $new_cursor; | return $new_cursor; | ||||
| } | } | ||||
| ▲ Show 20 Lines • Show All 1,107 Lines • Show Last 20 Lines | |||||