1
0
Fork 0
mirror of https://we.phorge.it/source/arcanist.git synced 2024-11-28 09:42:40 +01:00

Make Mercurial use "hg shelve" and "hg unshelve" in dirty working copies in "arc land"

Summary: Ref T13546. Implement the equivalents of "git stash" in Mercurial.

Test Plan: Dirtied a working copy in Mercurial, ran "arc land", saw dirty changes survive the process.

Maniphest Tasks: T13546

Differential Revision: https://secure.phabricator.com/D21329
This commit is contained in:
epriestley 2020-06-07 12:51:47 -07:00
parent 4d61c00531
commit 0bf4da60f6
6 changed files with 131 additions and 28 deletions

View file

@ -1349,7 +1349,7 @@ final class ArcanistGitLandEngine
} }
$commits = phutil_split_lines($commits, false); $commits = phutil_split_lines($commits, false);
$is_first = false; $is_first = true;
foreach ($commits as $line) { foreach ($commits as $line) {
if (!strlen($line)) { if (!strlen($line)) {
continue; continue;

View file

@ -406,6 +406,7 @@ final class ArcanistMercurialLandEngine
} }
$commits = phutil_split_lines($commits, false); $commits = phutil_split_lines($commits, false);
$is_first = true;
foreach ($commits as $line) { foreach ($commits as $line) {
if (!strlen($line)) { if (!strlen($line)) {
continue; continue;
@ -438,7 +439,12 @@ final class ArcanistMercurialLandEngine
} }
$commit = $commit_map[$hash]; $commit = $commit_map[$hash];
$commit->addSymbol($symbol); if ($is_first) {
$commit->addDirectSymbol($symbol);
$is_first = false;
}
$commit->addIndirectSymbol($symbol);
} }
} }
@ -607,14 +613,12 @@ final class ArcanistMercurialLandEngine
$message = pht( $message = pht(
'Holding changes locally, they have not been pushed.'); 'Holding changes locally, they have not been pushed.');
// TODO: This is only vaguely correct.
$push_command = csprintf( $push_command = csprintf(
'$ hg push -- %s %Ls', '$ hg push --rev %s -- %s',
// TODO: When a parameter contains only "safe" characters, we could
// relax the behavior of hgsprintf().
hgsprintf('%s', $this->getDisplayHash($into_commit)), hgsprintf('%s', $this->getDisplayHash($into_commit)),
$this->newOntoRefArguments($into_commit)); $this->getOntoRemote());
echo tsprintf( echo tsprintf(
"\n%!\n%s\n\n", "\n%!\n%s\n\n",
@ -643,7 +647,7 @@ final class ArcanistMercurialLandEngine
} }
echo tsprintf( echo tsprintf(
"%s\n". "%s\n",
pht( pht(
'Local branches and bookmarks have not been changed, and are still '. 'Local branches and bookmarks have not been changed, and are still '.
'in the same state as before.')); 'in the same state as before.'));

View file

@ -12,6 +12,9 @@ final class ArcanistMercurialAPI extends ArcanistRepositoryAPI {
private $supportsRebase; private $supportsRebase;
private $supportsPhases; private $supportsPhases;
private $featureResults = array();
private $featureFutures = array();
protected function buildLocalFuture(array $argv) { protected function buildLocalFuture(array $argv) {
$env = $this->getMercurialEnvironmentVariables(); $env = $this->getMercurialEnvironmentVariables();
@ -1167,5 +1170,57 @@ final class ArcanistMercurialAPI extends ArcanistRepositoryAPI {
->setRepositoryAPI($this); ->setRepositoryAPI($this);
} }
public function willTestMercurialFeature($feature) {
$this->executeMercurialFeatureTest($feature, false);
return $this;
}
public function getMercurialFeature($feature) {
return $this->executeMercurialFeatureTest($feature, true);
}
private function executeMercurialFeatureTest($feature, $resolve) {
if (array_key_exists($feature, $this->featureResults)) {
return $this->featureResults[$feature];
}
if (!array_key_exists($feature, $this->featureFutures)) {
$future = $this->newMercurialFeatureFuture($feature);
$future->start();
$this->featureFutures[$feature] = $future;
}
if (!$resolve) {
return;
}
$future = $this->featureFutures[$feature];
$result = $this->resolveMercurialFeatureFuture($feature, $future);
$this->featureResults[$feature] = $result;
return $result;
}
private function newMercurialFeatureFuture($feature) {
switch ($feature) {
case 'shelve':
return $this->execFutureLocal(
'--config extensions.shelve= shelve --help');
default:
throw new Exception(
pht(
'Unknown Mercurial feature "%s".',
$feature));
}
}
private function resolveMercurialFeatureFuture($feature, $future) {
// By default, assume the feature is a simple capability test and the
// capability is present if the feature resolves without an error.
list($err) = $future->resolve();
return !$err;
}
} }

View file

@ -5,7 +5,6 @@ final class ArcanistMercurialLocalState
private $localCommit; private $localCommit;
private $localRef; private $localRef;
private $localPath;
public function getLocalRef() { public function getLocalRef() {
return $this->localRef; return $this->localRef;
@ -17,6 +16,7 @@ final class ArcanistMercurialLocalState
protected function executeSaveLocalState() { protected function executeSaveLocalState() {
$api = $this->getRepositoryAPI(); $api = $this->getRepositoryAPI();
// TODO: Fix this. // TODO: Fix this.
} }
@ -36,13 +36,16 @@ final class ArcanistMercurialLocalState
} }
protected function canStashChanges() { protected function canStashChanges() {
// Depends on stash extension. $api = $this->getRepositoryAPI();
return false; return $api->getMercurialFeature('shelve');
} }
protected function getIgnoreHints() { protected function getIgnoreHints() {
// TODO: Provide this. return array(
return array(); pht(
'To configure Mercurial to ignore certain files in the working '.
'copy, add them to ".hgignore".'),
);
} }
protected function newRestoreCommandsForDisplay() { protected function newRestoreCommandsForDisplay() {
@ -51,15 +54,43 @@ final class ArcanistMercurialLocalState
} }
protected function saveStash() { protected function saveStash() {
return null; $api = $this->getRepositoryAPI();
$log = $this->getWorkflow()->getLogEngine();
$stash_ref = sprintf(
'arc-%s',
Filesystem::readRandomCharacters(12));
$api->execxLocal(
'--config extensions.shelve= shelve --unknown --name %s --',
$stash_ref);
$log->writeStatus(
pht('SHELVE'),
pht('Shelving uncommitted changes from working copy.'));
return $stash_ref;
} }
protected function restoreStash($stash_ref) { protected function restoreStash($stash_ref) {
return null; $api = $this->getRepositoryAPI();
$log = $this->getWorkflow()->getLogEngine();
$log->writeStatus(
pht('UNSHELVE'),
pht('Restoring uncommitted changes to working copy.'));
$api->execxLocal(
'--config extensions.shelve= unshelve --keep --name %s --',
$stash_ref);
} }
protected function discardStash($stash_ref) { protected function discardStash($stash_ref) {
return null; $api = $this->getRepositoryAPI();
$api->execxLocal(
'--config extensions.shelve= shelve --delete %s --',
$stash_ref);
} }
} }

View file

@ -161,9 +161,8 @@ abstract class ArcanistRepositoryLocalState
$this->shouldRestore = false; $this->shouldRestore = false;
$this->executeRestoreLocalState(); $this->executeRestoreLocalState();
if ($this->stashRef !== null) { $this->applyStash();
$this->restoreStash($this->stashRef); $this->executeDiscardLocalState();
}
return $this; return $this;
} }
@ -171,12 +170,8 @@ abstract class ArcanistRepositoryLocalState
final public function discardLocalState() { final public function discardLocalState() {
$this->shouldRestore = false; $this->shouldRestore = false;
$this->applyStash();
$this->executeDiscardLocalState(); $this->executeDiscardLocalState();
if ($this->stashRef !== null) {
$this->restoreStash($this->stashRef);
$this->discardStash($this->stashRef);
$this->stashRef = null;
}
return $this; return $this;
} }
@ -184,9 +179,9 @@ abstract class ArcanistRepositoryLocalState
final public function __destruct() { final public function __destruct() {
if ($this->shouldRestore) { if ($this->shouldRestore) {
$this->restoreLocalState(); $this->restoreLocalState();
} else {
$this->discardLocalState();
} }
$this->discardLocalState();
} }
final public function getRestoreCommandsForDisplay() { final public function getRestoreCommandsForDisplay() {
@ -209,6 +204,17 @@ abstract class ArcanistRepositoryLocalState
throw new PhutilMethodNotImplementedException(); throw new PhutilMethodNotImplementedException();
} }
private function applyStash() {
if ($this->stashRef === null) {
return;
}
$stash_ref = $this->stashRef;
$this->stashRef = null;
$this->restoreStash($stash_ref);
$this->discardStash($stash_ref);
}
abstract protected function executeSaveLocalState(); abstract protected function executeSaveLocalState();
abstract protected function executeRestoreLocalState(); abstract protected function executeRestoreLocalState();
abstract protected function executeDiscardLocalState(); abstract protected function executeDiscardLocalState();

View file

@ -22,7 +22,14 @@ function xsprintf_mercurial($userdata, &$pattern, &$pos, &$value, &$length) {
switch ($type) { switch ($type) {
case 's': case 's':
$value = "'".addcslashes($value, "'\\")."'"; // If this is symbol only has "safe" alphanumeric latin characters,
// and is at least one character long, we can let it through without
// escaping it. This tends to produce more readable commands.
if (preg_match('(^[a-zA-Z0-9]+\z)', $value)) {
$value = $value;
} else {
$value = "'".addcslashes($value, "'\\")."'";
}
break; break;
case 'R': case 'R':
$type = 's'; $type = 's';