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:
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);
|
$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;
|
||||||
|
|
|
@ -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.'));
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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';
|
||||||
|
|
Loading…
Reference in a new issue