Changeset View
Changeset View
Standalone View
Standalone View
src/land/engine/ArcanistGitLandEngine.php
Show First 20 Lines • Show All 220 Lines • ▼ Show 20 Lines | $log->writeStatus( | ||||
$target->getRef(), | $target->getRef(), | ||||
$target->getRemote())); | $target->getRemote())); | ||||
$this->fetchLandTarget($target, $ignore_failure = false); | $this->fetchLandTarget($target, $ignore_failure = false); | ||||
return $this->getLandTargetLocalCommit($target); | return $this->getLandTargetLocalCommit($target); | ||||
} | } | ||||
private function updateWorkingCopy($into_commit) { | |||||
$api = $this->getRepositoryAPI(); | |||||
if ($into_commit === null) { | |||||
throw new Exception('TODO: Author a new empty state.'); | |||||
} else { | |||||
$api->execxLocal('checkout %s --', $into_commit); | |||||
} | |||||
} | |||||
protected function executeMerge(ArcanistLandCommitSet $set, $into_commit) { | protected function executeMerge(ArcanistLandCommitSet $set, $into_commit) { | ||||
$api = $this->getRepositoryAPI(); | $api = $this->getRepositoryAPI(); | ||||
$log = $this->getLogEngine(); | $log = $this->getLogEngine(); | ||||
$this->updateWorkingCopy($into_commit); | $is_empty = ($into_commit === null); | ||||
if ($is_empty) { | |||||
$empty_commit = ArcanistGitRawCommit::newEmptyCommit(); | |||||
$into_commit = $api->writeRawCommit($empty_commit); | |||||
} | |||||
$api->execxLocal('checkout %s --', $into_commit); | |||||
$commits = $set->getCommits(); | $commits = $set->getCommits(); | ||||
$max_commit = last($commits); | $max_commit = last($commits); | ||||
$source_commit = $max_commit->getHash(); | $source_commit = $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 HEAD..%s --', | 'diff --no-ext-diff %s..%s --', | ||||
$into_commit, | |||||
$source_commit); | $source_commit); | ||||
$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($source_commit), | ||||
$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($source_commit), | ||||
$max_commit->getDisplaySummary())); | $max_commit->getDisplaySummary())); | ||||
try { | $argv = array(); | ||||
$argv[] = '--no-stat'; | |||||
$argv[] = '--no-commit'; | |||||
// 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. | |||||
if ($is_empty) { | |||||
$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'; | |||||
$api->execxLocal( | $argv[] = '--squash'; | ||||
'merge --no-stat --no-commit --ff --squash -- %s', | |||||
$source_commit); | |||||
} else { | } else { | ||||
$api->execxLocal( | $argv[] = '--no-ff'; | ||||
'merge --no-stat --no-commit --no-ff -- %s', | |||||
$source_commit); | |||||
} | } | ||||
$argv[] = '--'; | |||||
$argv[] = $source_commit; | |||||
try { | |||||
$api->execxLocal('merge %Ls', $argv); | |||||
} catch (CommandException $ex) { | } catch (CommandException $ex) { | ||||
// TODO: If we previously succeeded with at least one merge, we could | // TODO: If we previously succeeded with at least one merge, we could | ||||
// provide a hint that "--incremental" can do some of the work. | // provide a hint that "--incremental" can do some of the work. | ||||
$api->execManualLocal('merge --abort'); | $api->execManualLocal('merge --abort'); | ||||
$api->execManualLocal('reset --hard HEAD --'); | $api->execManualLocal('reset --hard HEAD --'); | ||||
Show All 36 Lines | $future = $api->execFutureLocal( | ||||
$original_author, | $original_author, | ||||
$original_date); | $original_date); | ||||
$future->write($commit_message); | $future->write($commit_message); | ||||
$future->resolvex(); | $future->resolvex(); | ||||
list($stdout) = $api->execxLocal('rev-parse --verify %s', 'HEAD'); | list($stdout) = $api->execxLocal('rev-parse --verify %s', 'HEAD'); | ||||
$new_cursor = trim($stdout); | $new_cursor = trim($stdout); | ||||
if ($into_commit === null) { | if ($is_empty) { | ||||
// See T12876. If we're landing into the empty state, we just did a fake | |||||
// merge on top of an empty commit. We're now on a commit with all of the | |||||
// right details except that it has an extra empty commit as a parent. | |||||
// Create a new commit which is the same as the current HEAD, except that | |||||
// it doesn't have the extra parent. | |||||
$raw_commit = $api->readRawCommit($new_cursor); | |||||
if ($this->isSquashStrategy()) { | if ($this->isSquashStrategy()) { | ||||
throw new Exception( | $raw_commit->setParents(array()); | ||||
pht('TODO: Rewrite HEAD to have no parents.')); | |||||
} else { | } else { | ||||
throw new Exception( | $raw_commit->setParents(array($source_commit)); | ||||
pht('TODO: Rewrite HEAD to have only source as a parent.')); | |||||
} | } | ||||
$new_cursor = $api->writeRawCommit($raw_commit); | |||||
$api->execxLocal('checkout %s --', $new_cursor); | |||||
} | } | ||||
return $new_cursor; | return $new_cursor; | ||||
} | } | ||||
protected function pushChange($into_commit) { | protected function pushChange($into_commit) { | ||||
$api = $this->getRepositoryAPI(); | $api = $this->getRepositoryAPI(); | ||||
$log = $this->getLogEngine(); | $log = $this->getLogEngine(); | ||||
▲ Show 20 Lines • Show All 356 Lines • ▼ Show 20 Lines | private function getAuthorAndDate($commit) { | ||||
list($date, $author, $email) = explode("\n", $info, 3); | list($date, $author, $email) = explode("\n", $info, 3); | ||||
return array( | return array( | ||||
"$author <{$email}>", | "$author <{$email}>", | ||||
$date, | $date, | ||||
); | ); | ||||
} | } | ||||
private function didHoldChanges() { | protected function didHoldChanges( | ||||
ArcanistRepositoryLocalState $state) { | |||||
$log = $this->getLogEngine(); | $log = $this->getLogEngine(); | ||||
// TODO: This probably needs updates. | |||||
// TODO: We should refuse "--hold" if we stash. | |||||
if ($this->getIsGitPerforce()) { | if ($this->getIsGitPerforce()) { | ||||
$this->writeInfo( | $this->writeInfo( | ||||
pht('HOLD'), | pht('HOLD'), | ||||
pht( | pht( | ||||
'Holding change locally, it has not been submitted.')); | 'Holding change locally, it has not been submitted.')); | ||||
$push_command = csprintf( | $push_command = csprintf( | ||||
'$ git p4 submit -M --commit %R --', | '$ git p4 submit -M --commit %R --', | ||||
$this->mergedRef); | $this->mergedRef); | ||||
} else { | } else { | ||||
$log->writeStatus( | $log->writeStatus( | ||||
pht('HOLD'), | pht('HOLD'), | ||||
pht( | pht( | ||||
'Holding change locally, it has not been pushed.')); | 'Holding change locally, it has not been pushed.')); | ||||
$push_command = csprintf( | $push_command = 'TODO: ...'; | ||||
'$ git push -- %R %R:%R', | // csprintf( | ||||
$this->getTargetRemote(), | // '$ git push -- %R %R:%R', | ||||
$this->mergedRef, | // $this->getOntoRemote(), | ||||
$this->getTargetOnto()); | // $this->mergedRef, | ||||
// $this->getOnto()); | |||||
} | } | ||||
$restore_command = csprintf( | $restore_command = 'TODO: ...'; | ||||
'$ git checkout %R --', | |||||
$this->localRef); | |||||
echo tsprintf( | echo tsprintf( | ||||
"\n%s\n\n". | "\n%s\n\n". | ||||
"%s\n\n". | "%s\n\n". | ||||
" **%s**\n\n". | " **%s**\n\n". | ||||
"%s\n\n". | "%s\n\n". | ||||
" **%s**\n\n". | " **%s**\n\n". | ||||
"%s\n", | "%s\n", | ||||
▲ Show 20 Lines • Show All 651 Lines • Show Last 20 Lines |