1
0
Fork 0
mirror of https://we.phorge.it/source/arcanist.git synced 2024-11-08 16:02:39 +01:00

In "arc land" in Mercurial, inch closer to making complex branch/bookmark workflows function

Summary:
Ref T9948. Ref T13546. This change moves toward a functional "arc land" in Mercurial.

Because of how "bundlerepo.getremotechanges()" works, "hg arc-ls-markers" does not actually list markers in the remote that aren't different from local markers so it's hard to get anywhere with this.

Test Plan: Got somewhat-encouraging output from "arc land" and "hg arc-ls-markers", but too many things are still broken for this to really work yet.

Maniphest Tasks: T13546, T9948

Differential Revision: https://secure.phabricator.com/D21348
This commit is contained in:
epriestley 2020-06-10 13:43:08 -07:00
parent 727d73fec9
commit 488a24c40a
7 changed files with 429 additions and 190 deletions

View file

@ -3,6 +3,9 @@
final class ArcanistMercurialLandEngine final class ArcanistMercurialLandEngine
extends ArcanistLandEngine { extends ArcanistLandEngine {
private $ontoBranchMarker;
private $ontoMarkers;
protected function getDefaultSymbols() { protected function getDefaultSymbols() {
$api = $this->getRepositoryAPI(); $api = $this->getRepositoryAPI();
$log = $this->getLogEngine(); $log = $this->getLogEngine();
@ -242,6 +245,8 @@ final class ArcanistMercurialLandEngine
} }
protected function confirmOntoRefs(array $onto_refs) { protected function confirmOntoRefs(array $onto_refs) {
$api = $this->getRepositoryAPI();
foreach ($onto_refs as $onto_ref) { foreach ($onto_refs as $onto_ref) {
if (!strlen($onto_ref)) { if (!strlen($onto_ref)) {
throw new PhutilArgumentUsageException( throw new PhutilArgumentUsageException(
@ -251,6 +256,63 @@ final class ArcanistMercurialLandEngine
$onto_ref)); $onto_ref));
} }
} }
$remote_ref = id(new ArcanistRemoteRef())
->setRemoteName($this->getOntoRemote());
$markers = $api->newMarkerRefQuery()
->withRemotes(array($remote_ref))
->execute();
$onto_markers = array();
$new_markers = array();
foreach ($onto_refs as $onto_ref) {
$matches = array();
foreach ($markers as $marker) {
if ($marker->getName() === $onto_ref) {
$matches[] = $marker;
}
}
$match_count = count($matches);
if ($match_count > 1) {
throw new PhutilArgumentUsageException(
pht(
'TODO: Ambiguous ref.'));
} else if (!$match_count) {
$new_bookmark = id(new ArcanistMarkerRef())
->setMarkerType(ArcanistMarkerRef::TYPE_BOOKMARK)
->setName($onto_ref)
->attachRemoteRef($remote_ref);
$onto_markers[] = $new_bookmark;
$new_markers[] = $new_bookmark;
} else {
$onto_markers[] = $marker;
}
}
$branches = array();
foreach ($onto_markers as $onto_marker) {
if ($onto_marker->isBranch()) {
$branches[] = $onto_marker;
}
$branch_count = count($branches);
if ($branch_count > 1) {
throw new PhutilArgumentUsageException(
pht(
'TODO: You can not push onto multiple branches in Mercurial.'));
} else if ($branch_count) {
$this->ontoBranchMarker = head($branches);
}
}
if ($new_markers) {
// TODO: If we're creating bookmarks, ask the user to confirm.
}
$this->ontoMarkers = $onto_markers;
} }
protected function selectIntoRemote() { protected function selectIntoRemote() {
@ -366,7 +428,6 @@ final class ArcanistMercurialLandEngine
} }
protected function selectIntoCommit() { protected function selectIntoCommit() {
// Make sure that our "into" target is valid.
$log = $this->getLogEngine(); $log = $this->getLogEngine();
if ($this->getIntoEmpty()) { if ($this->getIntoEmpty()) {
@ -385,7 +446,8 @@ final class ArcanistMercurialLandEngine
$api = $this->getRepositoryAPI(); $api = $this->getRepositoryAPI();
$local_ref = $this->getIntoRef(); $local_ref = $this->getIntoRef();
// TODO: This error handling could probably be cleaner. // TODO: This error handling could probably be cleaner, it will just
// raise an exception without any context.
$into_commit = $api->getCanonicalRevisionName($local_ref); $into_commit = $api->getCanonicalRevisionName($local_ref);
@ -446,51 +508,20 @@ final class ArcanistMercurialLandEngine
$api = $this->getRepositoryAPI(); $api = $this->getRepositoryAPI();
$log = $this->getLogEngine(); $log = $this->getLogEngine();
// See T9948. If the user specified "--into X", we don't know if it's a
// branch, a bookmark, or a symbol which doesn't exist yet.
// In native Mercurial it is difficult to figure this out, so we use
// an extension to provide a command which works like "git ls-remote".
// NOTE: We're using passthru on this because it's a remote command and
// may prompt the user for credentials.
$tmpfile = new TempFile();
Filesystem::remove($tmpfile);
$command = $this->newPassthruCommand(
'%Ls arc-ls-remote --output %s -- %s',
$api->getMercurialExtensionArguments(),
phutil_string_cast($tmpfile),
$target->getRemote());
$command->setDisplayCommand(
'hg ls-remote -- %s',
$target->getRemote());
$err = $command->execute();
if ($err) {
throw new Exception(
pht(
'Call to "hg arc-ls-remote" failed with error "%s".',
$err));
}
$raw_data = Filesystem::readFile($tmpfile);
unset($tmpfile);
$markers = phutil_json_decode($raw_data);
$target_name = $target->getRef(); $target_name = $target->getRef();
$remote_ref = id(new ArcanistRemoteRef())
->setRemoteName($target->getRemote());
$markers = $api->newMarkerRefQuery()
->withRemotes(array($remote_ref))
->withNames(array($target_name))
->execute();
$bookmarks = array(); $bookmarks = array();
$branches = array(); $branches = array();
foreach ($markers as $marker) { foreach ($markers as $marker) {
if ($marker['name'] !== $target_name) { if ($marker->isBookmark()) {
continue;
}
if ($marker['type'] === 'bookmark') {
$bookmarks[] = $marker; $bookmarks[] = $marker;
} else { } else {
$branches[] = $marker; $branches[] = $marker;
@ -536,8 +567,7 @@ final class ArcanistMercurialLandEngine
} }
$bookmark = head($bookmarks); $bookmark = head($bookmarks);
$target_hash = $bookmark['node']; $target_marker = $bookmark;
$is_bookmark = true;
} }
if ($branches) { if ($branches) {
@ -559,13 +589,12 @@ final class ArcanistMercurialLandEngine
$branch = head($branches); $branch = head($branches);
$target_hash = $branch['node']; $target_marker = $branch;
$is_branch = true;
} }
if ($is_branch) { if ($is_branch) {
$err = $this->newPassthru( $err = $this->newPassthru(
'pull -b %s -- %s', 'pull --branch %s -- %s',
$target->getRef(), $target->getRef(),
$target->getRemote()); $target->getRemote());
} else { } else {
@ -581,12 +610,12 @@ final class ArcanistMercurialLandEngine
// them as the cost of doing business. // them as the cost of doing business.
$err = $this->newPassthru( $err = $this->newPassthru(
'pull -B %s -- %s', 'pull --bookmark %s -- %s',
$target->getRef(), $target->getRef(),
$target->getRemote()); $target->getRemote());
} }
// NOTE: It's possible that between the time we ran "ls-remote" and the // NOTE: It's possible that between the time we ran "ls-markers" and the
// time we ran "pull" that the remote changed. // time we ran "pull" that the remote changed.
// It may even have been rewound or rewritten, in which case we did not // It may even have been rewound or rewritten, in which case we did not
@ -597,7 +626,7 @@ final class ArcanistMercurialLandEngine
// TODO: If the Mercurial command server is revived, this check becomes // TODO: If the Mercurial command server is revived, this check becomes
// more reasonable if it's cheap. // more reasonable if it's cheap.
return $target_hash; return $target_marker->getCommitHash();
} }
protected function selectCommits($into_commit, array $symbols) { protected function selectCommits($into_commit, array $symbols) {
@ -691,6 +720,17 @@ final class ArcanistMercurialLandEngine
$revision_ref = $set->getRevisionRef(); $revision_ref = $set->getRevisionRef();
$commit_message = $revision_ref->getCommitMessage(); $commit_message = $revision_ref->getCommitMessage();
// If we're landing "--onto" a branch, set that as the branch marker
// before creating the new commit.
// TODO: We could skip this if we know that the "$into_commit" already
// has the right branch, which it will if we created it.
$branch_marker = $this->ontoBranchMarker;
if ($branch_marker) {
$api->execxLocal('branch -- %s', $branch_marker);
}
try { try {
$argv = array(); $argv = array();
$argv[] = '--dest'; $argv[] = '--dest';
@ -724,12 +764,83 @@ final class ArcanistMercurialLandEngine
protected function pushChange($into_commit) { protected function pushChange($into_commit) {
$api = $this->getRepositoryAPI(); $api = $this->getRepositoryAPI();
// TODO: This does not respect "--into" or "--onto" properly. $bookmarks = array();
foreach ($this->ontoMarkers as $onto_marker) {
if (!$onto_marker->isBookmark()) {
continue;
}
$bookmarks[] = $onto_marker;
}
// If we're pushing to bookmarks, move all the bookmarks we want to push
// to the merge commit. (There doesn't seem to be any way to specify
// "push commit X as bookmark Y" in Mercurial.)
$restore = array();
if ($bookmarks) {
$markers = $api->newMarkerRefQuery()
->withNames(array(mpull($bookmarks, 'getName')))
->withTypes(array(ArcanistMarkerRef::TYPE_BOOKMARK))
->execute();
$markers = mpull($markers, 'getCommitHash', 'getName');
foreach ($bookmarks as $bookmark) {
$bookmark_name = $bookmark->getName();
$old_position = idx($markers, $bookmark_name);
$new_position = $into_commit;
if ($old_position === $new_position) {
continue;
}
$api->execxLocal(
'bookmark --force --rev %s -- %s',
hgsprintf('%s', $new_position),
$bookmark_name);
$restore[$bookmark_name] = $old_position;
}
}
// Now, do the actual push.
$argv = array();
$argv[] = 'push';
if ($bookmarks) {
// If we're pushing at least one bookmark, we can just specify the list
// of bookmarks as things we want to push.
foreach ($bookmarks as $bookmark) {
$argv[] = '--bookmark';
$argv[] = $bookmark->getName();
}
} else {
// Otherwise, specify the commit itself.
$argv[] = '--rev';
$argv[] = hgsprintf('%s', $into_commit);
}
$argv[] = '--';
$argv[] = $this->getOntoRemote();
try {
$this->newPassthru('%Ls', $argv);
} finally {
foreach ($restore as $bookmark_name => $old_position) {
if ($old_position === null) {
$api->execxLocal(
'bookmark --delete -- %s',
$bookmark_name);
} else {
$api->execxLocal(
'bookmark --force --rev %s -- %s',
hgsprintf('%s', $old_position),
$bookmark_name);
}
}
}
$this->newPassthru(
'push --rev %s -- %s',
hgsprintf('%s', $into_commit),
$this->getOntoRemote());
} }
protected function cascadeState(ArcanistLandCommitSet $set, $into_commit) { protected function cascadeState(ArcanistLandCommitSet $set, $into_commit) {
@ -871,4 +982,13 @@ final class ArcanistMercurialLandEngine
'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.'));
} }
private function newRemoteMarkers($remote) {
// See T9948. If the user specified "--into X" or "--onto X", we don't know
// if it's a branch, a bookmark, or a symbol which doesn't exist yet.
}
} }

View file

@ -3,8 +3,7 @@
final class ArcanistGitRepositoryMarkerQuery final class ArcanistGitRepositoryMarkerQuery
extends ArcanistRepositoryMarkerQuery { extends ArcanistRepositoryMarkerQuery {
protected function newLocalRefMarkers() {
protected function newRefMarkers() {
$api = $this->getRepositoryAPI(); $api = $this->getRepositoryAPI();
$future = $this->newCurrentBranchNameFuture()->start(); $future = $this->newCurrentBranchNameFuture()->start();
@ -122,4 +121,8 @@ final class ArcanistGitRepositoryMarkerQuery
return $matches[1]; return $matches[1];
} }
protected function newRemoteRefMarkers(ArcanistRemoteRef $remote) {
throw new PhutilMethodNotImplementedException();
}
} }

View file

@ -7,6 +7,7 @@ final class ArcanistMarkerRef
const HARDPOINT_COMMITREF = 'arc.marker.commitRef'; const HARDPOINT_COMMITREF = 'arc.marker.commitRef';
const HARDPOINT_WORKINGCOPYSTATEREF = 'arc.marker.workingCopyStateRef'; const HARDPOINT_WORKINGCOPYSTATEREF = 'arc.marker.workingCopyStateRef';
const HARDPOINT_REMOTEREF = 'arc.marker.remoteRef';
const TYPE_BRANCH = 'branch'; const TYPE_BRANCH = 'branch';
const TYPE_BOOKMARK = 'bookmark'; const TYPE_BOOKMARK = 'bookmark';
@ -48,6 +49,7 @@ final class ArcanistMarkerRef
return array( return array(
$this->newHardpoint(self::HARDPOINT_COMMITREF), $this->newHardpoint(self::HARDPOINT_COMMITREF),
$this->newHardpoint(self::HARDPOINT_WORKINGCOPYSTATEREF), $this->newHardpoint(self::HARDPOINT_WORKINGCOPYSTATEREF),
$this->newHardpoint(self::HARDPOINT_REMOTEREF),
); );
} }
@ -167,4 +169,12 @@ final class ArcanistMarkerRef
return $this->getHardpoint(self::HARDPOINT_WORKINGCOPYSTATEREF); return $this->getHardpoint(self::HARDPOINT_WORKINGCOPYSTATEREF);
} }
public function attachRemoteRef(ArcanistRemoteRef $ref = null) {
return $this->attachHardpoint(self::HARDPOINT_REMOTEREF, $ref);
}
public function getRemoteRef() {
return $this->getHardpoint(self::HARDPOINT_REMOTEREF);
}
} }

View file

@ -3,144 +3,123 @@
final class ArcanistMercurialRepositoryMarkerQuery final class ArcanistMercurialRepositoryMarkerQuery
extends ArcanistRepositoryMarkerQuery { extends ArcanistRepositoryMarkerQuery {
protected function newRefMarkers() { protected function newLocalRefMarkers() {
$markers = array(); return $this->newMarkers();
if ($this->shouldQueryMarkerType(ArcanistMarkerRef::TYPE_BRANCH)) {
$markers[] = $this->newBranchOrBookmarkMarkers(false);
}
if ($this->shouldQueryMarkerType(ArcanistMarkerRef::TYPE_BOOKMARK)) {
$markers[] = $this->newBranchOrBookmarkMarkers(true);
}
return array_mergev($markers);
} }
private function newBranchOrBookmarkMarkers($is_bookmarks) { protected function newRemoteRefMarkers(ArcanistRemoteRef $remote = null) {
return $this->newMarkers($remote);
}
private function newMarkers(ArcanistRemoteRef $remote = null) {
$api = $this->getRepositoryAPI(); $api = $this->getRepositoryAPI();
$is_branches = !$is_bookmarks; // In native Mercurial it is difficult to identify remote markers, and
// complicated to identify local markers efficiently. We use an extension
// to provide a command which works like "git for-each-ref" locally and
// "git ls-remote" when given a remote.
// NOTE: This is a bit clumsy, but it allows us to get most bookmark and $argv = array();
// branch information in a single command, including full hashes, without foreach ($api->getMercurialExtensionArguments() as $arg) {
// using "--debug" or matching any human readable strings in the output. $argv[] = $arg;
}
$argv[] = 'arc-ls-markers';
// NOTE: We can't get branches and bookmarks together in a single command // NOTE: In remote mode, we're using passthru and a tempfile on this
// because if we query for "heads() + bookmark()", we can't tell if a // because it's a remote command and may prompt the user to provide
// bookmarked result is a branch head or not. // credentials interactively. In local mode, we can just read stdout.
$template_fields = array( if ($remote !== null) {
'{node}', $tmpfile = new TempFile();
'{branch}', Filesystem::remove($tmpfile);
'{join(bookmarks, "\3")}',
'{activebookmark}',
'{desc}',
);
$expect_fields = count($template_fields);
$template = implode('\2', $template_fields).'\1'; $argv[] = '--output';
$argv[] = phutil_string_cast($tmpfile);
if ($is_bookmarks) {
$query = hgsprintf('bookmark()');
} else {
$query = hgsprintf('head()');
} }
$future = $api->newFuture( $argv[] = '--';
'log --rev %s --template %s --',
$query,
$template);
list($lines) = $future->resolve(); if ($remote !== null) {
$argv[] = $remote->getRemoteName();
}
if ($remote !== null) {
$passthru = $api->newPassthru('%Ls', $argv);
$err = $passthru->execute();
if ($err) {
throw new Exception(
pht(
'Call to "hg arc-ls-markers" failed with error "%s".',
$err));
}
$raw_data = Filesystem::readFile($tmpfile);
unset($tmpfile);
} else {
$future = $api->newFuture('%Ls', $argv);
list($raw_data) = $future->resolve();
}
$items = phutil_json_decode($raw_data);
$markers = array(); $markers = array();
foreach ($items as $item) {
$lines = explode("\1", $lines); if (!empty($item['isClosed'])) {
foreach ($lines as $line) { // NOTE: For now, we ignore closed branch heads.
if (!strlen(trim($line))) {
continue; continue;
} }
$fields = explode("\2", $line, $expect_fields); $node = $item['node'];
$actual_fields = count($fields); if (!$node) {
if ($actual_fields !== $expect_fields) { // NOTE: For now, we ignore the virtual "current branch" marker.
throw new Exception( continue;
pht(
'Unexpected number of fields in line "%s", expected %s but '.
'found %s.',
$line,
new PhutilNumber($expect_fields),
new PhutilNumber($actual_fields)));
} }
$node = $fields[0]; switch ($item['type']) {
case 'branch':
$branch = $fields[1]; $marker_type = ArcanistMarkerRef::TYPE_BRANCH;
if (!strlen($branch)) { break;
$branch = 'default'; case 'bookmark':
$marker_type = ArcanistMarkerRef::TYPE_BOOKMARK;
break;
case 'commit':
$marker_type = null;
break;
default:
throw new Exception(
pht(
'Call to "hg arc-ls-markers" returned marker of unknown '.
'type "%s".',
$item['type']));
} }
if ($is_bookmarks) { if ($marker_type === null) {
$bookmarks = $fields[2]; // NOTE: For now, we ignore the virtual "head" marker.
if (strlen($bookmarks)) { continue;
$bookmarks = explode("\3", $fields[2]);
} else {
$bookmarks = array();
}
if (strlen($fields[3])) {
$active_bookmark = $fields[3];
} else {
$active_bookmark = null;
}
} else {
$bookmarks = array();
$active_bookmark = null;
} }
$message = $fields[4];
$message_lines = phutil_split_lines($message, false);
$commit_ref = $api->newCommitRef() $commit_ref = $api->newCommitRef()
->setCommitHash($node) ->setCommitHash($node);
->attachMessage($message);
$template = id(new ArcanistMarkerRef()) $marker_ref = id(new ArcanistMarkerRef())
->setName($item['name'])
->setCommitHash($node) ->setCommitHash($node)
->setSummary(head($message_lines))
->attachCommitRef($commit_ref); ->attachCommitRef($commit_ref);
if ($is_bookmarks) { if (isset($item['description'])) {
foreach ($bookmarks as $bookmark) { $description = $item['description'];
$is_active = ($bookmark === $active_bookmark); $commit_ref->attachMessage($description);
$markers[] = id(clone $template) $description_lines = phutil_split_lines($description, false);
->setMarkerType(ArcanistMarkerRef::TYPE_BOOKMARK) $marker_ref->setSummary(head($description_lines));
->setName($bookmark)
->setIsActive($is_active);
}
} }
if ($is_branches) { $marker_ref
$markers[] = id(clone $template) ->setMarkerType($marker_type)
->setMarkerType(ArcanistMarkerRef::TYPE_BRANCH) ->setIsActive(!empty($item['isActive']));
->setName($branch);
}
}
if ($is_branches) { $markers[] = $marker_ref;
$current_hash = $api->getCanonicalRevisionName('.');
foreach ($markers as $marker) {
if ($marker->getMarkerType() !== ArcanistMarkerRef::TYPE_BRANCH) {
continue;
}
if ($marker->getCommitHash() === $current_hash) {
$marker->setIsActive(true);
}
}
} }
return $markers; return $markers;

View file

@ -8,6 +8,7 @@ abstract class ArcanistRepositoryMarkerQuery
private $names; private $names;
private $commitHashes; private $commitHashes;
private $ancestorCommitHashes; private $ancestorCommitHashes;
private $remotes;
final public function withMarkerTypes(array $types) { final public function withMarkerTypes(array $types) {
$this->markerTypes = array_fuse($types); $this->markerTypes = array_fuse($types);
@ -19,13 +20,35 @@ abstract class ArcanistRepositoryMarkerQuery
return $this; return $this;
} }
final public function withRemotes(array $remotes) {
assert_instances_of($remotes, 'ArcanistRemoteRef');
$this->remotes = $remotes;
return $this;
}
final public function withIsActive($active) { final public function withIsActive($active) {
$this->isActive = $active; $this->isActive = $active;
return $this; return $this;
} }
final public function execute() { final public function execute() {
$markers = $this->newRefMarkers(); $remotes = $this->remotes;
if ($remotes !== null) {
$marker_lists = array();
foreach ($remotes as $remote) {
$marker_list = $this->newRemoteRefMarkers($remote);
foreach ($marker_list as $marker) {
$marker->attachRemoteRef($remote);
}
$marker_lists[] = $marker_list;
}
$markers = array_mergev($marker_lists);
} else {
$markers = $this->newLocalRefMarkers();
foreach ($markers as $marker) {
$marker->attachRemoteRef(null);
}
}
$api = $this->getRepositoryAPI(); $api = $this->getRepositoryAPI();
foreach ($markers as $marker) { foreach ($markers as $marker) {
@ -65,7 +88,6 @@ abstract class ArcanistRepositoryMarkerQuery
} }
} }
return $this->sortMarkers($markers); return $this->sortMarkers($markers);
} }
@ -92,4 +114,7 @@ abstract class ArcanistRepositoryMarkerQuery
return isset($this->markerTypes[$marker_type]); return isset($this->markerTypes[$marker_type]);
} }
abstract protected function newLocalRefMarkers();
abstract protected function newRemoteRefMarkers(ArcanistRemoteRef $remote);
} }

View file

@ -17,12 +17,14 @@ final class ArcanistMercurialLocalState
protected function executeSaveLocalState() { protected function executeSaveLocalState() {
$api = $this->getRepositoryAPI(); $api = $this->getRepositoryAPI();
// TODO: Fix this. // TODO: We need to save the position of "." and the current active
// branch, which may be any symbol at all. Both of these can be pulled
// from "hg arc-ls-markers".
} }
protected function executeRestoreLocalState() { protected function executeRestoreLocalState() {
$api = $this->getRepositoryAPI(); $api = $this->getRepositoryAPI();
// TODO: Fix this.
// TODO: In Mercurial, we may want to discard commits we've created. // TODO: In Mercurial, we may want to discard commits we've created.
// $repository_api->execxLocal( // $repository_api->execxLocal(

View file

@ -19,16 +19,16 @@ cmdtable = {}
command = registrar.command(cmdtable) command = registrar.command(cmdtable)
@command( @command(
"arc-ls-remote", "arc-ls-markers",
[('', 'output', '', [('', 'output', '',
_('file to output refs to'), _('FILE')), _('file to output refs to'), _('FILE')),
] + cmdutil.remoteopts, ] + cmdutil.remoteopts,
_('[--output FILENAME] [SOURCE]')) _('[--output FILENAME] [SOURCE]'))
def lsremote(ui, repo, source="default", **opts): def lsmarkers(ui, repo, source=None, **opts):
"""list markers in a remote """list markers
Show the current branch heads and bookmarks in a specified path/URL or the Show the current branch heads and bookmarks in the local working copy, or
default pull location. a specified path/URL.
Markers are printed to stdout in JSON. Markers are printed to stdout in JSON.
@ -37,14 +37,128 @@ def lsremote(ui, repo, source="default", **opts):
Returns 0 if listing the markers succeeds, 1 otherwise. Returns 0 if listing the markers succeeds, 1 otherwise.
""" """
if source is None:
markers = localmarkers(ui, repo)
else:
markers = remotemarkers(ui, repo, source, opts)
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:
print json.dumps(markers, output_file, **json_opts)
return 0
def localmarkers(ui, repo):
markers = []
active_node = repo['.'].node()
all_heads = set(repo.heads())
current_name = repo.dirstate.branch()
saw_current = False
saw_active = False
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 = (head_node == active_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:
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,
'isCurrent': is_current,
'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
active_bookmark = repo._activebookmark
for bookmark_name, bookmark_node in bookmarks.iteritems():
is_active = (active_bookmark == bookmark_name)
description = repo[bookmark_node].description()
if is_active:
saw_active = True
markers.append({
'type': 'bookmark',
'name': bookmark_name,
'node': node.hex(bookmark_node),
'isActive': is_active,
'description': description,
})
# If the current working copy state is not the head of a branch and there is
# also no active bookmark, add a virtual marker for it so callers can figure
# out exactly where we are.
if not saw_active:
markers.append({
'type': 'commit',
'name': None,
'node': node.hex(active_node),
'isActive': False,
'isClosed': False,
'isTip': False,
'isCurrent': True,
'description': repo['.'].description(),
})
return markers
def remotemarkers(ui, repo, source, opts):
# Disable status output from fetching a remote. # Disable status output from fetching a remote.
ui.quiet = True ui.quiet = True
markers = []
source, branches = hg.parseurl(ui.expandpath(source)) source, branches = hg.parseurl(ui.expandpath(source))
remote = hg.peer(repo, opts, source) remote = hg.peer(repo, opts, source)
markers = []
bundle, remotebranches, cleanup = bundlerepo.getremotechanges( bundle, remotebranches, cleanup = bundlerepo.getremotechanges(
ui, ui,
repo, repo,
@ -73,18 +187,4 @@ def lsremote(ui, repo, source="default", **opts):
'node': node.hex(remotemarks[mark]), 'node': node.hex(remotemarks[mark]),
}) })
json_opts = { return markers
'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:
print json.dumps(markers, output_file, **json_opts)
return 0