Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F15331707
D21314.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
12 KB
Referenced Files
None
Subscribers
None
D21314.diff
View Options
diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php
--- a/src/__phutil_library_map__.php
+++ b/src/__phutil_library_map__.php
@@ -219,6 +219,7 @@
'ArcanistGitCommitMessageHardpointQuery' => 'query/ArcanistGitCommitMessageHardpointQuery.php',
'ArcanistGitCommitSymbolCommitHardpointQuery' => 'ref/commit/ArcanistGitCommitSymbolCommitHardpointQuery.php',
'ArcanistGitLandEngine' => 'land/ArcanistGitLandEngine.php',
+ 'ArcanistGitLocalState' => 'repository/state/ArcanistGitLocalState.php',
'ArcanistGitUpstreamPath' => 'repository/api/ArcanistGitUpstreamPath.php',
'ArcanistGitWorkingCopy' => 'workingcopy/ArcanistGitWorkingCopy.php',
'ArcanistGitWorkingCopyRevisionHardpointQuery' => 'query/ArcanistGitWorkingCopyRevisionHardpointQuery.php',
@@ -401,6 +402,7 @@
'ArcanistRepositoryAPI' => 'repository/api/ArcanistRepositoryAPI.php',
'ArcanistRepositoryAPIMiscTestCase' => 'repository/api/__tests__/ArcanistRepositoryAPIMiscTestCase.php',
'ArcanistRepositoryAPIStateTestCase' => 'repository/api/__tests__/ArcanistRepositoryAPIStateTestCase.php',
+ 'ArcanistRepositoryLocalState' => 'repository/state/ArcanistRepositoryLocalState.php',
'ArcanistRepositoryRef' => 'ref/ArcanistRepositoryRef.php',
'ArcanistReusedAsIteratorXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistReusedAsIteratorXHPASTLinterRule.php',
'ArcanistReusedAsIteratorXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistReusedAsIteratorXHPASTLinterRuleTestCase.php',
@@ -1227,6 +1229,7 @@
'ArcanistGitCommitMessageHardpointQuery' => 'ArcanistWorkflowGitHardpointQuery',
'ArcanistGitCommitSymbolCommitHardpointQuery' => 'ArcanistWorkflowGitHardpointQuery',
'ArcanistGitLandEngine' => 'ArcanistLandEngine',
+ 'ArcanistGitLocalState' => 'ArcanistRepositoryLocalState',
'ArcanistGitUpstreamPath' => 'Phobject',
'ArcanistGitWorkingCopy' => 'ArcanistWorkingCopy',
'ArcanistGitWorkingCopyRevisionHardpointQuery' => 'ArcanistWorkflowGitHardpointQuery',
@@ -1412,6 +1415,7 @@
'ArcanistRepositoryAPI' => 'Phobject',
'ArcanistRepositoryAPIMiscTestCase' => 'PhutilTestCase',
'ArcanistRepositoryAPIStateTestCase' => 'PhutilTestCase',
+ 'ArcanistRepositoryLocalState' => 'Phobject',
'ArcanistRepositoryRef' => 'ArcanistRef',
'ArcanistReusedAsIteratorXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
'ArcanistReusedAsIteratorXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase',
diff --git a/src/repository/state/ArcanistGitLocalState.php b/src/repository/state/ArcanistGitLocalState.php
new file mode 100644
--- /dev/null
+++ b/src/repository/state/ArcanistGitLocalState.php
@@ -0,0 +1,147 @@
+<?php
+
+final class ArcanistGitLocalState
+ extends ArcanistRepositoryLocalState {
+
+ private $localCommit;
+ private $localRef;
+ private $localPath;
+
+ public function getLocalRef() {
+ return $this->localRef;
+ }
+
+ public function getLocalPath() {
+ return $this->localPath;
+ }
+
+ protected function executeSaveLocalState() {
+ $api = $this->getRepositoryAPI();
+
+ $commit = $api->getWorkingCopyRevision();
+
+ list($ref) = $api->execxLocal('rev-parse --abbrev-ref HEAD');
+ $ref = trim($ref);
+ if ($ref === 'HEAD') {
+ $ref = null;
+ $where = pht(
+ 'Saving local state (at detached commit "%s").',
+ $this->getDisplayHash($commit));
+ } else {
+ $where = pht(
+ 'Saving local state (on ref "%s" at commit "%s").',
+ $ref,
+ $this->getDisplayHash($commit));
+ }
+
+ $this->localRef = $ref;
+ $this->localCommit = $commit;
+
+ if ($ref !== null) {
+ $this->localPath = $api->getPathToUpstream($ref);
+ }
+
+ $log = $this->getWorkflow()->getLogEngine();
+ $log->writeStatus(pht('SAVE STATE'), $where);
+ }
+
+ protected function executeRestoreLocalState() {
+ $api = $this->getRepositoryAPI();
+
+ $log = $this->getWorkflow()->getLogEngine();
+
+ $ref = $this->localRef;
+ $commit = $this->localCommit;
+
+ if ($ref !== null) {
+ $where = pht(
+ 'Restoring local state (to ref "%s" at commit "%s").',
+ $ref,
+ $this->getDisplayHash($commit));
+ } else {
+ $where = pht(
+ 'Restoring local state (to detached commit "%s").',
+ $this->getDisplayHash($commit));
+ }
+
+ $log->writeStatus(pht('LOAD STATE'), $where);
+
+ if ($ref !== null) {
+ $api->execxLocal('checkout -B %s %s --', $ref, $commit);
+
+ // TODO: We save, but do not restore, the upstream configuration of
+ // this branch.
+
+ } else {
+ $api->execxLocal('checkout %s --', $commit);
+ }
+
+ $api->execxLocal('submodule update --init --recursive');
+ }
+
+ protected function executeDiscardLocalState() {
+ // We don't have anything to clean up in Git.
+ return;
+ }
+
+ protected function canStashChanges() {
+ return true;
+ }
+
+ protected function getIgnoreHints() {
+ return array(
+ pht(
+ 'To configure Git to ignore certain files in this working copy, '.
+ 'add the file paths to "%s".',
+ '.git/info/exclude'),
+ );
+ }
+
+ protected function saveStash() {
+ $api = $this->getRepositoryAPI();
+
+ // NOTE: We'd prefer to "git stash create" here, because using "push"
+ // and "pop" means we're affecting the stash list as a side effect.
+
+ // However, under Git 2.21.1, "git stash create" exits with no output,
+ // no error, and no effect if the working copy contains only untracked
+ // files. For now, accept mutations to the stash list.
+
+ $api->execxLocal('stash push --include-untracked --');
+
+ $log = $this->getWorkflow()->getLogEngine();
+ $log->writeStatus(
+ pht('SAVE STASH'),
+ pht('Saved uncommitted changes from working copy.'));
+
+ return true;
+ }
+
+ protected function restoreStash($stash_ref) {
+ $api = $this->getRepositoryAPI();
+
+ $log = $this->getWorkflow()->getLogEngine();
+ $log->writeStatus(
+ pht('LOAD STASH'),
+ pht('Restoring uncommitted changes to working copy.'));
+
+ // NOTE: Under Git 2.21.1, "git stash apply" does not accept "--".
+ $api->execxLocal('stash apply');
+ }
+
+ protected function discardStash($stash_ref) {
+ $api = $this->getRepositoryAPI();
+
+ // NOTE: Under Git 2.21.1, "git stash drop" does not accept "--".
+ $api->execxLocal('stash drop');
+ }
+
+ private function getDisplayStashRef($stash_ref) {
+ return substr($stash_ref, 0, 12);
+ }
+
+ private function getDisplayHash($hash) {
+ return substr($hash, 0, 12);
+ }
+
+}
diff --git a/src/repository/state/ArcanistRepositoryLocalState.php b/src/repository/state/ArcanistRepositoryLocalState.php
new file mode 100644
--- /dev/null
+++ b/src/repository/state/ArcanistRepositoryLocalState.php
@@ -0,0 +1,245 @@
+<?php
+
+abstract class ArcanistRepositoryLocalState
+ extends Phobject {
+
+ private $repositoryAPI;
+ private $shouldRestore;
+ private $stashRef;
+ private $workflow;
+
+ final public function setWorkflow(ArcanistWorkflow $workflow) {
+ $this->workflow = $workflow;
+ return $this;
+ }
+
+ final public function getWorkflow() {
+ return $this->workflow;
+ }
+
+ final public function setRepositoryAPI(ArcanistRepositoryAPI $api) {
+ $this->repositoryAPI = $api;
+ return $this;
+ }
+
+ final public function getRepositoryAPI() {
+ return $this->repositoryAPI;
+ }
+
+ final public function saveLocalState() {
+ $api = $this->getRepositoryAPI();
+
+ $working_copy_display = tsprintf(
+ " %s: %s\n",
+ pht('Working Copy'),
+ $api->getPath());
+
+ $conflicts = $api->getMergeConflicts();
+ if ($conflicts) {
+ echo tsprintf(
+ "\n%!\n%W\n\n%s\n",
+ pht('MERGE CONFLICTS'),
+ pht('You have merge conflicts in this working copy.'),
+ $working_copy_display);
+
+ $lists = array();
+
+ $lists[] = $this->newDisplayFileList(
+ pht('Merge conflicts in working copy:'),
+ $conflicts);
+
+ $this->printFileLists($lists);
+
+ throw new PhutilArgumentUsageException(
+ pht(
+ 'Resolve merge conflicts before proceeding.'));
+ }
+
+ $externals = $api->getDirtyExternalChanges();
+ if ($externals) {
+ $message = pht(
+ '%s submodule(s) have uncommitted or untracked changes:',
+ new PhutilNumber(count($externals)));
+
+ $prompt = pht(
+ 'Ignore the changes to these %s submodule(s) and continue?',
+ new PhutilNumber(count($externals)));
+
+ $list = id(new PhutilConsoleList())
+ ->setWrap(false)
+ ->addItems($externals);
+
+ id(new PhutilConsoleBlock())
+ ->addParagraph($message)
+ ->addList($list)
+ ->draw();
+
+ $ok = phutil_console_confirm($prompt, $default_no = false);
+ if (!$ok) {
+ throw new ArcanistUserAbortException();
+ }
+ }
+
+ $uncommitted = $api->getUncommittedChanges();
+ $unstaged = $api->getUnstagedChanges();
+ $untracked = $api->getUntrackedChanges();
+
+ // We already dealt with externals.
+ $unstaged = array_diff($unstaged, $externals);
+
+ // We only want files which are purely uncommitted.
+ $uncommitted = array_diff($uncommitted, $unstaged);
+ $uncommitted = array_diff($uncommitted, $externals);
+
+ if ($untracked || $unstaged || $uncommitted) {
+ echo tsprintf(
+ "\n%!\n%W\n\n%s\n",
+ pht('UNCOMMITTED CHANGES'),
+ pht('You have uncommitted changes in this working copy.'),
+ $working_copy_display);
+
+ $lists = array();
+
+ $lists[] = $this->newDisplayFileList(
+ pht('Untracked changes in working copy:'),
+ $untracked);
+
+ $lists[] = $this->newDisplayFileList(
+ pht('Unstaged changes in working copy:'),
+ $unstaged);
+
+ $lists[] = $this->newDisplayFileList(
+ pht('Uncommitted changes in working copy:'),
+ $uncommitted);
+
+ $this->printFileLists($lists);
+
+ if ($untracked) {
+ $hints = $this->getIgnoreHints();
+ foreach ($hints as $hint) {
+ echo tsprintf("%?\n", $hint);
+ }
+ }
+
+ if ($this->canStashChanges()) {
+
+ $query = pht('Stash these changes and continue?');
+
+ $this->getWorkflow()
+ ->getPrompt('arc.state.stash')
+ ->setQuery($query)
+ ->execute();
+
+ $stash_ref = $this->saveStash();
+
+ if ($stash_ref === null) {
+ throw new Exception(
+ pht(
+ 'Expected a non-null return from call to "%s->saveStash()".',
+ get_class($this)));
+ }
+
+ $this->stashRef = $stash_ref;
+ } else {
+ throw new PhutilArgumentUsageException(
+ pht(
+ 'You can not continue with uncommitted changes. Commit or '.
+ 'discard them before proceeding.'));
+ }
+ }
+
+ $this->executeSaveLocalState();
+ $this->shouldRestore = true;
+
+ return $this;
+ }
+
+ final public function restoreLocalState() {
+ $this->shouldRestore = false;
+
+ $this->executeRestoreLocalState();
+ if ($this->stashRef !== null) {
+ $this->restoreStash($this->stashRef);
+ }
+
+ return $this;
+ }
+
+ final public function discardLocalState() {
+ $this->shouldRestore = false;
+
+ $this->executeDiscardLocalState();
+ if ($this->stashRef !== null) {
+ $this->restoreStash($this->stashRef);
+ $this->discardStash($this->stashRef);
+ $this->stashRef = null;
+ }
+
+ return $this;
+ }
+
+ final public function __destruct() {
+ if ($this->shouldRestore) {
+ $this->restoreLocalState();
+ }
+
+ $this->discardLocalState();
+ }
+
+ protected function canStashChanges() {
+ return false;
+ }
+
+ protected function saveStash() {
+ throw new PhutilMethodNotImplementedException();
+ }
+
+ protected function restoreStash($ref) {
+ throw new PhutilMethodNotImplementedException();
+ }
+
+ protected function discardStash($ref) {
+ throw new PhutilMethodNotImplementedException();
+ }
+
+ abstract protected function executeSaveLocalState();
+ abstract protected function executeRestoreLocalState();
+ abstract protected function executeDiscardLocalState();
+
+ protected function getIgnoreHints() {
+ return array();
+ }
+
+ final protected function newDisplayFileList($title, array $files) {
+ if (!$files) {
+ return null;
+ }
+
+ $items = array();
+ $items[] = tsprintf("%s\n\n", $title);
+ foreach ($files as $file) {
+ $items[] = tsprintf(
+ " %s\n",
+ $file);
+ }
+
+ return $items;
+ }
+
+ final protected function printFileLists(array $lists) {
+ $lists = array_filter($lists);
+
+ $last_key = last_key($lists);
+ foreach ($lists as $key => $list) {
+ foreach ($list as $item) {
+ echo tsprintf('%B', $item);
+ }
+ if ($key !== $last_key) {
+ echo tsprintf("\n\n");
+ }
+ }
+
+ echo tsprintf("\n");
+ }
+
+}
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Sat, Mar 8, 12:55 PM (4 d, 5 h ago)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
7382415
Default Alt Text
D21314.diff (12 KB)
Attached To
Mode
D21314: Introduce "RepositoryLocalState", a modern version of "requireCleanWorkingCopy()"
Attached
Detach File
Event Timeline
Log In to Comment