1
0
Fork 0
mirror of https://we.phorge.it/source/arcanist.git synced 2024-11-22 06:42:41 +01:00

When saving and restoring local state in Mercurial, also save and restore bookmarks

Summary:
Ref PHI1808. In Mercurial, we must save and restore bookmark state explicitly.

  - Save and restore bookmarks.
  - Clean up concepts in "arc-ls-markers" slightly, so we don't need separate "isCurrent" and "isActive" flags, hopefully.

I believe the totality of Mercurial state is:

  - A (non-bare) working copy points at exactly one commit (which might be the empty/null commit, in an empty repository).
  - A working copy has exactly one active branch.
    - Each branch has zero or more heads.
    - Each head may be closed.
    - Each (non-null) commit belongs to exactly one branch.
    - Note that the active branch may have zero heads and zero commits which belong to it!
  - A working copy has zero or one active bookmark.

To capture this, we now emit:

  - A list of branch heads. If a branch head is a working copy commit, that head is flagged as active.
  - A list of bookmarks. If a bookmark is the current bookmark, that bookmark is flagged as active.
  - A single "branch-state" virtual marker. This covers the case where you have run "hg branch X" to create X, but no objects in the working copy actually correspond to X yet. It also covers the case where you are on a concrete branch, but not any head of that branch.
  - A single "commit-state" virtual marker. This always shows the current commit in the working copy.

Test Plan:
  - Useful states to test are:
    - Empty repository (not all commands currently work here).
    - Normal repository, on a bookmark.
    - Normal repository, no bookmark.
    - "hg up 123" to update to somewhere in history.
    - "hg branch X", to start a new branch with no commits.
  - Ran "arc branches" and "arc bookmarks" in various states. Saw generally sensible output.
  - Ran "arc land --hold ..." in various states against a failing remote. Saw generally sensible output, and saw working properly restored to the original state.

Differential Revision: https://secure.phabricator.com/D21396
This commit is contained in:
epriestley 2020-07-08 14:10:00 -07:00
parent 3633364bb9
commit 354da1ddaa
4 changed files with 134 additions and 68 deletions

View file

@ -9,6 +9,8 @@ final class ArcanistMarkerRef
const TYPE_BRANCH = 'branch'; const TYPE_BRANCH = 'branch';
const TYPE_BOOKMARK = 'bookmark'; const TYPE_BOOKMARK = 'bookmark';
const TYPE_COMMIT_STATE = 'commit-state';
const TYPE_BRANCH_STATE = 'branch-state';
private $name; private $name;
private $markerType; private $markerType;
@ -148,6 +150,14 @@ final class ArcanistMarkerRef
return ($this->getMarkerType() === self::TYPE_BRANCH); return ($this->getMarkerType() === self::TYPE_BRANCH);
} }
public function isCommitState() {
return ($this->getMarkerType() === self::TYPE_COMMIT_STATE);
}
public function isBranchState() {
return ($this->getMarkerType() === self::TYPE_BRANCH_STATE);
}
public function attachCommitRef(ArcanistCommitRef $ref) { public function attachCommitRef(ArcanistCommitRef $ref) {
return $this->attachHardpoint(self::HARDPOINT_COMMITREF, $ref); return $this->attachHardpoint(self::HARDPOINT_COMMITREF, $ref);
} }

View file

@ -71,10 +71,6 @@ final class ArcanistMercurialRepositoryMarkerQuery
} }
$node = $item['node']; $node = $item['node'];
if (!$node) {
// NOTE: For now, we ignore the virtual "current branch" marker.
continue;
}
switch ($item['type']) { switch ($item['type']) {
case 'branch': case 'branch':
@ -83,8 +79,11 @@ final class ArcanistMercurialRepositoryMarkerQuery
case 'bookmark': case 'bookmark':
$marker_type = ArcanistMarkerRef::TYPE_BOOKMARK; $marker_type = ArcanistMarkerRef::TYPE_BOOKMARK;
break; break;
case 'commit': case 'commit-state':
$marker_type = null; $marker_type = ArcanistMarkerRef::TYPE_COMMIT_STATE;
break;
case 'branch-state':
$marker_type = ArcanistMarkerRef::TYPE_BRANCH_STATE;
break; break;
default: default:
throw new Exception( throw new Exception(
@ -94,11 +93,6 @@ final class ArcanistMercurialRepositoryMarkerQuery
$item['type'])); $item['type']));
} }
if ($marker_type === null) {
// NOTE: For now, we ignore the virtual "head" marker.
continue;
}
$commit_ref = $api->newCommitRef() $commit_ref = $api->newCommitRef()
->setCommitHash($node); ->setCommitHash($node);

View file

@ -5,40 +5,105 @@ final class ArcanistMercurialLocalState
private $localCommit; private $localCommit;
private $localBranch; private $localBranch;
private $localBookmark;
protected function executeSaveLocalState() { protected function executeSaveLocalState() {
$api = $this->getRepositoryAPI(); $api = $this->getRepositoryAPI();
$log = $this->getWorkflow()->getLogEngine(); $log = $this->getWorkflow()->getLogEngine();
// TODO: Both of these can be pulled from "hg arc-ls-markers" more $markers = $api->newMarkerRefQuery()
// efficiently. ->execute();
$this->localCommit = $api->getCanonicalRevisionName('.'); $local_commit = null;
foreach ($markers as $marker) {
if ($marker->isCommitState()) {
$local_commit = $marker->getCommitHash();
}
}
list($branch) = $api->execxLocal('branch'); if ($local_commit === null) {
$this->localBranch = trim($branch); throw new Exception(
$log->writeTrace(
pht('SAVE STATE'),
pht( pht(
'Unable to identify the current commit in the working copy.'));
}
$this->localCommit = $local_commit;
$local_branch = null;
foreach ($markers as $marker) {
if ($marker->isBranchState()) {
$local_branch = $marker->getName();
break;
}
}
if ($local_branch === null) {
throw new Exception(
pht(
'Unable to identify the current branch in the working copy.'));
}
if ($local_branch !== null) {
$this->localBranch = $local_branch;
}
$local_bookmark = null;
foreach ($markers as $marker) {
if ($marker->isBookmark()) {
if ($marker->getIsActive()) {
$local_bookmark = $marker->getName();
break;
}
}
}
if ($local_bookmark !== null) {
$this->localBookmark = $local_bookmark;
}
$has_bookmark = ($this->localBookmark !== null);
if ($has_bookmark) {
$location = pht(
'Saving local state (at "%s" on branch "%s", bookmarked as "%s").',
$api->getDisplayHash($this->localCommit),
$this->localBranch,
$this->localBookmark);
} else {
$location = pht(
'Saving local state (at "%s" on branch "%s").', 'Saving local state (at "%s" on branch "%s").',
$api->getDisplayHash($this->localCommit), $api->getDisplayHash($this->localCommit),
$this->localBranch)); $this->localBranch);
}
$log->writeTrace(pht('SAVE STATE'), $location);
} }
protected function executeRestoreLocalState() { protected function executeRestoreLocalState() {
$api = $this->getRepositoryAPI(); $api = $this->getRepositoryAPI();
$log = $this->getWorkflow()->getLogEngine(); $log = $this->getWorkflow()->getLogEngine();
$log->writeStatus( if ($this->localBookmark !== null) {
pht('LOAD STATE'), $location = pht(
pht( 'Restoring local state (at "%s" on branch "%s", bookmarked as "%s").',
$api->getDisplayHash($this->localCommit),
$this->localBranch,
$this->localBookmark);
} else {
$location = pht(
'Restoring local state (at "%s" on branch "%s").', 'Restoring local state (at "%s" on branch "%s").',
$api->getDisplayHash($this->localCommit), $api->getDisplayHash($this->localCommit),
$this->localBranch)); $this->localBranch);
}
$log->writeStatus(pht('LOAD STATE'), $location);
$api->execxLocal('update -- %s', $this->localCommit); $api->execxLocal('update -- %s', $this->localCommit);
$api->execxLocal('branch --force -- %s', $this->localBranch); $api->execxLocal('branch --force -- %s', $this->localBranch);
if ($this->localBookmark !== null) {
$api->execxLocal('bookmark --force -- %s', $this->localBookmark);
}
} }
protected function executeDiscardLocalState() { protected function executeDiscardLocalState() {
@ -70,6 +135,12 @@ final class ArcanistMercurialLocalState
'hg branch --force -- %s', 'hg branch --force -- %s',
$this->localBranch); $this->localBranch);
if ($this->localBookmark !== null) {
$commands[] = csprintf(
'hg bookmark --force -- %s',
$this->localBookmark);
}
return $commands; return $commands;
} }

View file

@ -85,21 +85,17 @@ def localmarkers(ui, repo):
active_node = repo[b'.'].node() active_node = repo[b'.'].node()
all_heads = set(repo.heads()) all_heads = set(repo.heads())
current_name = repo.dirstate.branch() current_name = repo.dirstate.branch()
saw_current = False
saw_active = False
branch_list = repo.branchmap().iterbranches() branch_list = repo.branchmap().iterbranches()
for branch_name, branch_heads, tip_node, is_closed in branch_list: for branch_name, branch_heads, tip_node, is_closed in branch_list:
for head_node in branch_heads: for head_node in branch_heads:
is_active = (head_node == active_node)
is_active = False
if branch_name == current_name:
if head_node == active_node:
is_active = True
is_tip = (head_node == tip_node) is_tip = (head_node == tip_node)
is_current = (branch_name == current_name)
if is_current:
saw_current = True
if is_active:
saw_active = True
if is_closed: if is_closed:
head_closed = True head_closed = True
@ -115,26 +111,9 @@ def localmarkers(ui, repo):
'isActive': is_active, 'isActive': is_active,
'isClosed': head_closed, 'isClosed': head_closed,
'isTip': is_tip, 'isTip': is_tip,
'isCurrent': is_current,
'description': description, 'description': description,
}) })
# If the current branch (selected with "hg branch X") is not reflected in
# the list of heads we selected, add a virtual head for it so callers get
# a complete picture of repository marker state.
if not saw_current:
markers.append({
'type': 'branch',
'name': current_name,
'node': None,
'isActive': False,
'isClosed': False,
'isTip': False,
'isCurrent': True,
'description': None,
})
bookmarks = repo._bookmarks bookmarks = repo._bookmarks
active_bookmark = repo._activebookmark active_bookmark = repo._activebookmark
@ -142,9 +121,6 @@ def localmarkers(ui, repo):
is_active = (active_bookmark == bookmark_name) is_active = (active_bookmark == bookmark_name)
description = repo[bookmark_node].description() description = repo[bookmark_node].description()
if is_active:
saw_active = True
markers.append({ markers.append({
'type': 'bookmark', 'type': 'bookmark',
'name': bookmark_name, 'name': bookmark_name,
@ -153,19 +129,34 @@ def localmarkers(ui, repo):
'description': description, 'description': description,
}) })
# If the current working copy state is not the head of a branch and there is # Add virtual markers for the current commit state and current branch state
# also no active bookmark, add a virtual marker for it so callers can figure # so callers can figure out exactly where we are.
# out exactly where we are.
# Common cases where this matters include:
# You run "hg update 123" to update to an older revision. Your working
# copy commit will not be a branch head or a bookmark.
# You run "hg branch X" to create a new branch, but have not made any commits
# yet. Your working copy branch will not be reflected in any commits.
if not saw_active:
markers.append({ markers.append({
'type': 'commit', 'type': 'branch-state',
'name': None, 'name': current_name,
'node': node.hex(active_node), 'node': None,
'isActive': False, 'isActive': True,
'isClosed': False,
'isTip': False,
'description': None,
})
markers.append({
'type': 'commit-state',
'name': None,
'node': node.hex(active_node),
'isActive': True,
'isClosed': False, 'isClosed': False,
'isTip': False, 'isTip': False,
'isCurrent': True,
'description': repo[b'.'].description(), 'description': repo[b'.'].description(),
}) })