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:
parent
4d61c00531
commit
0bf4da60f6
6 changed files with 131 additions and 28 deletions
|
@ -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;
|
||||
|
|
|
@ -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.'));
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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,10 +179,10 @@ abstract class ArcanistRepositoryLocalState
|
|||
final public function __destruct() {
|
||||
if ($this->shouldRestore) {
|
||||
$this->restoreLocalState();
|
||||
}
|
||||
|
||||
} else {
|
||||
$this->discardLocalState();
|
||||
}
|
||||
}
|
||||
|
||||
final public function getRestoreCommandsForDisplay() {
|
||||
return $this->newRestoreCommandsForDisplay();
|
||||
|
@ -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();
|
||||
|
|
|
@ -22,7 +22,14 @@ function xsprintf_mercurial($userdata, &$pattern, &$pos, &$value, &$length) {
|
|||
|
||||
switch ($type) {
|
||||
case 's':
|
||||
// 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';
|
||||
|
|
Loading…
Reference in a new issue