summaryrefslogtreecommitdiff
path: root/mercurial/cmdutil.py
diff options
context:
space:
mode:
Diffstat (limited to 'mercurial/cmdutil.py')
-rw-r--r--mercurial/cmdutil.py789
1 files changed, 51 insertions, 738 deletions
diff --git a/mercurial/cmdutil.py b/mercurial/cmdutil.py
index 7ccbb62..8f9a9a7 100644
--- a/mercurial/cmdutil.py
+++ b/mercurial/cmdutil.py
@@ -8,9 +8,9 @@
from node import hex, nullid, nullrev, short
from i18n import _
import os, sys, errno, re, tempfile
-import util, scmutil, templater, patch, error, templatekw, revlog, copies
+import util, scmutil, templater, patch, error, templatekw, revlog
import match as matchmod
-import subrepo, context, repair, bookmarks, graphmod, revset
+import subrepo
def parsealiases(cmd):
return cmd.lstrip("^").split("|")
@@ -23,14 +23,7 @@ def findpossible(cmd, table, strict=False):
"""
choice = {}
debugchoice = {}
-
- if cmd in table:
- # short-circuit exact matches, "log" alias beats "^log|history"
- keys = [cmd]
- else:
- keys = table.keys()
-
- for e in keys:
+ for e in table.keys():
aliases = parsealiases(e)
found = None
if cmd in aliases:
@@ -82,10 +75,6 @@ def bailifchanged(repo):
modified, added, removed, deleted = repo.status()[:4]
if modified or added or removed or deleted:
raise util.Abort(_("outstanding uncommitted changes"))
- ctx = repo[None]
- for s in ctx.substate:
- if ctx.sub(s).dirty():
- raise util.Abort(_("uncommitted changes in subrepo %s") % s)
def logmessage(ui, opts):
""" get the log message according to -m and -l option """
@@ -120,13 +109,12 @@ def loglimit(opts):
limit = None
return limit
-def makefilename(repo, pat, node, desc=None,
+def makefilename(repo, pat, node,
total=None, seqno=None, revwidth=None, pathname=None):
node_expander = {
'H': lambda: hex(node),
'R': lambda: str(repo.changelog.rev(node)),
'h': lambda: short(node),
- 'm': lambda: re.sub('[^\w]', '_', str(desc))
}
expander = {
'%': lambda: '%',
@@ -166,14 +154,14 @@ def makefilename(repo, pat, node, desc=None,
raise util.Abort(_("invalid format spec '%%%s' in output filename") %
inst.args[0])
-def makefileobj(repo, pat, node=None, desc=None, total=None,
+def makefileobj(repo, pat, node=None, total=None,
seqno=None, revwidth=None, mode='wb', pathname=None):
writable = mode not in ('r', 'rb')
if not pat or pat == '-':
fp = writable and repo.ui.fout or repo.ui.fin
- if util.safehasattr(fp, 'fileno'):
+ if hasattr(fp, 'fileno'):
return os.fdopen(os.dup(fp.fileno()), mode)
else:
# if this fp can't be duped properly, return
@@ -189,11 +177,11 @@ def makefileobj(repo, pat, node=None, desc=None, total=None,
return getattr(self.f, attr)
return wrappedfileobj(fp)
- if util.safehasattr(pat, 'write') and writable:
+ if hasattr(pat, 'write') and writable:
return pat
- if util.safehasattr(pat, 'read') and 'r' in mode:
+ if hasattr(pat, 'read') and 'r' in mode:
return pat
- return open(makefilename(repo, pat, node, desc, total, seqno, revwidth,
+ return open(makefilename(repo, pat, node, total, seqno, revwidth,
pathname),
mode)
@@ -268,11 +256,6 @@ def copy(ui, repo, pats, opts, rename=False):
# otarget: ossep
def copyfile(abssrc, relsrc, otarget, exact):
abstarget = scmutil.canonpath(repo.root, cwd, otarget)
- if '/' in abstarget:
- # We cannot normalize abstarget itself, this would prevent
- # case only renames, like a => A.
- abspath, absname = abstarget.rsplit('/', 1)
- abstarget = repo.dirstate.normalize(abspath) + '/' + absname
reltarget = repo.pathto(abstarget, cwd)
target = repo.wjoin(abstarget)
src = repo.wjoin(abssrc)
@@ -290,16 +273,6 @@ def copy(ui, repo, pats, opts, rename=False):
# check for overwrites
exists = os.path.lexists(target)
- samefile = False
- if exists and abssrc != abstarget:
- if (repo.dirstate.normalize(abssrc) ==
- repo.dirstate.normalize(abstarget)):
- if not rename:
- ui.warn(_("%s: can't copy - same file\n") % reltarget)
- return
- exists = False
- samefile = True
-
if not after and exists or after and state in 'mn':
if not opts['force']:
ui.warn(_('%s: not overwriting - file exists\n') %
@@ -322,12 +295,7 @@ def copy(ui, repo, pats, opts, rename=False):
targetdir = os.path.dirname(target) or '.'
if not os.path.isdir(targetdir):
os.makedirs(targetdir)
- if samefile:
- tmp = target + "~hgrename"
- os.rename(src, tmp)
- os.rename(tmp, target)
- else:
- util.copyfile(src, target)
+ util.copyfile(src, target)
srcexists = True
except IOError, inst:
if inst.errno == errno.ENOENT:
@@ -350,7 +318,7 @@ def copy(ui, repo, pats, opts, rename=False):
scmutil.dirstatecopy(ui, repo, wctx, abssrc, abstarget,
dryrun=dryrun, cwd=cwd)
if rename and not dryrun:
- if not after and srcexists and not samefile:
+ if not after and srcexists:
util.unlinkpath(repo.wjoin(abssrc))
wctx.forget([abssrc])
@@ -548,13 +516,11 @@ def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
shouldclose = False
if not fp:
- desc_lines = ctx.description().rstrip().split('\n')
- desc = desc_lines[0] #Commit always has a first line.
- fp = makefileobj(repo, template, node, desc=desc, total=total,
- seqno=seqno, revwidth=revwidth, mode='ab')
+ fp = makefileobj(repo, template, node, total=total, seqno=seqno,
+ revwidth=revwidth, mode='ab')
if fp != template:
shouldclose = True
- if fp != sys.stdout and util.safehasattr(fp, 'name'):
+ if fp != sys.stdout and hasattr(fp, 'name'):
repo.ui.note("%s\n" % fp.name)
fp.write("# HG changeset patch\n")
@@ -608,17 +574,10 @@ def diffordiffstat(ui, repo, diffopts, node1, node2, match,
ctx1 = repo[node1]
ctx2 = repo[node2]
for subpath, sub in subrepo.itersubrepos(ctx1, ctx2):
- tempnode2 = node2
- try:
- if node2 is not None:
- tempnode2 = ctx2.substate[subpath][1]
- except KeyError:
- # A subrepo that existed in node1 was deleted between node1 and
- # node2 (inclusive). Thus, ctx2's substate won't contain that
- # subpath. The best we can do is to ignore it.
- tempnode2 = None
+ if node2 is not None:
+ node2 = ctx2.substate[subpath][1]
submatch = matchmod.narrowmatcher(subpath, match)
- sub.diff(diffopts, tempnode2, submatch, changes=changes,
+ sub.diff(diffopts, node2, submatch, changes=changes,
stat=stat, fp=fp, prefix=prefix)
class changeset_printer(object):
@@ -692,9 +651,6 @@ class changeset_printer(object):
for tag in self.repo.nodetags(changenode):
self.ui.write(_("tag: %s\n") % tag,
label='log.tag')
- if self.ui.debugflag and ctx.phase():
- self.ui.write(_("phase: %s\n") % _(ctx.phasestr()),
- label='log.phase')
for parent in parents:
self.ui.write(_("parent: %d:%s\n") % parent,
label='log.parent')
@@ -910,10 +866,7 @@ def show_changeset(ui, repo, opts, buffered=False):
if not (tmpl or style):
tmpl = ui.config('ui', 'logtemplate')
if tmpl:
- try:
- tmpl = templater.parsestring(tmpl)
- except SyntaxError:
- tmpl = templater.parsestring(tmpl, quoted=False)
+ tmpl = templater.parsestring(tmpl)
else:
style = util.expandpath(ui.config('ui', 'style', ''))
@@ -952,26 +905,12 @@ def finddate(ui, repo, date):
for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
rev = ctx.rev()
if rev in results:
- ui.status(_("found revision %s from %s\n") %
+ ui.status(_("Found revision %s from %s\n") %
(rev, util.datestr(results[rev])))
return str(rev)
raise util.Abort(_("revision matching date not found"))
-def increasingwindows(start, end, windowsize=8, sizelimit=512):
- if start < end:
- while start < end:
- yield start, min(windowsize, end - start)
- start += windowsize
- if windowsize < sizelimit:
- windowsize *= 2
- else:
- while start > end:
- yield start, min(windowsize, start - end - 1)
- start -= windowsize
- if windowsize < sizelimit:
- windowsize *= 2
-
def walkchangerevs(repo, match, opts, prepare):
'''Iterate over files and the revs in which they changed.
@@ -987,6 +926,20 @@ def walkchangerevs(repo, match, opts, prepare):
yielding each context, the iterator will first call the prepare
function on each context in the window in forward order.'''
+ def increasing_windows(start, end, windowsize=8, sizelimit=512):
+ if start < end:
+ while start < end:
+ yield start, min(windowsize, end - start)
+ start += windowsize
+ if windowsize < sizelimit:
+ windowsize *= 2
+ else:
+ while start > end:
+ yield start, min(windowsize, start - end - 1)
+ start -= windowsize
+ if windowsize < sizelimit:
+ windowsize *= 2
+
follow = opts.get('follow') or opts.get('follow_first')
if not len(repo):
@@ -996,13 +949,13 @@ def walkchangerevs(repo, match, opts, prepare):
defrange = '%s:0' % repo['.'].rev()
else:
defrange = '-1:0'
- revs = scmutil.revrange(repo, opts.get('rev') or [defrange])
+ revs = scmutil.revrange(repo, opts['rev'] or [defrange])
if not revs:
return []
wanted = set()
slowpath = match.anypats() or (match.files() and opts.get('removed'))
fncache = {}
- change = repo.changectx
+ change = util.cachefunc(repo.changectx)
# First step is to fill wanted, the set of revisions that we want to yield.
# When it does not induce extra cost, we also fill fncache for revisions in
@@ -1014,7 +967,7 @@ def walkchangerevs(repo, match, opts, prepare):
wanted = set(revs)
copies = []
- if not slowpath and match.files():
+ if not slowpath:
# We only have to read through the filelog to find wanted revisions
minrev, maxrev = min(revs), max(revs)
@@ -1047,15 +1000,8 @@ def walkchangerevs(repo, match, opts, prepare):
return reversed(revs)
def iterfiles():
- pctx = repo['.']
for filename in match.files():
- if follow:
- if filename not in pctx:
- raise util.Abort(_('cannot follow file not in parent '
- 'revision: "%s"') % filename)
- yield filename, pctx[filename].filenode()
- else:
- yield filename, None
+ yield filename, None
for filename_node in copies:
yield filename_node
for file_, node in iterfiles():
@@ -1158,7 +1104,7 @@ def walkchangerevs(repo, match, opts, prepare):
# it might be worthwhile to do this in the iterator if the rev range
# is descending and the prune args are all within that range
for rev in opts.get('prune', ()):
- rev = repo[rev].rev()
+ rev = repo.changelog.rev(repo.lookup(rev))
ff = followfilter()
stop = min(revs[0], revs[-1])
for x in xrange(rev, stop - 1, -1):
@@ -1176,7 +1122,7 @@ def walkchangerevs(repo, match, opts, prepare):
def want(rev):
return rev in wanted
- for i, window in increasingwindows(0, len(revs)):
+ for i, window in increasing_windows(0, len(revs)):
nrevs = [rev for rev in revs[i:i + window] if want(rev)]
for rev in sorted(nrevs):
fns = fncache.get(rev)
@@ -1192,278 +1138,7 @@ def walkchangerevs(repo, match, opts, prepare):
yield change(rev)
return iterate()
-def _makegraphfilematcher(repo, pats, followfirst):
- # When displaying a revision with --patch --follow FILE, we have
- # to know which file of the revision must be diffed. With
- # --follow, we want the names of the ancestors of FILE in the
- # revision, stored in "fcache". "fcache" is populated by
- # reproducing the graph traversal already done by --follow revset
- # and relating linkrevs to file names (which is not "correct" but
- # good enough).
- fcache = {}
- fcacheready = [False]
- pctx = repo['.']
- wctx = repo[None]
-
- def populate():
- for fn in pats:
- for i in ((pctx[fn],), pctx[fn].ancestors(followfirst=followfirst)):
- for c in i:
- fcache.setdefault(c.linkrev(), set()).add(c.path())
-
- def filematcher(rev):
- if not fcacheready[0]:
- # Lazy initialization
- fcacheready[0] = True
- populate()
- return scmutil.match(wctx, fcache.get(rev, []), default='path')
-
- return filematcher
-
-def _makegraphlogrevset(repo, pats, opts, revs):
- """Return (expr, filematcher) where expr is a revset string built
- from log options and file patterns or None. If --stat or --patch
- are not passed filematcher is None. Otherwise it is a callable
- taking a revision number and returning a match objects filtering
- the files to be detailed when displaying the revision.
- """
- opt2revset = {
- 'no_merges': ('not merge()', None),
- 'only_merges': ('merge()', None),
- '_ancestors': ('ancestors(%(val)s)', None),
- '_fancestors': ('_firstancestors(%(val)s)', None),
- '_descendants': ('descendants(%(val)s)', None),
- '_fdescendants': ('_firstdescendants(%(val)s)', None),
- '_matchfiles': ('_matchfiles(%(val)s)', None),
- 'date': ('date(%(val)r)', None),
- 'branch': ('branch(%(val)r)', ' or '),
- '_patslog': ('filelog(%(val)r)', ' or '),
- '_patsfollow': ('follow(%(val)r)', ' or '),
- '_patsfollowfirst': ('_followfirst(%(val)r)', ' or '),
- 'keyword': ('keyword(%(val)r)', ' or '),
- 'prune': ('not (%(val)r or ancestors(%(val)r))', ' and '),
- 'user': ('user(%(val)r)', ' or '),
- }
-
- opts = dict(opts)
- # follow or not follow?
- follow = opts.get('follow') or opts.get('follow_first')
- followfirst = opts.get('follow_first') and 1 or 0
- # --follow with FILE behaviour depends on revs...
- startrev = revs[0]
- followdescendants = (len(revs) > 1 and revs[0] < revs[1]) and 1 or 0
-
- # branch and only_branch are really aliases and must be handled at
- # the same time
- opts['branch'] = opts.get('branch', []) + opts.get('only_branch', [])
- opts['branch'] = [repo.lookupbranch(b) for b in opts['branch']]
- # pats/include/exclude are passed to match.match() directly in
- # _matchfile() revset but walkchangerevs() builds its matcher with
- # scmutil.match(). The difference is input pats are globbed on
- # platforms without shell expansion (windows).
- pctx = repo[None]
- match, pats = scmutil.matchandpats(pctx, pats, opts)
- slowpath = match.anypats() or (match.files() and opts.get('removed'))
- if not slowpath:
- for f in match.files():
- if follow and f not in pctx:
- raise util.Abort(_('cannot follow file not in parent '
- 'revision: "%s"') % f)
- filelog = repo.file(f)
- if not len(filelog):
- # A zero count may be a directory or deleted file, so
- # try to find matching entries on the slow path.
- if follow:
- raise util.Abort(
- _('cannot follow nonexistent file: "%s"') % f)
- slowpath = True
- if slowpath:
- # See walkchangerevs() slow path.
- #
- if follow:
- raise util.Abort(_('can only follow copies/renames for explicit '
- 'filenames'))
- # pats/include/exclude cannot be represented as separate
- # revset expressions as their filtering logic applies at file
- # level. For instance "-I a -X a" matches a revision touching
- # "a" and "b" while "file(a) and not file(b)" does
- # not. Besides, filesets are evaluated against the working
- # directory.
- matchargs = ['r:', 'd:relpath']
- for p in pats:
- matchargs.append('p:' + p)
- for p in opts.get('include', []):
- matchargs.append('i:' + p)
- for p in opts.get('exclude', []):
- matchargs.append('x:' + p)
- matchargs = ','.join(('%r' % p) for p in matchargs)
- opts['_matchfiles'] = matchargs
- else:
- if follow:
- fpats = ('_patsfollow', '_patsfollowfirst')
- fnopats = (('_ancestors', '_fancestors'),
- ('_descendants', '_fdescendants'))
- if pats:
- # follow() revset inteprets its file argument as a
- # manifest entry, so use match.files(), not pats.
- opts[fpats[followfirst]] = list(match.files())
- else:
- opts[fnopats[followdescendants][followfirst]] = str(startrev)
- else:
- opts['_patslog'] = list(pats)
-
- filematcher = None
- if opts.get('patch') or opts.get('stat'):
- if follow:
- filematcher = _makegraphfilematcher(repo, pats, followfirst)
- else:
- filematcher = lambda rev: match
-
- expr = []
- for op, val in opts.iteritems():
- if not val:
- continue
- if op not in opt2revset:
- continue
- revop, andor = opt2revset[op]
- if '%(val)' not in revop:
- expr.append(revop)
- else:
- if not isinstance(val, list):
- e = revop % {'val': val}
- else:
- e = '(' + andor.join((revop % {'val': v}) for v in val) + ')'
- expr.append(e)
-
- if expr:
- expr = '(' + ' and '.join(expr) + ')'
- else:
- expr = None
- return expr, filematcher
-
-def getgraphlogrevs(repo, pats, opts):
- """Return (revs, expr, filematcher) where revs is an iterable of
- revision numbers, expr is a revset string built from log options
- and file patterns or None, and used to filter 'revs'. If --stat or
- --patch are not passed filematcher is None. Otherwise it is a
- callable taking a revision number and returning a match objects
- filtering the files to be detailed when displaying the revision.
- """
- def increasingrevs(repo, revs, matcher):
- # The sorted input rev sequence is chopped in sub-sequences
- # which are sorted in ascending order and passed to the
- # matcher. The filtered revs are sorted again as they were in
- # the original sub-sequence. This achieve several things:
- #
- # - getlogrevs() now returns a generator which behaviour is
- # adapted to log need. First results come fast, last ones
- # are batched for performances.
- #
- # - revset matchers often operate faster on revision in
- # changelog order, because most filters deal with the
- # changelog.
- #
- # - revset matchers can reorder revisions. "A or B" typically
- # returns returns the revision matching A then the revision
- # matching B. We want to hide this internal implementation
- # detail from the caller, and sorting the filtered revision
- # again achieves this.
- for i, window in increasingwindows(0, len(revs), windowsize=1):
- orevs = revs[i:i + window]
- nrevs = set(matcher(repo, sorted(orevs)))
- for rev in orevs:
- if rev in nrevs:
- yield rev
-
- if not len(repo):
- return iter([]), None, None
- # Default --rev value depends on --follow but --follow behaviour
- # depends on revisions resolved from --rev...
- follow = opts.get('follow') or opts.get('follow_first')
- if opts.get('rev'):
- revs = scmutil.revrange(repo, opts['rev'])
- else:
- if follow and len(repo) > 0:
- revs = scmutil.revrange(repo, ['.:0'])
- else:
- revs = range(len(repo) - 1, -1, -1)
- if not revs:
- return iter([]), None, None
- expr, filematcher = _makegraphlogrevset(repo, pats, opts, revs)
- if expr:
- matcher = revset.match(repo.ui, expr)
- revs = increasingrevs(repo, revs, matcher)
- if not opts.get('hidden'):
- # --hidden is still experimental and not worth a dedicated revset
- # yet. Fortunately, filtering revision number is fast.
- revs = (r for r in revs if r not in repo.hiddenrevs)
- else:
- revs = iter(revs)
- return revs, expr, filematcher
-
-def displaygraph(ui, dag, displayer, showparents, edgefn, getrenamed=None,
- filematcher=None):
- seen, state = [], graphmod.asciistate()
- for rev, type, ctx, parents in dag:
- char = 'o'
- if ctx.node() in showparents:
- char = '@'
- elif ctx.obsolete():
- char = 'x'
- copies = None
- if getrenamed and ctx.rev():
- copies = []
- for fn in ctx.files():
- rename = getrenamed(fn, ctx.rev())
- if rename:
- copies.append((fn, rename[0]))
- revmatchfn = None
- if filematcher is not None:
- revmatchfn = filematcher(ctx.rev())
- displayer.show(ctx, copies=copies, matchfn=revmatchfn)
- lines = displayer.hunk.pop(rev).split('\n')
- if not lines[-1]:
- del lines[-1]
- displayer.flush(rev)
- edges = edgefn(type, char, lines, seen, rev, parents)
- for type, char, lines, coldata in edges:
- graphmod.ascii(ui, state, type, char, lines, coldata)
- displayer.close()
-
-def graphlog(ui, repo, *pats, **opts):
- # Parameters are identical to log command ones
- revs, expr, filematcher = getgraphlogrevs(repo, pats, opts)
- revs = sorted(revs, reverse=1)
- limit = loglimit(opts)
- if limit is not None:
- revs = revs[:limit]
- revdag = graphmod.dagwalker(repo, revs)
-
- getrenamed = None
- if opts.get('copies'):
- endrev = None
- if opts.get('rev'):
- endrev = max(scmutil.revrange(repo, opts.get('rev'))) + 1
- getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
- displayer = show_changeset(ui, repo, opts, buffered=True)
- showparents = [ctx.node() for ctx in repo[None].parents()]
- displaygraph(ui, revdag, displayer, showparents,
- graphmod.asciiedges, getrenamed, filematcher)
-
-def checkunsupportedgraphflags(pats, opts):
- for op in ["newest_first"]:
- if op in opts and opts[op]:
- raise util.Abort(_("-G/--graph option is incompatible with --%s")
- % op.replace("_", "-"))
-
-def graphrevs(repo, nodes, opts):
- limit = loglimit(opts)
- nodes.reverse()
- if limit is not None:
- nodes = nodes[:limit]
- return graphmod.nodes(repo, nodes)
-
-def add(ui, repo, match, dryrun, listsubrepos, prefix, explicitonly):
+def add(ui, repo, match, dryrun, listsubrepos, prefix):
join = lambda f: os.path.join(prefix, f)
bad = []
oldbad = match.bad
@@ -1473,82 +1148,31 @@ def add(ui, repo, match, dryrun, listsubrepos, prefix, explicitonly):
cca = None
abort, warn = scmutil.checkportabilityalert(ui)
if abort or warn:
- cca = scmutil.casecollisionauditor(ui, abort, repo.dirstate)
+ cca = scmutil.casecollisionauditor(ui, abort, wctx)
for f in repo.walk(match):
exact = match.exact(f)
- if exact or not explicitonly and f not in repo.dirstate:
+ if exact or f not in repo.dirstate:
if cca:
cca(f)
names.append(f)
if ui.verbose or not exact:
ui.status(_('adding %s\n') % match.rel(join(f)))
- for subpath in wctx.substate:
- sub = wctx.sub(subpath)
- try:
- submatch = matchmod.narrowmatcher(subpath, match)
- if listsubrepos:
- bad.extend(sub.add(ui, submatch, dryrun, listsubrepos, prefix,
- False))
- else:
- bad.extend(sub.add(ui, submatch, dryrun, listsubrepos, prefix,
- True))
- except error.LookupError:
- ui.status(_("skipping missing subrepository: %s\n")
- % join(subpath))
+ if listsubrepos:
+ for subpath in wctx.substate:
+ sub = wctx.sub(subpath)
+ try:
+ submatch = matchmod.narrowmatcher(subpath, match)
+ bad.extend(sub.add(ui, submatch, dryrun, prefix))
+ except error.LookupError:
+ ui.status(_("skipping missing subrepository: %s\n")
+ % join(subpath))
if not dryrun:
rejected = wctx.add(names, prefix)
bad.extend(f for f in rejected if f in match.files())
return bad
-def forget(ui, repo, match, prefix, explicitonly):
- join = lambda f: os.path.join(prefix, f)
- bad = []
- oldbad = match.bad
- match.bad = lambda x, y: bad.append(x) or oldbad(x, y)
- wctx = repo[None]
- forgot = []
- s = repo.status(match=match, clean=True)
- forget = sorted(s[0] + s[1] + s[3] + s[6])
- if explicitonly:
- forget = [f for f in forget if match.exact(f)]
-
- for subpath in wctx.substate:
- sub = wctx.sub(subpath)
- try:
- submatch = matchmod.narrowmatcher(subpath, match)
- subbad, subforgot = sub.forget(ui, submatch, prefix)
- bad.extend([subpath + '/' + f for f in subbad])
- forgot.extend([subpath + '/' + f for f in subforgot])
- except error.LookupError:
- ui.status(_("skipping missing subrepository: %s\n")
- % join(subpath))
-
- if not explicitonly:
- for f in match.files():
- if f not in repo.dirstate and not os.path.isdir(match.rel(join(f))):
- if f not in forgot:
- if os.path.exists(match.rel(join(f))):
- ui.warn(_('not removing %s: '
- 'file is already untracked\n')
- % match.rel(join(f)))
- bad.append(f)
-
- for f in forget:
- if ui.verbose or not match.exact(f):
- ui.status(_('removing %s\n') % match.rel(join(f)))
-
- rejected = wctx.forget(forget, prefix)
- bad.extend(f for f in rejected if f in match.files())
- forgot.extend(forget)
- return bad, forgot
-
-def duplicatecopies(repo, rev, p1):
- "Reproduce copies found in the source revision in the dirstate for grafts"
- for dst, src in copies.pathcopies(repo[p1], repo[rev]).iteritems():
- repo.dirstate.copy(src, dst)
-
def commit(ui, repo, commitfunc, pats, opts):
'''commit the specified files or all outstanding changes'''
date = opts.get('date')
@@ -1564,136 +1188,6 @@ def commit(ui, repo, commitfunc, pats, opts):
return commitfunc(ui, repo, message,
scmutil.match(repo[None], pats, opts), opts)
-def amend(ui, repo, commitfunc, old, extra, pats, opts):
- ui.note(_('amending changeset %s\n') % old)
- base = old.p1()
-
- wlock = repo.wlock()
- try:
- # First, do a regular commit to record all changes in the working
- # directory (if there are any)
- ui.callhooks = False
- try:
- node = commit(ui, repo, commitfunc, pats, opts)
- finally:
- ui.callhooks = True
- ctx = repo[node]
-
- # Participating changesets:
- #
- # node/ctx o - new (intermediate) commit that contains changes from
- # | working dir to go into amending commit (or a workingctx
- # | if there were no changes)
- # |
- # old o - changeset to amend
- # |
- # base o - parent of amending changeset
-
- # Update extra dict from amended commit (e.g. to preserve graft source)
- extra.update(old.extra())
-
- # Also update it from the intermediate commit or from the wctx
- extra.update(ctx.extra())
-
- files = set(old.files())
-
- # Second, we use either the commit we just did, or if there were no
- # changes the parent of the working directory as the version of the
- # files in the final amend commit
- if node:
- ui.note(_('copying changeset %s to %s\n') % (ctx, base))
-
- user = ctx.user()
- date = ctx.date()
- message = ctx.description()
- # Recompute copies (avoid recording a -> b -> a)
- copied = copies.pathcopies(base, ctx)
-
- # Prune files which were reverted by the updates: if old introduced
- # file X and our intermediate commit, node, renamed that file, then
- # those two files are the same and we can discard X from our list
- # of files. Likewise if X was deleted, it's no longer relevant
- files.update(ctx.files())
-
- def samefile(f):
- if f in ctx.manifest():
- a = ctx.filectx(f)
- if f in base.manifest():
- b = base.filectx(f)
- return (not a.cmp(b)
- and a.flags() == b.flags())
- else:
- return False
- else:
- return f not in base.manifest()
- files = [f for f in files if not samefile(f)]
-
- def filectxfn(repo, ctx_, path):
- try:
- fctx = ctx[path]
- flags = fctx.flags()
- mctx = context.memfilectx(fctx.path(), fctx.data(),
- islink='l' in flags,
- isexec='x' in flags,
- copied=copied.get(path))
- return mctx
- except KeyError:
- raise IOError
- else:
- ui.note(_('copying changeset %s to %s\n') % (old, base))
-
- # Use version of files as in the old cset
- def filectxfn(repo, ctx_, path):
- try:
- return old.filectx(path)
- except KeyError:
- raise IOError
-
- # See if we got a message from -m or -l, if not, open the editor
- # with the message of the changeset to amend
- user = opts.get('user') or old.user()
- date = opts.get('date') or old.date()
- message = logmessage(ui, opts)
- if not message:
- cctx = context.workingctx(repo, old.description(), user, date,
- extra,
- repo.status(base.node(), old.node()))
- message = commitforceeditor(repo, cctx, [])
-
- new = context.memctx(repo,
- parents=[base.node(), nullid],
- text=message,
- files=files,
- filectxfn=filectxfn,
- user=user,
- date=date,
- extra=extra)
- newid = repo.commitctx(new)
- if newid != old.node():
- # Reroute the working copy parent to the new changeset
- repo.setparents(newid, nullid)
-
- # Move bookmarks from old parent to amend commit
- bms = repo.nodebookmarks(old.node())
- if bms:
- for bm in bms:
- repo._bookmarks[bm] = newid
- bookmarks.write(repo)
-
- # Strip the intermediate commit (if there was one) and the amended
- # commit
- lock = repo.lock()
- try:
- if node:
- ui.note(_('stripping intermediate changeset %s\n') % ctx)
- ui.note(_('stripping amended changeset %s\n') % old)
- repair.strip(ui, repo, old.node(), topic='amend-backup')
- finally:
- lock.release()
- finally:
- wlock.release()
- return newid
-
def commiteditor(repo, ctx, subs):
if ctx.description():
return ctx.description()
@@ -1734,187 +1228,6 @@ def commitforceeditor(repo, ctx, subs):
return text
-def revert(ui, repo, ctx, parents, *pats, **opts):
- parent, p2 = parents
- node = ctx.node()
-
- mf = ctx.manifest()
- if node == parent:
- pmf = mf
- else:
- pmf = None
-
- # need all matching names in dirstate and manifest of target rev,
- # so have to walk both. do not print errors if files exist in one
- # but not other.
-
- names = {}
-
- wlock = repo.wlock()
- try:
- # walk dirstate.
-
- m = scmutil.match(repo[None], pats, opts)
- m.bad = lambda x, y: False
- for abs in repo.walk(m):
- names[abs] = m.rel(abs), m.exact(abs)
-
- # walk target manifest.
-
- def badfn(path, msg):
- if path in names:
- return
- if path in ctx.substate:
- return
- path_ = path + '/'
- for f in names:
- if f.startswith(path_):
- return
- ui.warn("%s: %s\n" % (m.rel(path), msg))
-
- m = scmutil.match(ctx, pats, opts)
- m.bad = badfn
- for abs in ctx.walk(m):
- if abs not in names:
- names[abs] = m.rel(abs), m.exact(abs)
-
- # get the list of subrepos that must be reverted
- targetsubs = [s for s in ctx.substate if m(s)]
- m = scmutil.matchfiles(repo, names)
- changes = repo.status(match=m)[:4]
- modified, added, removed, deleted = map(set, changes)
-
- # if f is a rename, also revert the source
- cwd = repo.getcwd()
- for f in added:
- src = repo.dirstate.copied(f)
- if src and src not in names and repo.dirstate[src] == 'r':
- removed.add(src)
- names[src] = (repo.pathto(src, cwd), True)
-
- def removeforget(abs):
- if repo.dirstate[abs] == 'a':
- return _('forgetting %s\n')
- return _('removing %s\n')
-
- revert = ([], _('reverting %s\n'))
- add = ([], _('adding %s\n'))
- remove = ([], removeforget)
- undelete = ([], _('undeleting %s\n'))
-
- disptable = (
- # dispatch table:
- # file state
- # action if in target manifest
- # action if not in target manifest
- # make backup if in target manifest
- # make backup if not in target manifest
- (modified, revert, remove, True, True),
- (added, revert, remove, True, False),
- (removed, undelete, None, False, False),
- (deleted, revert, remove, False, False),
- )
-
- for abs, (rel, exact) in sorted(names.items()):
- mfentry = mf.get(abs)
- target = repo.wjoin(abs)
- def handle(xlist, dobackup):
- xlist[0].append(abs)
- if (dobackup and not opts.get('no_backup') and
- os.path.lexists(target)):
- bakname = "%s.orig" % rel
- ui.note(_('saving current version of %s as %s\n') %
- (rel, bakname))
- if not opts.get('dry_run'):
- util.rename(target, bakname)
- if ui.verbose or not exact:
- msg = xlist[1]
- if not isinstance(msg, basestring):
- msg = msg(abs)
- ui.status(msg % rel)
- for table, hitlist, misslist, backuphit, backupmiss in disptable:
- if abs not in table:
- continue
- # file has changed in dirstate
- if mfentry:
- handle(hitlist, backuphit)
- elif misslist is not None:
- handle(misslist, backupmiss)
- break
- else:
- if abs not in repo.dirstate:
- if mfentry:
- handle(add, True)
- elif exact:
- ui.warn(_('file not managed: %s\n') % rel)
- continue
- # file has not changed in dirstate
- if node == parent:
- if exact:
- ui.warn(_('no changes needed to %s\n') % rel)
- continue
- if pmf is None:
- # only need parent manifest in this unlikely case,
- # so do not read by default
- pmf = repo[parent].manifest()
- if abs in pmf and mfentry:
- # if version of file is same in parent and target
- # manifests, do nothing
- if (pmf[abs] != mfentry or
- pmf.flags(abs) != mf.flags(abs)):
- handle(revert, False)
- else:
- handle(remove, False)
-
- if not opts.get('dry_run'):
- def checkout(f):
- fc = ctx[f]
- repo.wwrite(f, fc.data(), fc.flags())
-
- audit_path = scmutil.pathauditor(repo.root)
- for f in remove[0]:
- if repo.dirstate[f] == 'a':
- repo.dirstate.drop(f)
- continue
- audit_path(f)
- try:
- util.unlinkpath(repo.wjoin(f))
- except OSError:
- pass
- repo.dirstate.remove(f)
-
- normal = None
- if node == parent:
- # We're reverting to our parent. If possible, we'd like status
- # to report the file as clean. We have to use normallookup for
- # merges to avoid losing information about merged/dirty files.
- if p2 != nullid:
- normal = repo.dirstate.normallookup
- else:
- normal = repo.dirstate.normal
- for f in revert[0]:
- checkout(f)
- if normal:
- normal(f)
-
- for f in add[0]:
- checkout(f)
- repo.dirstate.add(f)
-
- normal = repo.dirstate.normallookup
- if node == parent and p2 == nullid:
- normal = repo.dirstate.normal
- for f in undelete[0]:
- checkout(f)
- normal(f)
-
- if targetsubs:
- # Revert the subrepos on the revert list
- for sub in targetsubs:
- ctx.sub(sub).revert(ui, ctx.substate[sub], *pats, **opts)
- finally:
- wlock.release()
-
def command(table):
'''returns a function object bound to table which can be used as
a decorator for populating table as a command table'''