2013-06-20 23:16:36 +04:00
|
|
|
# smartlog.py
|
|
|
|
#
|
|
|
|
# Copyright 2013 Facebook, Inc.
|
|
|
|
#
|
|
|
|
# This software may be used and distributed according to the terms of the
|
|
|
|
# GNU General Public License version 2 or any later version.
|
|
|
|
|
2016-09-17 01:48:42 +03:00
|
|
|
"""command to display a relevant subgraph
|
|
|
|
|
|
|
|
With this extension installed, Mercurial gains one new command: smartlog.
|
|
|
|
It displays a subgraph of changesets containing only the changesets relevant
|
|
|
|
to the user.
|
|
|
|
|
|
|
|
::
|
|
|
|
|
|
|
|
[smartlog]
|
|
|
|
# (remote) names to show
|
|
|
|
repos = , remote/, default/
|
|
|
|
names = @, master, stable
|
2016-09-16 18:36:10 +03:00
|
|
|
# move the top non-public stack to the second column
|
|
|
|
indentnonpublic = True
|
2017-05-26 19:03:58 +03:00
|
|
|
# whether to use ancestor cache (speed up on huge repos)
|
|
|
|
useancestorcache = False
|
2016-09-17 01:48:42 +03:00
|
|
|
"""
|
|
|
|
|
2016-04-23 00:40:20 +03:00
|
|
|
from __future__ import absolute_import
|
|
|
|
|
2017-05-26 19:03:58 +03:00
|
|
|
import contextlib
|
2017-05-31 14:11:53 +03:00
|
|
|
import anydbm
|
2016-02-11 18:11:59 +03:00
|
|
|
from itertools import chain
|
2015-12-29 22:36:15 +03:00
|
|
|
import re
|
2013-06-20 23:16:36 +04:00
|
|
|
|
2016-04-23 00:40:20 +03:00
|
|
|
from mercurial import (
|
|
|
|
bookmarks,
|
|
|
|
cmdutil,
|
|
|
|
commands,
|
|
|
|
error,
|
|
|
|
extensions,
|
|
|
|
graphmod,
|
|
|
|
obsolete,
|
2016-09-16 18:36:10 +03:00
|
|
|
phases,
|
templates: fix help messages for template keywords
Summary:
Many of the template keywords in our extensions were being registered
incorrectly, causing their help output to be rendered incorrectly in the
"hg help templates" output. The ones in smartlog.py were particularly bad, as
most of them showed only their description, without displaying the name of the
template. In smartlog.py only singlepublicsuccessor was being displayed
correctly, because it's docstring explicitly included it's own name at the
start.
This fixes all of our extensions to consistently use the
registrar.templatekeyword() decorator to register the keywords. This decorator
automatically prefixes the help message with the keyword name. The
mercurial/extensions.py code will explicitly check to see if an extension
contains an "templatekeyword" attribute, and if so it will register any
keywords contained in this registry after calling extsetup().
Test Plan:
Added new unit tests to check the output of "hg help templates" for the
affected keywords.
Reviewers: #sourcecontrol, kulshrax, ikostia, rmcelroy
Reviewed By: rmcelroy
Subscribers: rmcelroy, net-systems-diffs@, yogeshwer, mjpieters
Differential Revision: https://phabricator.intern.facebook.com/D4427729
Signature: t1:4427729:1484831476:17b478a5e867dfc3f85402588c381bf8b1831107
2017-01-19 23:52:54 +03:00
|
|
|
registrar,
|
2017-05-26 19:03:58 +03:00
|
|
|
revlog,
|
2016-04-23 00:40:20 +03:00
|
|
|
revset,
|
2017-02-23 15:08:18 +03:00
|
|
|
revsetlang,
|
2016-04-23 00:40:20 +03:00
|
|
|
scmutil,
|
2017-02-24 00:09:59 +03:00
|
|
|
smartset,
|
2016-04-23 00:40:20 +03:00
|
|
|
templatekw,
|
|
|
|
util,
|
|
|
|
)
|
|
|
|
from mercurial import node as nodemod
|
|
|
|
from mercurial.i18n import _
|
|
|
|
from hgext import pager
|
|
|
|
|
2013-07-21 07:30:11 +04:00
|
|
|
pager.attended.append('smartlog')
|
|
|
|
|
2013-06-20 23:16:36 +04:00
|
|
|
cmdtable = {}
|
2017-05-22 23:38:37 +03:00
|
|
|
command = registrar.command(cmdtable)
|
2016-11-29 16:24:07 +03:00
|
|
|
testedwith = 'ships-with-fb-hgext'
|
2014-11-11 05:45:39 +03:00
|
|
|
commit_info = False
|
2015-01-21 23:20:33 +03:00
|
|
|
hiddenchanges = 0
|
2013-06-20 23:16:36 +04:00
|
|
|
|
2016-12-22 23:07:45 +03:00
|
|
|
# Remove unsupported --limit option.
|
|
|
|
logopts = [opt for opt in commands.logopts if opt[1] != "limit"]
|
|
|
|
|
2017-05-26 19:03:58 +03:00
|
|
|
@contextlib.contextmanager
|
|
|
|
def ancestorcache(path):
|
|
|
|
# simple cache to speed up revlog.ancestors
|
|
|
|
try:
|
2017-05-31 14:11:53 +03:00
|
|
|
db = anydbm.open(path, 'c')
|
|
|
|
except anydbm.error:
|
2017-05-26 19:03:58 +03:00
|
|
|
# database locked, fail gracefully
|
|
|
|
yield
|
|
|
|
else:
|
|
|
|
def revlogancestor(orig, self, a, b):
|
|
|
|
key = a + b
|
|
|
|
try:
|
|
|
|
return db[key]
|
|
|
|
except KeyError:
|
|
|
|
result = orig(self, a, b)
|
|
|
|
db[key] = result
|
|
|
|
return result
|
|
|
|
extensions.wrapfunction(revlog.revlog, 'ancestor', revlogancestor)
|
|
|
|
try:
|
|
|
|
yield
|
|
|
|
finally:
|
|
|
|
extensions.unwrapfunction(revlog.revlog, 'ancestor', revlogancestor)
|
|
|
|
db.close()
|
|
|
|
|
2016-09-17 04:22:36 +03:00
|
|
|
def _drawendinglines(orig, lines, extra, edgemap, seen):
|
|
|
|
# if we are going to have only one single column, draw the missing '|'s
|
|
|
|
# and restore everything to normal. see comment in 'ascii' below for an
|
|
|
|
# example of what will be changed. note: we do not respect 'graphstyle'
|
|
|
|
# but always draw '|' here, for simplicity.
|
|
|
|
if len(seen) == 1 or any(l[0:2] != [' ', ' '] for l in lines):
|
|
|
|
# draw '|' from bottom to top in the 1st column to connect to
|
|
|
|
# something, like a '/' in the 2nd column, or a '+' in the 1st column.
|
|
|
|
for line in reversed(lines):
|
|
|
|
if line[0:2] != [' ', ' ']:
|
|
|
|
break
|
|
|
|
line[0] = '|'
|
|
|
|
# undo the wrapfunction
|
|
|
|
extensions.unwrapfunction(graphmod, '_drawendinglines',
|
|
|
|
_drawendinglines)
|
|
|
|
# restore the space to '|'
|
|
|
|
for k, v in edgemap.iteritems():
|
|
|
|
if v == ' ':
|
|
|
|
edgemap[k] = '|'
|
|
|
|
orig(lines, extra, edgemap, seen)
|
|
|
|
|
2013-06-20 23:16:36 +04:00
|
|
|
def uisetup(ui):
|
|
|
|
# Hide output for fake nodes
|
|
|
|
def show(orig, self, ctx, copies, matchfn, props):
|
|
|
|
if ctx.node() == "...":
|
|
|
|
self.ui.write('\n\n\n')
|
|
|
|
return
|
2014-11-11 05:45:39 +03:00
|
|
|
res = orig(self, ctx, copies, matchfn, props)
|
|
|
|
|
|
|
|
if commit_info and ctx == self.repo['.']:
|
|
|
|
changes = ctx.p1().status(ctx)
|
2016-04-23 00:40:20 +03:00
|
|
|
prefixes = ['M', 'A', 'R', '!', '?', 'I', 'C']
|
|
|
|
for prefix, change in zip(prefixes, changes):
|
|
|
|
for fname in change:
|
|
|
|
self.ui.write(' {0} {1}\n'.format(prefix, fname))
|
2014-11-11 05:45:39 +03:00
|
|
|
self.ui.write('\n')
|
|
|
|
return res
|
2013-06-20 23:16:36 +04:00
|
|
|
|
2016-04-23 00:40:20 +03:00
|
|
|
extensions.wrapfunction(cmdutil.changeset_printer, '_show', show)
|
|
|
|
extensions.wrapfunction(cmdutil.changeset_templater, '_show', show)
|
2013-06-20 23:16:36 +04:00
|
|
|
|
|
|
|
def ascii(orig, ui, state, type, char, text, coldata):
|
|
|
|
if type == 'F':
|
2016-09-17 04:22:36 +03:00
|
|
|
# the fake node is used to move draft changesets to the 2nd column.
|
|
|
|
# there can be at most one fake node, which should also be at the
|
|
|
|
# top of the graph.
|
|
|
|
# we should not draw the fake node and its edges, so change its
|
|
|
|
# edge style to a space, and return directly.
|
|
|
|
# these are very hacky but it seems to work well and it seems there
|
|
|
|
# is no other easy choice for now.
|
|
|
|
edgemap = state['edges']
|
|
|
|
for k in edgemap.iterkeys():
|
|
|
|
edgemap[k] = ' '
|
|
|
|
# also we need to hack _drawendinglines to draw the missing '|'s:
|
|
|
|
# (before) (after)
|
|
|
|
# o draft o draft
|
|
|
|
# / /
|
|
|
|
# |
|
|
|
|
# o o
|
|
|
|
extensions.wrapfunction(graphmod, '_drawendinglines',
|
|
|
|
_drawendinglines)
|
|
|
|
return
|
|
|
|
orig(ui, state, type, char, text, coldata)
|
2016-04-23 00:40:20 +03:00
|
|
|
|
|
|
|
extensions.wrapfunction(graphmod, 'ascii', ascii)
|
2013-06-20 23:16:36 +04:00
|
|
|
|
2015-01-21 23:20:33 +03:00
|
|
|
revset.symbols['smartlog'] = smartlogrevset
|
|
|
|
revset.safesymbols.add('smartlog')
|
|
|
|
|
2016-10-26 03:22:22 +03:00
|
|
|
extensions.afterloaded('evolve', wrapshowgraphnode)
|
|
|
|
|
templates: fix help messages for template keywords
Summary:
Many of the template keywords in our extensions were being registered
incorrectly, causing their help output to be rendered incorrectly in the
"hg help templates" output. The ones in smartlog.py were particularly bad, as
most of them showed only their description, without displaying the name of the
template. In smartlog.py only singlepublicsuccessor was being displayed
correctly, because it's docstring explicitly included it's own name at the
start.
This fixes all of our extensions to consistently use the
registrar.templatekeyword() decorator to register the keywords. This decorator
automatically prefixes the help message with the keyword name. The
mercurial/extensions.py code will explicitly check to see if an extension
contains an "templatekeyword" attribute, and if so it will register any
keywords contained in this registry after calling extsetup().
Test Plan:
Added new unit tests to check the output of "hg help templates" for the
affected keywords.
Reviewers: #sourcecontrol, kulshrax, ikostia, rmcelroy
Reviewed By: rmcelroy
Subscribers: rmcelroy, net-systems-diffs@, yogeshwer, mjpieters
Differential Revision: https://phabricator.intern.facebook.com/D4427729
Signature: t1:4427729:1484831476:17b478a5e867dfc3f85402588c381bf8b1831107
2017-01-19 23:52:54 +03:00
|
|
|
templatekeyword = registrar.templatekeyword()
|
|
|
|
|
|
|
|
@templatekeyword('singlepublicsuccessor')
|
|
|
|
def singlepublicsuccessor(repo, ctx, templ, **args):
|
|
|
|
"""String. Get a single public successor for a
|
|
|
|
given node. If there's none or more than one, return empty string.
|
|
|
|
This is intended to be used for "Landed as" marking
|
|
|
|
in `hg sl` output."""
|
|
|
|
successorssets = obsolete.successorssets(repo, ctx.node())
|
|
|
|
unfiltered = repo.unfiltered()
|
|
|
|
ctxs = (unfiltered[n] for n in chain.from_iterable(successorssets))
|
|
|
|
public = (c.hex() for c in ctxs if not c.mutable() and c != ctx)
|
|
|
|
first = next(public, '')
|
|
|
|
second = next(public, '')
|
|
|
|
|
|
|
|
return '' if first and second else first
|
|
|
|
|
|
|
|
@templatekeyword('rebasesuccessors')
|
|
|
|
def rebasesuccessors(repo, ctx, **args):
|
|
|
|
"""Return all of the node's successors created as a result of rebase"""
|
|
|
|
rsnodes = list(modifysuccessors(ctx, 'rebase'))
|
2017-04-18 23:04:11 +03:00
|
|
|
return templatekw.showlist('rebasesuccessor', rsnodes, args)
|
templates: fix help messages for template keywords
Summary:
Many of the template keywords in our extensions were being registered
incorrectly, causing their help output to be rendered incorrectly in the
"hg help templates" output. The ones in smartlog.py were particularly bad, as
most of them showed only their description, without displaying the name of the
template. In smartlog.py only singlepublicsuccessor was being displayed
correctly, because it's docstring explicitly included it's own name at the
start.
This fixes all of our extensions to consistently use the
registrar.templatekeyword() decorator to register the keywords. This decorator
automatically prefixes the help message with the keyword name. The
mercurial/extensions.py code will explicitly check to see if an extension
contains an "templatekeyword" attribute, and if so it will register any
keywords contained in this registry after calling extsetup().
Test Plan:
Added new unit tests to check the output of "hg help templates" for the
affected keywords.
Reviewers: #sourcecontrol, kulshrax, ikostia, rmcelroy
Reviewed By: rmcelroy
Subscribers: rmcelroy, net-systems-diffs@, yogeshwer, mjpieters
Differential Revision: https://phabricator.intern.facebook.com/D4427729
Signature: t1:4427729:1484831476:17b478a5e867dfc3f85402588c381bf8b1831107
2017-01-19 23:52:54 +03:00
|
|
|
|
|
|
|
@templatekeyword('amendsuccessors')
|
|
|
|
def amendsuccessors(repo, ctx, **args):
|
|
|
|
"""Return all of the node's successors created as a result of amend"""
|
|
|
|
asnodes = list(modifysuccessors(ctx, 'amend'))
|
2017-04-18 23:04:11 +03:00
|
|
|
return templatekw.showlist('amendsuccessor', asnodes, args)
|
templates: fix help messages for template keywords
Summary:
Many of the template keywords in our extensions were being registered
incorrectly, causing their help output to be rendered incorrectly in the
"hg help templates" output. The ones in smartlog.py were particularly bad, as
most of them showed only their description, without displaying the name of the
template. In smartlog.py only singlepublicsuccessor was being displayed
correctly, because it's docstring explicitly included it's own name at the
start.
This fixes all of our extensions to consistently use the
registrar.templatekeyword() decorator to register the keywords. This decorator
automatically prefixes the help message with the keyword name. The
mercurial/extensions.py code will explicitly check to see if an extension
contains an "templatekeyword" attribute, and if so it will register any
keywords contained in this registry after calling extsetup().
Test Plan:
Added new unit tests to check the output of "hg help templates" for the
affected keywords.
Reviewers: #sourcecontrol, kulshrax, ikostia, rmcelroy
Reviewed By: rmcelroy
Subscribers: rmcelroy, net-systems-diffs@, yogeshwer, mjpieters
Differential Revision: https://phabricator.intern.facebook.com/D4427729
Signature: t1:4427729:1484831476:17b478a5e867dfc3f85402588c381bf8b1831107
2017-01-19 23:52:54 +03:00
|
|
|
|
|
|
|
@templatekeyword('splitsuccessors')
|
|
|
|
def splitsuccessors(repo, ctx, **args):
|
|
|
|
"""Return all of the node's successors created as a result of split"""
|
|
|
|
asnodes = list(modifysuccessors(ctx, 'split'))
|
2017-04-18 23:04:11 +03:00
|
|
|
return templatekw.showlist('splitsuccessor', asnodes, args)
|
templates: fix help messages for template keywords
Summary:
Many of the template keywords in our extensions were being registered
incorrectly, causing their help output to be rendered incorrectly in the
"hg help templates" output. The ones in smartlog.py were particularly bad, as
most of them showed only their description, without displaying the name of the
template. In smartlog.py only singlepublicsuccessor was being displayed
correctly, because it's docstring explicitly included it's own name at the
start.
This fixes all of our extensions to consistently use the
registrar.templatekeyword() decorator to register the keywords. This decorator
automatically prefixes the help message with the keyword name. The
mercurial/extensions.py code will explicitly check to see if an extension
contains an "templatekeyword" attribute, and if so it will register any
keywords contained in this registry after calling extsetup().
Test Plan:
Added new unit tests to check the output of "hg help templates" for the
affected keywords.
Reviewers: #sourcecontrol, kulshrax, ikostia, rmcelroy
Reviewed By: rmcelroy
Subscribers: rmcelroy, net-systems-diffs@, yogeshwer, mjpieters
Differential Revision: https://phabricator.intern.facebook.com/D4427729
Signature: t1:4427729:1484831476:17b478a5e867dfc3f85402588c381bf8b1831107
2017-01-19 23:52:54 +03:00
|
|
|
|
|
|
|
@templatekeyword('foldsuccessors')
|
|
|
|
def foldsuccessors(repo, ctx, **args):
|
|
|
|
"""Return all of the node's successors created as a result of fold"""
|
|
|
|
asnodes = list(modifysuccessors(ctx, 'fold'))
|
2017-04-18 23:04:11 +03:00
|
|
|
return templatekw.showlist('foldsuccessor', asnodes, args)
|
templates: fix help messages for template keywords
Summary:
Many of the template keywords in our extensions were being registered
incorrectly, causing their help output to be rendered incorrectly in the
"hg help templates" output. The ones in smartlog.py were particularly bad, as
most of them showed only their description, without displaying the name of the
template. In smartlog.py only singlepublicsuccessor was being displayed
correctly, because it's docstring explicitly included it's own name at the
start.
This fixes all of our extensions to consistently use the
registrar.templatekeyword() decorator to register the keywords. This decorator
automatically prefixes the help message with the keyword name. The
mercurial/extensions.py code will explicitly check to see if an extension
contains an "templatekeyword" attribute, and if so it will register any
keywords contained in this registry after calling extsetup().
Test Plan:
Added new unit tests to check the output of "hg help templates" for the
affected keywords.
Reviewers: #sourcecontrol, kulshrax, ikostia, rmcelroy
Reviewed By: rmcelroy
Subscribers: rmcelroy, net-systems-diffs@, yogeshwer, mjpieters
Differential Revision: https://phabricator.intern.facebook.com/D4427729
Signature: t1:4427729:1484831476:17b478a5e867dfc3f85402588c381bf8b1831107
2017-01-19 23:52:54 +03:00
|
|
|
|
|
|
|
@templatekeyword('histeditsuccessors')
|
|
|
|
def histeditsuccessors(repo, ctx, **args):
|
|
|
|
"""Return all of the node's successors created as a result of
|
|
|
|
histedit
|
|
|
|
"""
|
|
|
|
asnodes = list(modifysuccessors(ctx, 'histedit'))
|
2017-04-18 23:04:11 +03:00
|
|
|
return templatekw.showlist('histeditsuccessor', asnodes, args)
|
templates: fix help messages for template keywords
Summary:
Many of the template keywords in our extensions were being registered
incorrectly, causing their help output to be rendered incorrectly in the
"hg help templates" output. The ones in smartlog.py were particularly bad, as
most of them showed only their description, without displaying the name of the
template. In smartlog.py only singlepublicsuccessor was being displayed
correctly, because it's docstring explicitly included it's own name at the
start.
This fixes all of our extensions to consistently use the
registrar.templatekeyword() decorator to register the keywords. This decorator
automatically prefixes the help message with the keyword name. The
mercurial/extensions.py code will explicitly check to see if an extension
contains an "templatekeyword" attribute, and if so it will register any
keywords contained in this registry after calling extsetup().
Test Plan:
Added new unit tests to check the output of "hg help templates" for the
affected keywords.
Reviewers: #sourcecontrol, kulshrax, ikostia, rmcelroy
Reviewed By: rmcelroy
Subscribers: rmcelroy, net-systems-diffs@, yogeshwer, mjpieters
Differential Revision: https://phabricator.intern.facebook.com/D4427729
Signature: t1:4427729:1484831476:17b478a5e867dfc3f85402588c381bf8b1831107
2017-01-19 23:52:54 +03:00
|
|
|
|
|
|
|
def showgraphnode(orig, repo, ctx, **args):
|
|
|
|
"""Show obsolete nodes as 'x', even when inhibited."""
|
|
|
|
char = orig(repo, ctx, **args)
|
|
|
|
if char != 'o' or ctx.node() == '...':
|
|
|
|
return char
|
|
|
|
return 'x' if repo.revs('allsuccessors(%d)', ctx.rev()) else char
|
|
|
|
|
|
|
|
def wrapshowgraphnode(loaded):
|
|
|
|
"""Ensure that evolve is loaded before wrapping showgraph() because
|
|
|
|
the wrapper functions uses the 'allsuccessors' revset symbol,
|
|
|
|
which is provided by the evolve extension.
|
|
|
|
"""
|
|
|
|
if loaded:
|
|
|
|
# Some callers directly call showgraphnode(), so wrap the original
|
|
|
|
# function in addition to updating templatekw.keywords.
|
|
|
|
extensions.wrapfunction(templatekw, 'showgraphnode', showgraphnode)
|
|
|
|
|
2016-04-27 14:14:12 +03:00
|
|
|
def modifysuccessors(ctx, operation):
|
|
|
|
"""Return all of the node's successors which were created as a result
|
2017-01-20 11:24:45 +03:00
|
|
|
of a given modification operation"""
|
|
|
|
for m in obsolete.successormarkers(ctx):
|
|
|
|
if m.metadata().get('operation') == operation:
|
|
|
|
for node in m.succnodes():
|
|
|
|
yield nodemod.hex(node)
|
2016-04-27 14:14:12 +03:00
|
|
|
|
2013-06-20 23:16:36 +04:00
|
|
|
def sortnodes(nodes, parentfunc, masters):
|
|
|
|
"""Topologically sorts the nodes, using the parentfunc to find
|
|
|
|
the parents of nodes. Given a topological tie between children,
|
|
|
|
any node in masters is chosen last."""
|
|
|
|
nodes = set(nodes)
|
|
|
|
childmap = {}
|
|
|
|
parentmap = {}
|
|
|
|
roots = []
|
|
|
|
|
|
|
|
# Build a child and parent map
|
|
|
|
for n in nodes:
|
|
|
|
parents = [p for p in parentfunc(n) if p in nodes]
|
|
|
|
parentmap[n] = set(parents)
|
|
|
|
for p in parents:
|
|
|
|
childmap.setdefault(p, set()).add(n)
|
2016-02-12 04:31:42 +03:00
|
|
|
if not parents or (len(parents) == 1 and parents[0] == -1) and n != -1:
|
2013-06-20 23:16:36 +04:00
|
|
|
roots.append(n)
|
|
|
|
|
2016-02-23 16:22:05 +03:00
|
|
|
def childsortkey(x):
|
|
|
|
# Process children in the master line last. This makes them always
|
|
|
|
# appear on the left side of the dag, resulting in a nice straight
|
|
|
|
# master line in the ascii output. Otherwise show the oldest first, so
|
|
|
|
# the graph is approximately in chronological order.
|
|
|
|
return (x in masters, x)
|
2013-06-20 23:16:36 +04:00
|
|
|
|
|
|
|
# Process roots, adding children to the queue as they become roots
|
|
|
|
results = []
|
|
|
|
while roots:
|
|
|
|
n = roots.pop(0)
|
|
|
|
results.append(n)
|
|
|
|
if n in childmap:
|
|
|
|
children = list(childmap[n])
|
|
|
|
# reverse=True here because we insert(0) below, resulting
|
|
|
|
# in a reversed insertion of the children.
|
2016-02-23 16:22:05 +03:00
|
|
|
children = sorted(children, reverse=True, key=childsortkey)
|
2013-06-20 23:16:36 +04:00
|
|
|
for c in children:
|
|
|
|
childparents = parentmap[c]
|
|
|
|
childparents.remove(n)
|
|
|
|
if len(childparents) == 0:
|
|
|
|
# insert at the beginning, that way child nodes
|
|
|
|
# are likely to be output immediately after their
|
|
|
|
# parents.
|
|
|
|
roots.insert(0, c)
|
|
|
|
|
|
|
|
return results
|
|
|
|
|
2014-10-31 02:01:50 +03:00
|
|
|
def getdag(ui, repo, revs, master):
|
2013-06-20 23:16:36 +04:00
|
|
|
|
|
|
|
# Fake ctx that we stick in the dag so we can special case it later
|
|
|
|
class fakectx(object):
|
|
|
|
def __init__(self, rev):
|
|
|
|
self._rev = rev
|
|
|
|
def node(self):
|
|
|
|
return "..."
|
|
|
|
def obsolete(self):
|
|
|
|
return False
|
2016-09-16 18:36:10 +03:00
|
|
|
def phase(self):
|
|
|
|
return None
|
2013-06-20 23:16:36 +04:00
|
|
|
def rev(self):
|
|
|
|
return self._rev
|
|
|
|
def files(self):
|
|
|
|
return []
|
2015-03-13 19:29:29 +03:00
|
|
|
def closesbranch(self):
|
|
|
|
return False
|
2013-06-20 23:16:36 +04:00
|
|
|
|
|
|
|
knownrevs = set(revs)
|
|
|
|
gpcache = {}
|
|
|
|
results = []
|
|
|
|
|
2016-03-31 21:26:15 +03:00
|
|
|
# we store parents together with the parent type information
|
|
|
|
# but sometimes we need just a list of parents
|
|
|
|
# [(a,b), (c,d), (e,f)] => [b, d, f]
|
|
|
|
def unzip(parents):
|
|
|
|
if parents:
|
|
|
|
return list(zip(*parents)[1])
|
|
|
|
else:
|
|
|
|
return list()
|
|
|
|
|
2013-06-20 23:16:36 +04:00
|
|
|
# For each rev we need to show, compute it's parents in the dag.
|
|
|
|
# If we have to reach for a grandparent, insert a fake node so we
|
|
|
|
# can show '...' in the graph.
|
|
|
|
# Use 'reversed' to start at the lowest commit so fake nodes are
|
|
|
|
# placed at their lowest possible positions.
|
|
|
|
for rev in reversed(revs):
|
|
|
|
ctx = repo[rev]
|
|
|
|
# Parents in the dag
|
2016-03-31 21:26:15 +03:00
|
|
|
parents = sorted(set([(graphmod.PARENT, p.rev()) for p in ctx.parents()
|
2013-06-20 23:16:36 +04:00
|
|
|
if p.rev() in knownrevs]))
|
|
|
|
# Parents not in the dag
|
|
|
|
mpars = [p.rev() for p in ctx.parents() if
|
2016-04-23 00:40:20 +03:00
|
|
|
p.rev() != nodemod.nullrev and p.rev() not in unzip(parents)]
|
2013-06-20 23:16:36 +04:00
|
|
|
|
|
|
|
for mpar in mpars:
|
|
|
|
gp = gpcache.get(mpar)
|
|
|
|
if gp is None:
|
2016-04-23 00:40:20 +03:00
|
|
|
gp = gpcache[mpar] = revset.reachableroots(
|
2017-02-24 00:09:59 +03:00
|
|
|
repo, smartset.baseset(revs), [mpar])
|
2013-06-20 23:16:36 +04:00
|
|
|
if not gp:
|
2016-03-31 21:26:15 +03:00
|
|
|
parents.append((graphmod.MISSINGPARENT, mpar))
|
2013-06-20 23:16:36 +04:00
|
|
|
else:
|
2016-03-31 21:26:15 +03:00
|
|
|
gp = [g for g in gp if g not in unzip(parents)]
|
2013-06-20 23:16:36 +04:00
|
|
|
for g in gp:
|
2016-09-17 02:51:26 +03:00
|
|
|
parents.append((graphmod.GRANDPARENT, g))
|
2013-06-20 23:16:36 +04:00
|
|
|
|
|
|
|
results.append((ctx.rev(), 'C', ctx, parents))
|
|
|
|
|
|
|
|
# Compute parent rev->parents mapping
|
|
|
|
lookup = {}
|
|
|
|
for r in results:
|
2016-03-31 21:26:15 +03:00
|
|
|
lookup[r[0]] = unzip(r[3])
|
|
|
|
|
2013-06-20 23:16:36 +04:00
|
|
|
def parentfunc(node):
|
|
|
|
return lookup.get(node, [])
|
|
|
|
|
|
|
|
# Compute the revs on the master line. We use this for sorting later.
|
|
|
|
masters = set()
|
|
|
|
queue = [master]
|
|
|
|
while queue:
|
|
|
|
m = queue.pop()
|
2016-04-23 00:40:20 +03:00
|
|
|
if m not in masters:
|
2013-06-20 23:16:36 +04:00
|
|
|
masters.add(m)
|
|
|
|
queue.extend(lookup.get(m, []))
|
|
|
|
|
2016-09-17 04:22:36 +03:00
|
|
|
# Topologically sort the noderev numbers. Note: unlike the vanilla
|
|
|
|
# topological sorting, we move master to the top.
|
2016-03-31 21:26:15 +03:00
|
|
|
order = sortnodes([r[0] for r in results], parentfunc, masters)
|
2015-12-23 05:39:39 +03:00
|
|
|
order = dict((e[1], e[0]) for e in enumerate(order))
|
2013-06-20 23:16:36 +04:00
|
|
|
|
|
|
|
# Sort the actual results based on their position in the 'order'
|
2014-10-31 02:01:50 +03:00
|
|
|
try:
|
2016-09-17 04:22:36 +03:00
|
|
|
results.sort(key=lambda x: order[x[0]], reverse=True)
|
2016-04-23 00:40:20 +03:00
|
|
|
except ValueError: # Happened when 'order' is empty
|
2015-12-29 22:36:15 +03:00
|
|
|
msg = _('note: smartlog encountered an error\n')
|
|
|
|
hint = _('(so the sorting might be wrong.\n\n)')
|
|
|
|
ui.warn(msg)
|
|
|
|
ui.warn(hint)
|
2016-09-17 04:22:36 +03:00
|
|
|
results.reverse()
|
|
|
|
|
|
|
|
# indent the top non-public stack
|
|
|
|
if ui.configbool('smartlog', 'indentnonpublic', False):
|
|
|
|
rev, ch, ctx, parents = results[0]
|
|
|
|
if ctx.phase() != phases.public:
|
|
|
|
# find a public parent and add a fake node, so the non-public nodes
|
|
|
|
# will be shown in the non-first column
|
|
|
|
prev = None
|
|
|
|
for i in xrange(1, len(results)):
|
|
|
|
pctx = results[i][2]
|
|
|
|
if pctx.phase() == phases.public:
|
|
|
|
prev = results[i][0]
|
|
|
|
break
|
|
|
|
# append the fake node to occupy the first column
|
|
|
|
if prev:
|
|
|
|
fakerev = rev + 1
|
|
|
|
results.insert(0, (fakerev, 'F', fakectx(fakerev),
|
|
|
|
[('P', prev)]))
|
|
|
|
|
|
|
|
return results
|
2013-06-20 23:16:36 +04:00
|
|
|
|
2015-02-20 03:03:41 +03:00
|
|
|
def _masterrevset(ui, repo, masterstring):
|
2015-02-20 02:34:34 +03:00
|
|
|
"""
|
|
|
|
Try to find the name of ``master`` -- usually a bookmark.
|
|
|
|
|
2017-02-15 04:25:12 +03:00
|
|
|
Defaults to the last public revision, if no suitable local or remote
|
|
|
|
bookmark is found.
|
2015-02-20 02:34:34 +03:00
|
|
|
"""
|
|
|
|
|
2015-02-20 03:03:41 +03:00
|
|
|
if not masterstring:
|
|
|
|
masterstring = ui.config('smartlog', 'master')
|
|
|
|
|
2015-02-20 01:39:54 +03:00
|
|
|
if masterstring:
|
|
|
|
return masterstring
|
|
|
|
|
2015-02-20 02:34:34 +03:00
|
|
|
names = set(bookmarks.bmstore(repo).keys())
|
|
|
|
if util.safehasattr(repo, 'names') and 'remotebookmarks' in repo.names:
|
|
|
|
names.update(set(repo.names['remotebookmarks'].listnames(repo)))
|
2015-02-20 01:39:54 +03:00
|
|
|
|
2015-08-13 00:58:41 +03:00
|
|
|
for name in _reposnames(ui):
|
2015-02-21 02:32:11 +03:00
|
|
|
if name in names:
|
|
|
|
return name
|
2015-02-20 02:34:34 +03:00
|
|
|
|
2017-02-15 04:25:12 +03:00
|
|
|
return 'last(public())'
|
2015-02-20 01:39:54 +03:00
|
|
|
|
2015-08-13 00:58:41 +03:00
|
|
|
def _reposnames(ui):
|
2015-02-21 02:32:11 +03:00
|
|
|
# '' is local repo. This also defines an order precedence for master.
|
2015-08-13 00:58:41 +03:00
|
|
|
repos = ui.configlist('smartlog', 'repos', ['', 'remote/', 'default/'])
|
|
|
|
names = ui.configlist('smartlog', 'names', ['@', 'master', 'stable'])
|
2015-02-21 02:32:11 +03:00
|
|
|
|
|
|
|
for repo in repos:
|
|
|
|
for name in names:
|
|
|
|
yield repo + name
|
|
|
|
|
2015-02-20 03:10:33 +03:00
|
|
|
def _masterrev(repo, masterrevset):
|
2015-02-20 02:49:52 +03:00
|
|
|
try:
|
2015-05-19 03:36:21 +03:00
|
|
|
master = scmutil.revsingle(repo, masterrevset)
|
2015-02-20 02:49:52 +03:00
|
|
|
except error.RepoLookupError:
|
2015-05-19 03:36:21 +03:00
|
|
|
master = scmutil.revsingle(repo, _masterrevset(repo.ui, repo, ''))
|
2017-02-15 04:25:12 +03:00
|
|
|
except error.Abort: # empty revision set
|
|
|
|
return None
|
2015-02-20 02:49:52 +03:00
|
|
|
|
2015-05-19 03:36:21 +03:00
|
|
|
if master:
|
|
|
|
return master.rev()
|
|
|
|
return None
|
2015-02-20 02:49:52 +03:00
|
|
|
|
2015-01-21 23:20:33 +03:00
|
|
|
def smartlogrevset(repo, subset, x):
|
2017-06-15 20:06:09 +03:00
|
|
|
"""``smartlog([master], [recentdays=N])``
|
|
|
|
Changesets relevent to you.
|
|
|
|
|
|
|
|
'master' is the head of the public branch.
|
|
|
|
Unnamed heads will be hidden unless it's within 'recentdays'.
|
2015-01-21 23:20:33 +03:00
|
|
|
"""
|
|
|
|
|
2017-06-15 20:06:09 +03:00
|
|
|
args = revset.getargsdict(x, 'smartlogrevset', 'master recentdays')
|
|
|
|
if 'master' in args:
|
|
|
|
masterstring = revsetlang.getstring(args['master'],
|
|
|
|
_('master must be a string'))
|
2015-01-21 23:20:33 +03:00
|
|
|
else:
|
2015-02-20 03:13:54 +03:00
|
|
|
masterstring = ''
|
2015-01-21 23:20:33 +03:00
|
|
|
|
2017-06-15 20:06:09 +03:00
|
|
|
# TODO(quark): remove this after changing Nuclide's smartlog query
|
|
|
|
if masterstring == 'all':
|
|
|
|
masterstring = ''
|
|
|
|
|
|
|
|
recentdays = revsetlang.getinteger(args.get('recentdays'),
|
|
|
|
_("recentdays should be int"), -1)
|
|
|
|
|
2015-01-21 23:20:33 +03:00
|
|
|
revs = set()
|
|
|
|
heads = set()
|
|
|
|
|
|
|
|
rev = repo.changelog.rev
|
|
|
|
branchinfo = repo.changelog.branchinfo
|
|
|
|
ancestor = repo.changelog.ancestor
|
|
|
|
node = repo.changelog.node
|
|
|
|
parentrevs = repo.changelog.parentrevs
|
|
|
|
|
|
|
|
books = bookmarks.bmstore(repo)
|
2015-01-29 22:32:37 +03:00
|
|
|
ignore = re.compile(repo.ui.config('smartlog',
|
|
|
|
'ignorebookmarks',
|
|
|
|
'!'))
|
2015-01-21 23:20:33 +03:00
|
|
|
for b in books:
|
2015-01-29 22:32:37 +03:00
|
|
|
if not ignore.match(b):
|
|
|
|
heads.add(rev(books[b]))
|
2015-01-21 23:20:33 +03:00
|
|
|
|
2015-02-21 02:32:11 +03:00
|
|
|
# add 'interesting' remote bookmarks as well
|
2015-07-21 21:32:27 +03:00
|
|
|
remotebooks = set()
|
2015-02-21 02:32:11 +03:00
|
|
|
if util.safehasattr(repo, 'names') and 'remotebookmarks' in repo.names:
|
2015-02-25 10:16:06 +03:00
|
|
|
ns = repo.names['remotebookmarks']
|
|
|
|
remotebooks = set(ns.listnames(repo))
|
2015-08-13 00:58:41 +03:00
|
|
|
for name in _reposnames(repo.ui):
|
2015-02-21 02:32:11 +03:00
|
|
|
if name in remotebooks:
|
2015-02-25 10:16:06 +03:00
|
|
|
heads.add(rev(ns.namemap(repo, name)[0]))
|
2015-02-21 02:32:11 +03:00
|
|
|
|
2015-01-21 23:20:33 +03:00
|
|
|
heads.update(repo.revs('.'))
|
|
|
|
|
|
|
|
global hiddenchanges
|
2015-07-21 21:32:27 +03:00
|
|
|
headquery = 'head() & branch(.)'
|
|
|
|
if remotebooks:
|
|
|
|
# When we have remote bookmarks, only show draft heads, since public
|
2016-04-23 00:40:20 +03:00
|
|
|
# heads should have a remote bookmark indicating them. This allows us
|
|
|
|
# to force push server bookmarks to new locations, and not have the
|
|
|
|
# commits clutter the user's smartlog.
|
2016-01-26 01:52:16 +03:00
|
|
|
headquery = 'draft() &' + headquery
|
2015-07-21 21:32:27 +03:00
|
|
|
|
|
|
|
allheads = set(repo.revs(headquery))
|
2017-06-15 20:06:09 +03:00
|
|
|
if recentdays >= 0:
|
|
|
|
recentquery = revsetlang.formatspec('%r & date(-%d)',
|
|
|
|
headquery, recentdays)
|
|
|
|
recentrevs = set(repo.revs(recentquery))
|
|
|
|
hiddenchanges += len(allheads - heads) - len(recentrevs - heads)
|
|
|
|
heads.update(recentrevs)
|
2015-01-21 23:20:33 +03:00
|
|
|
else:
|
2017-06-15 20:06:09 +03:00
|
|
|
heads.update(allheads)
|
2015-01-21 23:20:33 +03:00
|
|
|
|
|
|
|
branches = set()
|
|
|
|
for head in heads:
|
|
|
|
branches.add(branchinfo(head)[0])
|
|
|
|
|
2015-02-20 03:13:54 +03:00
|
|
|
masterrevset = _masterrevset(repo.ui, repo, masterstring)
|
|
|
|
masterrev = _masterrev(repo, masterrevset)
|
2015-01-21 23:20:33 +03:00
|
|
|
|
2017-02-15 04:25:12 +03:00
|
|
|
if masterrev is None:
|
2015-09-16 02:13:13 +03:00
|
|
|
masterbranch = None
|
|
|
|
else:
|
|
|
|
masterbranch = branchinfo(masterrev)[0]
|
2015-01-21 23:20:33 +03:00
|
|
|
|
|
|
|
for branch in branches:
|
|
|
|
if branch != masterbranch:
|
|
|
|
try:
|
2015-02-24 09:54:08 +03:00
|
|
|
rs = 'first(reverse(branch("%s")) & public())' % branch
|
|
|
|
branchmaster = repo.revs(rs).first()
|
|
|
|
if branchmaster is None:
|
|
|
|
# local-only (draft) branch
|
|
|
|
rs = 'branch("%s")' % branch
|
|
|
|
branchmaster = repo.revs(rs).first()
|
2016-04-23 00:40:20 +03:00
|
|
|
except Exception:
|
2015-01-21 23:20:33 +03:00
|
|
|
branchmaster = repo.revs('tip').first()
|
|
|
|
else:
|
2015-02-20 03:13:54 +03:00
|
|
|
branchmaster = masterrev
|
2015-01-21 23:20:33 +03:00
|
|
|
|
|
|
|
# Find ancestors of heads that are not in master
|
|
|
|
# Don't use revsets, they are too slow
|
|
|
|
for head in heads:
|
|
|
|
if branchinfo(head)[0] != branch:
|
|
|
|
continue
|
|
|
|
anc = rev(ancestor(node(head), node(branchmaster)))
|
|
|
|
queue = [head]
|
|
|
|
while queue:
|
|
|
|
current = queue.pop(0)
|
2016-04-23 00:40:20 +03:00
|
|
|
if current not in revs:
|
2015-01-21 23:20:33 +03:00
|
|
|
revs.add(current)
|
|
|
|
if current != anc:
|
|
|
|
parents = parentrevs(current)
|
|
|
|
for p in parents:
|
|
|
|
if p > anc:
|
|
|
|
queue.append(p)
|
|
|
|
|
|
|
|
# add context: master, current commit, and the common ancestor
|
|
|
|
revs.add(branchmaster)
|
|
|
|
|
|
|
|
# get common branch ancestor
|
|
|
|
if branch != masterbranch:
|
|
|
|
anc = None
|
|
|
|
for r in revs:
|
|
|
|
if branchinfo(r)[0] != branch:
|
|
|
|
continue
|
|
|
|
if anc is None:
|
|
|
|
anc = r
|
|
|
|
else:
|
|
|
|
anc = rev(ancestor(node(anc), node(r)))
|
|
|
|
if anc:
|
|
|
|
revs.add(anc)
|
|
|
|
|
|
|
|
return subset & revs
|
|
|
|
|
|
|
|
|
2013-06-20 23:16:36 +04:00
|
|
|
@command('^smartlog|slog', [
|
|
|
|
('', 'template', '', _('display with template'), _('TEMPLATE')),
|
2013-09-06 01:24:01 +04:00
|
|
|
('', 'master', '', _('master bookmark'), ''),
|
2014-04-18 05:49:11 +04:00
|
|
|
('r', 'rev', [], _('show the specified revisions or range'), _('REV')),
|
2016-09-17 01:48:42 +03:00
|
|
|
('', 'all', False, _('don\'t hide old local changesets'), ''),
|
|
|
|
('', 'commit-info', False, _('show changes in current changeset'), ''),
|
2016-12-22 23:07:45 +03:00
|
|
|
] + logopts, _('hg smartlog|slog'))
|
2014-04-18 05:49:11 +04:00
|
|
|
def smartlog(ui, repo, *pats, **opts):
|
2016-09-17 01:48:42 +03:00
|
|
|
'''displays the graph of changesets that are relevant to you
|
2013-09-06 01:24:01 +04:00
|
|
|
|
|
|
|
Includes:
|
|
|
|
|
|
|
|
- Your bookmarks
|
|
|
|
- The @ or master bookmark (or tip if no bookmarks present).
|
2016-09-17 01:48:42 +03:00
|
|
|
- Your local heads that don't have bookmarks.
|
2013-09-06 01:24:01 +04:00
|
|
|
|
|
|
|
Excludes:
|
|
|
|
|
2016-09-17 01:48:42 +03:00
|
|
|
- All changesets under @/master/tip that aren't related to your changesets.
|
|
|
|
- Your local heads that are older than 2 weeks.
|
2013-09-06 01:24:01 +04:00
|
|
|
'''
|
2017-05-26 19:03:58 +03:00
|
|
|
if ui.configbool('smartlog', 'useancestorcache'):
|
|
|
|
with ancestorcache(repo.vfs.join('cache/smartlog-ancestor')):
|
|
|
|
return _smartlog(ui, repo, *pats, **opts)
|
|
|
|
else:
|
|
|
|
return _smartlog(ui, repo, *pats, **opts)
|
|
|
|
|
|
|
|
def _smartlog(ui, repo, *pats, **opts):
|
2015-02-20 03:10:33 +03:00
|
|
|
masterstring = opts.get('master')
|
|
|
|
masterrevset = _masterrevset(ui, repo, masterstring)
|
2013-06-20 23:16:36 +04:00
|
|
|
|
2015-02-11 06:11:19 +03:00
|
|
|
revs = set()
|
2013-06-20 23:16:36 +04:00
|
|
|
|
2015-01-21 23:20:33 +03:00
|
|
|
global hiddenchanges
|
2014-04-18 05:49:11 +04:00
|
|
|
hiddenchanges = 0
|
|
|
|
|
2014-11-11 05:45:39 +03:00
|
|
|
global commit_info
|
|
|
|
commit_info = opts.get('commit_info')
|
|
|
|
|
2014-04-18 05:49:11 +04:00
|
|
|
if not opts.get('rev'):
|
|
|
|
if opts.get('all'):
|
2017-06-15 20:06:09 +03:00
|
|
|
recentdays = -1
|
2013-06-20 23:16:36 +04:00
|
|
|
else:
|
2017-06-15 20:06:09 +03:00
|
|
|
recentdays = 14
|
|
|
|
revstring = revsetlang.formatspec('smartlog(%s, %s)', masterrevset,
|
|
|
|
recentdays)
|
2015-05-19 03:36:21 +03:00
|
|
|
revs.update(scmutil.revrange(repo, [revstring]))
|
2015-02-20 03:10:33 +03:00
|
|
|
masterrev = _masterrev(repo, masterrevset)
|
2014-04-18 05:49:11 +04:00
|
|
|
else:
|
2015-04-30 01:00:31 +03:00
|
|
|
revs.update(scmutil.revrange(repo, opts.get('rev')))
|
2014-04-18 05:49:11 +04:00
|
|
|
try:
|
2015-02-20 03:10:33 +03:00
|
|
|
masterrev = repo.revs('.').first()
|
2014-04-18 05:49:11 +04:00
|
|
|
except error.RepoLookupError:
|
2015-02-20 03:10:33 +03:00
|
|
|
masterrev = revs[0]
|
2013-07-21 07:30:11 +04:00
|
|
|
|
2013-09-06 01:24:01 +04:00
|
|
|
if -1 in revs:
|
|
|
|
revs.remove(-1)
|
|
|
|
|
2015-04-07 23:52:11 +03:00
|
|
|
# It's important that these function caches come after the revsets above,
|
2016-04-23 00:40:20 +03:00
|
|
|
# because the revsets may cause extra nodes to become visible, which in
|
|
|
|
# turn invalidates the changelog instance.
|
2015-04-07 23:52:11 +03:00
|
|
|
rev = repo.changelog.rev
|
|
|
|
ancestor = repo.changelog.ancestor
|
|
|
|
node = repo.changelog.node
|
|
|
|
|
2016-03-31 22:43:16 +03:00
|
|
|
# Find lowest common ancestors of revs. If we have multiple roots in the
|
|
|
|
# repo the following will find one ancestor per group of revs with the
|
|
|
|
# same root.
|
2016-03-31 21:26:15 +03:00
|
|
|
ancestors = set()
|
2013-07-21 07:30:11 +04:00
|
|
|
for r in revs:
|
2016-03-31 21:26:15 +03:00
|
|
|
added = False
|
|
|
|
for anc in list(ancestors):
|
|
|
|
lca = rev(ancestor(node(anc), node(r)))
|
|
|
|
if lca != -1:
|
|
|
|
if anc != lca:
|
|
|
|
ancestors.discard(anc)
|
|
|
|
ancestors.add(lca)
|
|
|
|
added = True
|
|
|
|
|
|
|
|
if not added:
|
|
|
|
ancestors.add(r)
|
|
|
|
|
|
|
|
revs |= ancestors
|
2013-06-20 23:16:36 +04:00
|
|
|
|
|
|
|
revs = sorted(list(revs), reverse=True)
|
|
|
|
|
2014-03-22 03:28:00 +04:00
|
|
|
if len(revs) == 0:
|
|
|
|
return
|
|
|
|
|
2013-06-20 23:16:36 +04:00
|
|
|
# Print it!
|
2017-03-18 05:42:50 +03:00
|
|
|
overrides = {}
|
|
|
|
if ui.config('experimental', 'graphstyle.grandparent') == '|':
|
|
|
|
overrides[('experimental', 'graphstyle.grandparent')] = '2.'
|
|
|
|
with ui.configoverride(overrides, 'smartlog'):
|
2015-02-20 03:10:33 +03:00
|
|
|
revdag = getdag(ui, repo, revs, masterrev)
|
2013-06-21 00:21:06 +04:00
|
|
|
displayer = cmdutil.show_changeset(ui, repo, opts, buffered=True)
|
2016-09-17 02:51:26 +03:00
|
|
|
cmdutil.displaygraph(
|
|
|
|
ui, repo, revdag, displayer, graphmod.asciiedges, None, None)
|
2015-09-14 23:37:45 +03:00
|
|
|
|
2017-03-18 05:42:50 +03:00
|
|
|
try:
|
|
|
|
with open(repo.vfs.join('completionhints'), 'w+') as f:
|
|
|
|
for rev in revdag:
|
|
|
|
commit_hash = rev[2].node()
|
|
|
|
# Skip fakectxt nodes
|
|
|
|
if commit_hash != '...':
|
|
|
|
f.write(nodemod.short(commit_hash) + '\n')
|
|
|
|
except IOError:
|
|
|
|
# No write access. No big deal.
|
|
|
|
pass
|
2014-04-18 05:49:11 +04:00
|
|
|
|
|
|
|
if hiddenchanges:
|
2016-04-23 00:40:20 +03:00
|
|
|
msg = _(
|
|
|
|
"note: hiding %s old heads without bookmarks\n") % hiddenchanges
|
2015-12-29 22:36:15 +03:00
|
|
|
hint = _("(use --all to see them)\n")
|
|
|
|
ui.warn(msg)
|
|
|
|
ui.warn(hint)
|