mirror of
https://github.com/facebook/sapling.git
synced 2024-10-11 01:07:15 +03:00
6015a95c00
Summary: We are seeing perf issues with hidden/obsolete handling. `hg bookmark` is a frequently used command and by making it use the unfiltered repo, it could be 200ms-300ms faster. Test Plan: Added a new test Reviewers: #mercurial, mitrandir, ikostia, ttung Reviewed By: mitrandir, ikostia Subscribers: mitrandir, rmcelroy, akushner, mjpieters Differential Revision: https://phabricator.intern.facebook.com/D3692968 Signature: t1:3692968:1470777864:72ad5d0ffb52ecfcaaa607082693b88319d778fd
741 lines
27 KiB
Python
741 lines
27 KiB
Python
# tweakdefaults.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.
|
|
"""user friendly defaults
|
|
|
|
This extension changes defaults to be more user friendly.
|
|
|
|
hg bookmarks always use unfiltered repo (--hidden)
|
|
hg log always follows history (-f)
|
|
hg rebase aborts without arguments
|
|
hg update aborts without arguments
|
|
aborts if working copy is not clean
|
|
hg branch aborts and encourages use of bookmarks
|
|
hg grep greps the working directory instead of history
|
|
hg histgrep renamed from grep
|
|
|
|
"""
|
|
|
|
from mercurial import util, cmdutil, commands, extensions, hg, scmutil
|
|
from mercurial import bookmarks, obsolete, templater
|
|
from mercurial.extensions import wrapcommand, wrapfunction
|
|
from mercurial import extensions
|
|
from mercurial import error
|
|
from mercurial.i18n import _
|
|
from hgext import rebase
|
|
import errno, inspect, os, re, shlex, stat, subprocess, time
|
|
|
|
cmdtable = {}
|
|
command = cmdutil.command(cmdtable)
|
|
testedwith = 'internal'
|
|
|
|
globaldata = 'globaldata'
|
|
createmarkersoperation = 'createmarkersoperation'
|
|
|
|
logopts = [
|
|
('', 'all', None, _('shows all commits in the repo')),
|
|
]
|
|
|
|
def uisetup(ui):
|
|
tweakorder()
|
|
# if we want to replace command's docstring (not just add stuff to it),
|
|
# we need to do it in uisetup, not extsetup
|
|
commands.table['^annotate|blame'][0].__doc__ = blame.__doc__
|
|
|
|
def extsetup(ui):
|
|
entry = wrapcommand(commands.table, 'update', update)
|
|
options = entry[1]
|
|
# try to put in alphabetical order
|
|
options.insert(3, ('n', 'nocheck', None,
|
|
_('update even with outstanding changes')))
|
|
|
|
wrapblame()
|
|
|
|
entry = wrapcommand(commands.table, 'commit', commitcmd)
|
|
options = entry[1]
|
|
options.insert(9, ('M', 'reuse-message', '',
|
|
_('reuse commit message from REV'), _('REV')))
|
|
opawarerebase = markermetadatawritingcommand(ui, _rebase, 'rebase')
|
|
wrapcommand(rebase.cmdtable, 'rebase', opawarerebase)
|
|
entry = wrapcommand(commands.table, 'pull', pull)
|
|
options = entry[1]
|
|
options.append(
|
|
('d', 'dest', '', _('destination for rebase or update')))
|
|
|
|
try:
|
|
rebaseext = extensions.find('rebase')
|
|
# tweakdefaults is already loaded before other extensions
|
|
# (see tweakorder() function) so if these functions are wrapped
|
|
# by something else, it's not a problem.
|
|
wrapfunction(rebaseext, '_computeobsoletenotrebased',
|
|
_computeobsoletenotrebasedwrapper)
|
|
wrapfunction(rebaseext, '_checkobsrebase',
|
|
_checkobsrebasewrapper)
|
|
except KeyError:
|
|
pass # no rebase, no problem
|
|
except AssertionError:
|
|
msg = _('tweakdefaults: _computeobsoletenotrebased or ' +
|
|
'_checkobsrebase are not what we expect them to be')
|
|
ui.warning(msg)
|
|
|
|
try:
|
|
remotenames = extensions.find('remotenames')
|
|
wrapfunction(remotenames, '_getrebasedest', _getrebasedest)
|
|
except KeyError:
|
|
pass # no remotenames, no worries
|
|
except AttributeError:
|
|
pass # old version of remotenames doh
|
|
|
|
entry = wrapcommand(commands.table, 'log', log)
|
|
for opt in logopts:
|
|
opt = (opt[0], opt[1], opt[2], opt[3])
|
|
entry[1].append(opt)
|
|
|
|
entry = wrapcommand(commands.table, 'branch', branchcmd)
|
|
options = entry[1]
|
|
options.append(('', 'new', None, _('allow branch creation')))
|
|
wrapcommand(commands.table, 'branches', branchescmd)
|
|
|
|
wrapcommand(commands.table, 'merge', mergecmd)
|
|
|
|
entry = wrapcommand(commands.table, 'status', statuscmd)
|
|
options = entry[1]
|
|
options.append(
|
|
('', 'root-relative', None, _('show status relative to root')))
|
|
|
|
wrapcommand(commands.table, 'rollback', rollbackcmd)
|
|
|
|
wrapcommand(commands.table, 'tag', tagcmd)
|
|
wrapcommand(commands.table, 'tags', tagscmd)
|
|
wrapcommand(commands.table, 'graft', graftcmd)
|
|
try:
|
|
fbamendmodule = extensions.find('fbamend')
|
|
opawareamend = markermetadatawritingcommand(ui, amendcmd, 'amend')
|
|
wrapcommand(fbamendmodule.cmdtable, 'amend', opawareamend)
|
|
except KeyError:
|
|
pass
|
|
|
|
# wrapped createmarkers knows how to write operation-aware
|
|
# metadata (e.g. 'amend', 'rebase' and so forth)
|
|
wrapfunction(obsolete, 'createmarkers', _createmarkers)
|
|
|
|
# wrap bookmarks after remotenames
|
|
def afterloaded(loaded):
|
|
if loaded:
|
|
# remotenames is loaded, wrap its wrapper directly
|
|
remotenames = extensions.find('remotenames')
|
|
wrapfunction(remotenames, 'exbookmarks', unfilteredcmd)
|
|
else:
|
|
# otherwise wrap the bookmarks command
|
|
wrapcommand(commands.table, 'bookmarks', unfilteredcmd)
|
|
extensions.afterloaded('remotenames', afterloaded)
|
|
|
|
# Tweak Behavior
|
|
tweakbehaviors(ui)
|
|
|
|
def tweakorder():
|
|
"""
|
|
Tweakdefaults generally should load first; other extensions may modify
|
|
behavior such that tweakdefaults will be happy, so we should not prevent
|
|
that from happening too early in the process. Note that by loading first,
|
|
we ensure that tweakdefaults's function wrappers run *last*.
|
|
|
|
As of this writing, the extensions that we should load before are
|
|
remotenames and directaccess (NB: directaccess messes with order as well).
|
|
"""
|
|
order = extensions._order
|
|
order.remove('tweakdefaults')
|
|
order.insert(0, 'tweakdefaults')
|
|
extensions._order = order
|
|
|
|
# This is an ugly hack
|
|
# The remotenames extension removes the --rebase flag from pull so that the
|
|
# upstream rebase won't rebase to the wrong place. However, we want to allow
|
|
# the user to specify an explicit destination, but still abort if the user
|
|
# specifies dest without update or rebase. Conveniently, _getrebasedest is
|
|
# called before the --rebase flag is stripped from the opts. We will save it
|
|
# when _getrebasedest is called, then look it up in the pull command to do the
|
|
# right thing.
|
|
rebaseflag = False
|
|
rebasedest = None
|
|
|
|
def _getrebasedest(orig, repo, opts):
|
|
"""Use the manually specified destination over the tracking destination"""
|
|
global rebaseflag, rebasedest
|
|
rebaseflag = opts.get('rebase')
|
|
origdest = orig(repo, opts)
|
|
dest = opts.get('dest')
|
|
if not dest:
|
|
dest = origdest
|
|
rebasedest = dest
|
|
return dest
|
|
|
|
def pull(orig, ui, repo, *args, **opts):
|
|
"""pull --rebase/--update are problematic without an explicit destination"""
|
|
try:
|
|
rebasemodule = extensions.find('rebase')
|
|
except KeyError:
|
|
rebasemodule = None
|
|
|
|
rebase = opts.get('rebase')
|
|
update = opts.get('update')
|
|
isrebase = rebase or rebaseflag
|
|
# Only use from the global rebasedest if _getrebasedest was called. If the
|
|
# user isn't using remotenames, then rebasedest isn't set.
|
|
if rebaseflag:
|
|
dest = rebasedest
|
|
else:
|
|
dest = opts.get('dest')
|
|
|
|
if (isrebase or update) and not dest:
|
|
dest = ui.config('tweakdefaults', 'defaultdest')
|
|
|
|
if isrebase and update:
|
|
mess = _('specify either rebase or update, not both')
|
|
raise error.Abort(mess)
|
|
|
|
if dest and not (isrebase or update):
|
|
mess = _('only specify a destination if rebasing or updating')
|
|
raise error.Abort(mess)
|
|
|
|
if (isrebase or update) and not dest:
|
|
rebasemsg = _('you must use a bookmark with tracking '
|
|
'or manually specify a destination for the rebase')
|
|
if isrebase and bmactive(repo):
|
|
rebasehint = _('set up tracking with `hg book -t <destination>` '
|
|
'or manually supply --dest / -d')
|
|
mess = ui.config('tweakdefaults', 'bmnodestmsg', rebasemsg)
|
|
hint = ui.config('tweakdefaults', 'bmnodesthint', _(
|
|
'set up tracking with `hg book -t <destination>` '
|
|
'or manually supply --dest / -d'))
|
|
elif isrebase:
|
|
mess = ui.config('tweakdefaults', 'nodestmsg', rebasemsg)
|
|
hint = ui.config('tweakdefaults', 'nodesthint', _(
|
|
'set up tracking with `hg book <name> -t <destination>` '
|
|
'or manually supply --dest / -d'))
|
|
else: # update
|
|
mess = _('you must specify a destination for the update')
|
|
hint = _('use `hg pull --update --dest <destination>`')
|
|
raise error.Abort(mess, hint=hint)
|
|
|
|
if 'rebase' in opts:
|
|
del opts['rebase']
|
|
if 'update' in opts:
|
|
del opts['update']
|
|
if 'dest' in opts:
|
|
del opts['dest']
|
|
|
|
ret = orig(ui, repo, *args, **opts)
|
|
|
|
# NB: we use rebase and not isrebase on the next line because
|
|
# remotenames may have already handled the rebase.
|
|
if dest and rebase:
|
|
ret = ret or rebasemodule.rebase(ui, repo, dest=dest)
|
|
if dest and update:
|
|
ret = ret or commands.update(ui, repo, node=dest, check=True)
|
|
|
|
return ret
|
|
|
|
|
|
def tweakbehaviors(ui):
|
|
"""Tweak Behaviors
|
|
|
|
Right now this only tweaks the rebase behavior such that the default
|
|
exit status code for a noop rebase becomes 0 instead of 1.
|
|
|
|
In future we may add or modify other behaviours here.
|
|
"""
|
|
|
|
# noop rebase returns 0
|
|
def _nothingtorebase(orig, *args, **kwargs):
|
|
return 0
|
|
|
|
if ui.configbool("tweakdefaults", "nooprebase", True):
|
|
try:
|
|
rebase = extensions.find("rebase")
|
|
extensions.wrapfunction(
|
|
rebase, "_nothingtorebase", _nothingtorebase
|
|
)
|
|
except (KeyError, AttributeError):
|
|
pass
|
|
|
|
def commitcmd(orig, ui, repo, *pats, **opts):
|
|
if (opts.get("amend")
|
|
and not opts.get("date")
|
|
and not ui.configbool('tweakdefaults', 'amendkeepdate')):
|
|
opts["date"] = currentdate()
|
|
|
|
rev = opts.get('reuse_message')
|
|
if rev:
|
|
invalidargs = ['message', 'logfile']
|
|
currentinvalidargs = [ia for ia in invalidargs if opts.get(ia)]
|
|
if currentinvalidargs:
|
|
raise error.Abort(_('--reuse-message and --%s are '
|
|
'mutually exclusive') % (currentinvalidargs[0]))
|
|
|
|
if rev:
|
|
opts['message'] = repo[rev].description()
|
|
|
|
return orig(ui, repo, *pats, **opts)
|
|
|
|
def update(orig, ui, repo, node=None, rev=None, **kwargs):
|
|
# 'hg update' should do nothing
|
|
if not node and not rev and not kwargs['date']:
|
|
raise error.Abort(
|
|
'You must specify a destination to update to,' +
|
|
' for example "hg update master".',
|
|
hint='If you\'re trying to move a bookmark forward, try ' +
|
|
'"hg rebase -d <destination>".')
|
|
|
|
|
|
# By default, never update when there are local changes unless updating to
|
|
# the current rev. This is useful for, eg, arc feature when the only
|
|
# thing changing is the bookmark.
|
|
if not kwargs['clean'] and not kwargs['nocheck']:
|
|
target = node or rev
|
|
if target and scmutil.revsingle(repo, target, target).rev() != \
|
|
repo.revs('.').first():
|
|
kwargs['check'] = True
|
|
|
|
if 'nocheck' in kwargs:
|
|
del kwargs['nocheck']
|
|
|
|
return orig(ui, repo, node=node, rev=rev, **kwargs)
|
|
|
|
def wrapblame():
|
|
entry = wrapcommand(commands.table, 'annotate', blame)
|
|
options = entry[1]
|
|
options.append(('p', 'phabdiff', None, _('list phabricator diff id')))
|
|
|
|
# revision number is no longer default
|
|
nind = next((i for i, o in enumerate(options) if o[0] == 'n'), -1)
|
|
if nind != -1:
|
|
options[nind] = ('n', 'number', None, _('list the revision number'))
|
|
# changeset is default now
|
|
cind = next((i for i, o in enumerate(options) if o[0] == 'c'), -1)
|
|
if cind != -1:
|
|
options[cind] = ('c', 'changeset', None,
|
|
_('list the changeset (default)'))
|
|
|
|
def blame(orig, ui, repo, *pats, **opts):
|
|
"""show changeset information by line for each file
|
|
|
|
List changes in files, showing the changeset responsible for
|
|
each line.
|
|
|
|
This command is useful for discovering when a change was made and
|
|
by whom.
|
|
|
|
If you include -n, changeset gets replaced by revision id, unless
|
|
you also include -c, in which case both are shown. -p on the other
|
|
hand always adds Phabricator Diff Id, not replacing anything with it.
|
|
|
|
Without the -a/--text option, annotate will avoid processing files
|
|
it detects as binary. With -a, annotate will annotate the file
|
|
anyway, although the results will probably be neither useful
|
|
nor desirable.
|
|
|
|
Returns 0 on success.
|
|
"""
|
|
def phabdiff(context, mapping, args):
|
|
"""Fetch the Phab Diff Id from the node in mapping"""
|
|
res = ' ' * 8
|
|
try:
|
|
d = repo[mapping['node']].description()
|
|
pat = 'https://.*/(D\d+)'
|
|
m = re.search(pat, d)
|
|
res = m.group(1) if m else ''
|
|
except Exception:
|
|
pass
|
|
return res
|
|
|
|
if ui.plain():
|
|
return orig(ui, repo, *pats, **opts)
|
|
|
|
# We want to register template `blame_phabdiffid` function
|
|
# just for this call so that we don't pollute the function
|
|
# namespaces for other commandss. The reason is mainly
|
|
# because `blame_phabdiffid` relies on 'node' value being
|
|
# present in the `mapping` argument passed to it (which
|
|
# would not necessarily be the case for other non-log-like
|
|
# commands.
|
|
# tl/dr: this is a dirty hack and we want to have is as
|
|
# restricted as possible
|
|
templater.funcs['blame_phabdiffid'] = phabdiff
|
|
tmpl = "{short(node)}:"
|
|
if opts.get('number'):
|
|
# user expressed explicit desire to see revisions
|
|
# padding is really big here, but hopefully people
|
|
# don't want to see revisions too much
|
|
revtmpl = "{pad(rev, 9)}:"
|
|
if opts.get('changeset'):
|
|
tmpl += revtmpl
|
|
else:
|
|
tmpl = revtmpl
|
|
if opts.get('phabdiff'):
|
|
del opts['phabdiff']
|
|
tmpl += "{pad(blame_phabdiffid(), 8)}:"
|
|
if opts.get('user'):
|
|
tmpl += "{pad(user|emailuser, 13, ' ', True)}:"
|
|
if opts.get('date'):
|
|
tmpl += "{pad(date|rfc822date, 12)}:"
|
|
if opts.get('file'):
|
|
tmpl += "{file}:"
|
|
if opts.get('line_number'):
|
|
tmpl += "{pad(line_number, 5, ' ', True)}:"
|
|
if not opts.get('template'):
|
|
opts['template'] = tmpl + "{line}"
|
|
# this forces original blame to provide necessary node hash
|
|
# that we can reuse to parse Phabricator Diff id
|
|
opts['changeset'] = True
|
|
result = orig(ui, repo, *pats, **opts)
|
|
del templater.funcs['blame_phabdiffid']
|
|
return result
|
|
|
|
@command('histgrep', commands.table['grep'][1], commands.table['grep'][2])
|
|
def histgrep(ui, repo, pattern, *pats, **opts):
|
|
"""search for a pattern in specified files and revisions
|
|
|
|
Search revisions of files for a regular expression.
|
|
|
|
The command used to be hg grep.
|
|
|
|
This command behaves differently than Unix grep. It only accepts
|
|
Python/Perl regexps. It searches repository history, not the working
|
|
directory. It always prints the revision number in which a match appears.
|
|
|
|
By default, grep only prints output for the first revision of a file in
|
|
which it finds a match. To get it to print every revision that contains a
|
|
change in match status ("-" for a match that becomes a non-match, or "+"
|
|
for a non-match that becomes a match), use the --all flag.
|
|
|
|
Returns 0 if a match is found, 1 otherwise."""
|
|
if not pats and not ui.configbool("tweakdefaults", "allowfullrepohistgrep"):
|
|
m = _("can't run histgrep on the whole repo, please provide filenames")
|
|
h = _('this is disabled to avoid very slow greps over the whole repo')
|
|
raise error.Abort(m, hint=h)
|
|
|
|
return commands.grep(ui, repo, pattern, *pats, **opts)
|
|
|
|
del commands.table['grep']
|
|
@command('grep',
|
|
[('A', 'after-context', '', 'print NUM lines of trailing context', 'NUM'),
|
|
('B', 'before-context', '', 'print NUM lines of leading context', 'NUM'),
|
|
('C', 'context', '', 'print NUM lines of output context', 'NUM'),
|
|
('i', 'ignore-case', None, 'ignore case when matching'),
|
|
('l', 'files-with-matches', None, 'print only filenames that match'),
|
|
('n', 'line-number', None, 'print matching line numbers'),
|
|
('V', 'invert-match', None, 'select non-matching lines'),
|
|
('w', 'word-regexp', None, 'match whole words only'),
|
|
('E', 'extended-regexp', None, 'use POSIX extended regexps'),
|
|
('F', 'fixed-strings', None, 'interpret pattern as fixed string'),
|
|
('P', 'perl-regexp', None, 'use Perl-compatible regexps'),
|
|
], '[OPTION]... PATTERN [FILE]...',
|
|
inferrepo=True)
|
|
def grep(ui, repo, pattern, *pats, **opts):
|
|
"""search for a pattern in tracked files in the working directory
|
|
|
|
The default regexp style is POSIX basic regexps. If no FILE parameters are
|
|
passed in, the current directory and its subdirectories will be searched.
|
|
|
|
For the old 'hg grep', see 'histgrep'."""
|
|
|
|
grepcommandstr = ui.config('grep', 'command', default='grep')
|
|
# Use shlex.split() to split up grepcommandstr into multiple arguments.
|
|
# this allows users to specify a command plus arguments (e.g., "grep -i").
|
|
# We don't use a real shell to execute this, which ensures we won't do
|
|
# bad stuff if their command includes redirects, semicolons, or other
|
|
# special characters etc.
|
|
cmd = ['xargs', '-0'] + shlex.split(grepcommandstr) + [
|
|
'--no-messages',
|
|
'--binary-files=without-match',
|
|
'--with-filename',
|
|
'--regexp=' + pattern,
|
|
]
|
|
|
|
if opts.get('after_context'):
|
|
cmd.append('-A')
|
|
cmd.append(opts.get('after_context'))
|
|
if opts.get('before_context'):
|
|
cmd.append('-B')
|
|
cmd.append(opts.get('before_context'))
|
|
if opts.get('context'):
|
|
cmd.append('-C')
|
|
cmd.append(opts.get('context'))
|
|
if opts.get('ignore_case'):
|
|
cmd.append('-i')
|
|
if opts.get('files_with_matches'):
|
|
cmd.append('-l')
|
|
if opts.get('line_number'):
|
|
cmd.append('-n')
|
|
if opts.get('invert_match'):
|
|
cmd.append('-v')
|
|
if opts.get('word_regexp'):
|
|
cmd.append('-w')
|
|
if opts.get('extended_regexp'):
|
|
cmd.append('-E')
|
|
if opts.get('fixed_strings'):
|
|
cmd.append('-F')
|
|
if opts.get('perl_regexp'):
|
|
cmd.append('-P')
|
|
|
|
# color support, using the color extension
|
|
colormode = getattr(ui, '_colormode', '')
|
|
if colormode == 'ansi':
|
|
cmd.append('--color=always')
|
|
|
|
wctx = repo[None]
|
|
if not pats:
|
|
# Search everything in the current directory
|
|
m = scmutil.match(wctx, ['.'])
|
|
else:
|
|
# Search using the specified patterns
|
|
m = scmutil.match(wctx, pats)
|
|
|
|
# Add '--' to make sure grep recognizes all remaining arguments
|
|
# (passed in by xargs) as filenames.
|
|
cmd.append('--')
|
|
p = subprocess.Popen(cmd, bufsize=-1, close_fds=util.closefds,
|
|
stdin=subprocess.PIPE)
|
|
|
|
write = p.stdin.write
|
|
ds = repo.dirstate
|
|
getkind = stat.S_IFMT
|
|
lnkkind = stat.S_IFLNK
|
|
for f in wctx.matches(m):
|
|
# skip symlinks and removed files
|
|
t = ds._map[f]
|
|
if t[0] == 'r' or getkind(t[1]) == lnkkind:
|
|
continue
|
|
write(m.rel(f) + '\0')
|
|
|
|
p.stdin.close()
|
|
return p.wait()
|
|
|
|
def markermetadatawritingcommand(ui, origcmd, operationame):
|
|
"""Wrap origcmd in a context where globaldata config contains
|
|
the name of current operation so that any function up the call
|
|
stack can query for this value:
|
|
`repo.ui.config(globaldata, createmarkersoperation)`
|
|
|
|
In particular, we want `obsolete.createmarkers` to know whether
|
|
top-level scenario is amend, rebase or something else so that
|
|
it can write these values into marker metadata.
|
|
"""
|
|
origargs = inspect.getargspec(origcmd)
|
|
try:
|
|
repo_index = origargs.args.index('repo')
|
|
except ValueError:
|
|
ui.warn(_("cannot wrap a command that does not have repo argument"))
|
|
return origcmd
|
|
|
|
def cmd(*args, **kwargs):
|
|
repo = args[repo_index]
|
|
backupconfig = repo.ui.backupconfig(globaldata, createmarkersoperation)
|
|
repo.ui.setconfig(globaldata, createmarkersoperation, operationame)
|
|
try:
|
|
return origcmd(*args, **kwargs)
|
|
finally:
|
|
repo.ui.restoreconfig(backupconfig)
|
|
return cmd
|
|
|
|
def _rebase(orig, ui, repo, **opts):
|
|
if not opts.get('date') and not ui.configbool('tweakdefaults',
|
|
'rebasekeepdate'):
|
|
opts['date'] = currentdate()
|
|
|
|
if opts.get('continue') or opts.get('abort'):
|
|
return orig(ui, repo, **opts)
|
|
|
|
# 'hg rebase' w/o args should do nothing
|
|
if not opts.get('dest'):
|
|
raise error.Abort("you must specify a destination (-d) for the rebase")
|
|
|
|
# 'hg rebase' can fast-forward bookmark
|
|
prev = repo['.']
|
|
dest = scmutil.revsingle(repo, opts.get('dest'))
|
|
|
|
# Only fast-forward the bookmark if no source nodes were explicitly
|
|
# specified.
|
|
if not (opts.get('base') or opts.get('source') or opts.get('rev')):
|
|
common = dest.ancestor(prev)
|
|
if prev == common:
|
|
result = hg.update(repo, dest.node())
|
|
if bmactive(repo):
|
|
bookmarks.update(repo, [prev.node()], dest.node())
|
|
return result
|
|
|
|
return orig(ui, repo, **opts)
|
|
|
|
def _computeobsoletenotrebasedwrapper(orig, repo, rebaseobsrevs, dest):
|
|
"""Wrapper for _computeobsoletenotrebased from rebase extensions
|
|
|
|
Unlike upstream rebase, we don't want to skip purely pruned commits.
|
|
We also want to explain why some particular commit was skipped."""
|
|
res = orig(repo, rebaseobsrevs, dest)
|
|
for key in res.keys():
|
|
if res[key] is None:
|
|
# key => None is a sign of a pruned commit
|
|
del res[key]
|
|
return res
|
|
|
|
def _checkobsrebasewrapper(orig, repo, ui, *args):
|
|
cfgbackup = repo.ui.backupconfig('experimental', 'allowdivergence')
|
|
try:
|
|
extensions.find('inhibit')
|
|
repo.ui.setconfig('experimental', 'allowdivergence', 'on',
|
|
'tweakdefaults')
|
|
except KeyError:
|
|
pass
|
|
finally:
|
|
orig(repo, ui, *args)
|
|
repo.ui.restoreconfig(cfgbackup)
|
|
|
|
def currentdate():
|
|
return "%d %d" % util.makedate(time.time())
|
|
|
|
def graftcmd(orig, ui, repo, *revs, **opts):
|
|
if not opts.get("date") and not ui.configbool('tweakdefaults',
|
|
'graftkeepdate'):
|
|
opts["date"] = currentdate()
|
|
return orig(ui, repo, *revs, **opts)
|
|
|
|
def amendcmd(orig, ui, repo, *pats, **opts):
|
|
if not opts.get("date") and not ui.configbool('tweakdefaults',
|
|
'amendkeepdate'):
|
|
opts["date"] = currentdate()
|
|
return orig(ui, repo, *pats, **opts)
|
|
|
|
def log(orig, ui, repo, *pats, **opts):
|
|
# 'hg log' defaults to -f
|
|
# All special uses of log (--date, --branch, etc) will also now do follow.
|
|
if not opts.get('rev') and not opts.get('all'):
|
|
opts['follow'] = True
|
|
|
|
return orig(ui, repo, *pats, **opts)
|
|
|
|
def branchcmd(orig, ui, repo, label=None, **opts):
|
|
message = ui.config('tweakdefaults', 'branchmessage',
|
|
_('new named branches are disabled in this repository'))
|
|
enabled = ui.configbool('tweakdefaults', 'allowbranch', True)
|
|
if (enabled and opts.get('new')) or label is None:
|
|
if 'new' in opts:
|
|
del opts['new']
|
|
return orig(ui, repo, label, **opts)
|
|
elif enabled:
|
|
raise error.Abort(
|
|
_('do not use branches; use bookmarks instead'),
|
|
hint=_('use --new if you are certain you want a branch'))
|
|
else:
|
|
raise error.Abort(message)
|
|
|
|
def branchescmd(orig, ui, repo, active, closed, **opts):
|
|
message = ui.config('tweakdefaults', 'branchesmessage')
|
|
if message:
|
|
ui.warn(message + '\n')
|
|
return orig(ui, repo, active, closed, **opts)
|
|
|
|
def mergecmd(orig, ui, repo, node=None, **opts):
|
|
"""
|
|
Allowing to disable merges
|
|
"""
|
|
if ui.configbool('tweakdefaults','allowmerge', True):
|
|
return orig(ui, repo, node, **opts)
|
|
else:
|
|
message = ui.config('tweakdefaults', 'mergemessage',
|
|
_('merging is not supported for this repository'))
|
|
hint = ui.config('tweakdefaults', 'mergehint', _('use rebase instead'))
|
|
raise error.Abort(message, hint=hint)
|
|
|
|
def statuscmd(orig, ui, repo, *pats, **opts):
|
|
"""
|
|
Make status relative by default for interactive usage
|
|
"""
|
|
if opts.get('root_relative'):
|
|
del opts['root_relative']
|
|
if pats:
|
|
# Ugh. So, if people pass a pattern and --root-relative,
|
|
# they will get pattern behavior and not any root-relative paths,
|
|
# because that's how hg status works. It's non-trivial to fixup
|
|
# either all the patterns or all the output, so we just raise
|
|
# an exception instead.
|
|
message = _('--root-relative not supported with patterns')
|
|
hint = _('run from the repo root instead')
|
|
raise error.Abort(message, hint=hint)
|
|
elif os.environ.get('HGPLAIN'): # don't break automation
|
|
pass
|
|
# Here's an ugly hack! If users are passing "re:" to make status relative,
|
|
# hgwatchman will never refresh the full state and status will become and
|
|
# remain slow after a restart or 24 hours. Here, we check for this and
|
|
# replace 're:' with '' which has the same semantic effect but works for
|
|
# hgwatchman (because match.always() == True), if and only if 're:' is the
|
|
# only pattern passed.
|
|
#
|
|
# Also set pats to [''] if pats is empty because that makes status relative.
|
|
elif not pats or (len(pats) == 1 and pats[0] == 're:'):
|
|
pats = ['']
|
|
return orig(ui, repo, *pats, **opts)
|
|
|
|
def rollbackcmd(orig, ui, repo, **opts):
|
|
"""
|
|
Allowing to disable the rollback command
|
|
"""
|
|
if ui.configbool('tweakdefaults', 'allowrollback', True):
|
|
return orig(ui, repo, **opts)
|
|
else:
|
|
message = ui.config('tweakdefaults', 'rollbackmessage',
|
|
_('the use of rollback is disabled'))
|
|
hint = ui.config('tweakdefaults', 'rollbackhint', None)
|
|
raise error.Abort(message, hint=hint)
|
|
|
|
def tagcmd(orig, ui, repo, name1, *names, **opts):
|
|
"""
|
|
Allowing to disable tags
|
|
"""
|
|
message = ui.config('tweakdefaults', 'tagmessage',
|
|
_('new tags are disabled in this repository'))
|
|
if ui.configbool('tweakdefaults', 'allowtags', True):
|
|
return orig(ui, repo, name1, *names, **opts)
|
|
else:
|
|
raise error.Abort(message)
|
|
|
|
def tagscmd(orig, ui, repo, **opts):
|
|
message = ui.config('tweakdefaults', 'tagsmessage', '')
|
|
if message:
|
|
ui.warn(message + '\n')
|
|
return orig(ui, repo, **opts)
|
|
|
|
def unfilteredcmd(orig, *args, **opts):
|
|
# use unfiltered repo for performance
|
|
#
|
|
# find the "repo" arg and change it to the unfiltered version.
|
|
# "repo" could in different location, for example:
|
|
# args = [ui, repo, ...] for commands.bookmark
|
|
# args = [orig, ui, repo, ...] for remotenames.exbookmarks
|
|
for i in [1, 2]:
|
|
if len(args) > i and util.safehasattr(args[i], 'unfiltered'):
|
|
args = list(args)
|
|
args[i] = args[i].unfiltered()
|
|
args = tuple(args)
|
|
return orig(*args, **opts)
|
|
|
|
### bookmarks api compatibility layer ###
|
|
def bmactive(repo):
|
|
try:
|
|
return repo._activebookmark
|
|
except AttributeError:
|
|
return repo._bookmarkcurrent
|
|
|
|
def _createmarkers(orig, repo, relations, flag=0, date=None, metadata=None):
|
|
operation = repo.ui.config(globaldata, createmarkersoperation, None)
|
|
if operation is None:
|
|
return orig(repo, relations, flag, date, metadata)
|
|
|
|
if metadata is None:
|
|
metadata = {}
|
|
metadata['operation'] = operation
|
|
return orig(repo, relations, flag, date, metadata)
|