1
0
Fork 0
mirror of https://we.phorge.it/source/arcanist.git synced 2024-11-30 18:52:41 +01:00
phorge-arcanist/support/hg/arc-hg.py
epriestley 354da1ddaa 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
2020-07-08 15:30:17 -07:00

199 lines
4.7 KiB
Python

from __future__ import absolute_import
import sys
is_python_3 = sys.version_info[0] >= 3
if is_python_3:
def arc_items(dict):
return dict.items()
else:
def arc_items(dict):
return dict.iteritems()
import os
import json
from mercurial import (
cmdutil,
bookmarks,
bundlerepo,
error,
hg,
i18n,
node,
registrar,
)
_ = i18n._
cmdtable = {}
command = registrar.command(cmdtable)
@command(
b'arc-ls-markers',
[(b'', b'output', b'',
_(b'file to output refs to'), _(b'FILE')),
] + cmdutil.remoteopts,
_(b'[--output FILENAME] [SOURCE]'))
def lsmarkers(ui, repo, source=None, **opts):
"""list markers
Show the current branch heads and bookmarks in the local working copy, or
a specified path/URL.
Markers are printed to stdout in JSON.
(This is an Arcanist extension to Mercurial.)
Returns 0 if listing the markers succeeds, 1 otherwise.
"""
if source is None:
markers = localmarkers(ui, repo)
else:
markers = remotemarkers(ui, repo, source, opts)
for m in markers:
if m['name'] != None:
m['name'] = m['name'].decode('utf-8')
if m['node'] != None:
m['node'] = m['node'].decode('utf-8')
if m['description'] != None:
m['description'] = m['description'].decode('utf-8')
json_opts = {
'indent': 2,
'sort_keys': True,
}
output_file = opts.get('output')
if output_file:
if os.path.exists(output_file):
raise error.Abort(_('File "%s" already exists.' % output_file))
with open(output_file, 'w+') as f:
json.dump(markers, f, **json_opts)
else:
json_data = json.dumps(markers, **json_opts)
print(json_data)
return 0
def localmarkers(ui, repo):
markers = []
active_node = repo[b'.'].node()
all_heads = set(repo.heads())
current_name = repo.dirstate.branch()
branch_list = repo.branchmap().iterbranches()
for branch_name, branch_heads, tip_node, is_closed in branch_list:
for head_node in branch_heads:
is_active = False
if branch_name == current_name:
if head_node == active_node:
is_active = True
is_tip = (head_node == tip_node)
if is_closed:
head_closed = True
else:
head_closed = bool(head_node not in all_heads)
description = repo[head_node].description()
markers.append({
'type': 'branch',
'name': branch_name,
'node': node.hex(head_node),
'isActive': is_active,
'isClosed': head_closed,
'isTip': is_tip,
'description': description,
})
bookmarks = repo._bookmarks
active_bookmark = repo._activebookmark
for bookmark_name, bookmark_node in arc_items(bookmarks):
is_active = (active_bookmark == bookmark_name)
description = repo[bookmark_node].description()
markers.append({
'type': 'bookmark',
'name': bookmark_name,
'node': node.hex(bookmark_node),
'isActive': is_active,
'description': description,
})
# Add virtual markers for the current commit state and current branch state
# so callers can figure 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.
markers.append({
'type': 'branch-state',
'name': current_name,
'node': None,
'isActive': True,
'isClosed': False,
'isTip': False,
'description': None,
})
markers.append({
'type': 'commit-state',
'name': None,
'node': node.hex(active_node),
'isActive': True,
'isClosed': False,
'isTip': False,
'description': repo[b'.'].description(),
})
return markers
def remotemarkers(ui, repo, source, opts):
# Disable status output from fetching a remote.
ui.quiet = True
markers = []
source, branches = hg.parseurl(ui.expandpath(source))
remote = hg.peer(repo, opts, source)
with remote.commandexecutor() as e:
branchmap = e.callcommand(b'branchmap', {}).result()
for branch_name in branchmap:
for branch_node in branchmap[branch_name]:
markers.append({
'type': 'branch',
'name': branch_name,
'node': node.hex(branch_node),
'description': None,
})
with remote.commandexecutor() as e:
remotemarks = bookmarks.unhexlifybookmarks(e.callcommand(b'listkeys', {
b'namespace': b'bookmarks',
}).result())
for mark in remotemarks:
markers.append({
'type': 'bookmark',
'name': mark,
'node': node.hex(remotemarks[mark]),
'description': None,
})
return markers