1
0
Fork 0
mirror of https://we.phorge.it/source/arcanist.git synced 2024-11-24 15:52: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);
$is_first = false;
$is_first = true;
foreach ($commits as $line) {
if (!strlen($line)) {
continue;

View file

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

View file

@ -12,6 +12,9 @@ final class ArcanistMercurialAPI extends ArcanistRepositoryAPI {
private $supportsRebase;
private $supportsPhases;
private $featureResults = array();
private $featureFutures = array();
protected function buildLocalFuture(array $argv) {
$env = $this->getMercurialEnvironmentVariables();
@ -1167,5 +1170,57 @@ final class ArcanistMercurialAPI extends ArcanistRepositoryAPI {
->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 $localRef;
private $localPath;
public function getLocalRef() {
return $this->localRef;
@ -17,6 +16,7 @@ final class ArcanistMercurialLocalState
protected function executeSaveLocalState() {
$api = $this->getRepositoryAPI();
// TODO: Fix this.
}
@ -36,13 +36,16 @@ final class ArcanistMercurialLocalState
}
protected function canStashChanges() {
// Depends on stash extension.
return false;
$api = $this->getRepositoryAPI();
return $api->getMercurialFeature('shelve');
}
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() {
@ -51,15 +54,43 @@ final class ArcanistMercurialLocalState
}
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) {
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) {
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->executeRestoreLocalState();
if ($this->stashRef !== null) {
$this->restoreStash($this->stashRef);
}
$this->applyStash();
$this->executeDiscardLocalState();
return $this;
}
@ -171,12 +170,8 @@ abstract class ArcanistRepositoryLocalState
final public function discardLocalState() {
$this->shouldRestore = false;
$this->applyStash();
$this->executeDiscardLocalState();
if ($this->stashRef !== null) {
$this->restoreStash($this->stashRef);
$this->discardStash($this->stashRef);
$this->stashRef = null;
}
return $this;
}
@ -184,9 +179,9 @@ abstract class ArcanistRepositoryLocalState
final public function __destruct() {
if ($this->shouldRestore) {
$this->restoreLocalState();
} else {
$this->discardLocalState();
}
$this->discardLocalState();
}
final public function getRestoreCommandsForDisplay() {
@ -209,6 +204,17 @@ abstract class ArcanistRepositoryLocalState
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 executeRestoreLocalState();
abstract protected function executeDiscardLocalState();

View file

@ -22,7 +22,14 @@ function xsprintf_mercurial($userdata, &$pattern, &$pos, &$value, &$length) {
switch ($type) {
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;
case 'R':
$type = 's';