diff options
author | Ori Bernstein <ori@eigenstate.org> | 2021-06-14 00:00:37 +0000 |
---|---|---|
committer | Ori Bernstein <ori@eigenstate.org> | 2021-06-14 00:00:37 +0000 |
commit | a73a964e51247ed169d322c725a3a18859f109a3 (patch) | |
tree | 3f752d117274d444bda44e85609aeac1acf313f3 /sys/lib/python/mercurial/cmdutil.py | |
parent | e64efe273fcb921a61bf27d33b230c4e64fcd425 (diff) |
python, hg: tow outside the environment.
they've served us well, and can ride off into the sunset.
Diffstat (limited to 'sys/lib/python/mercurial/cmdutil.py')
-rw-r--r-- | sys/lib/python/mercurial/cmdutil.py | 1254 |
1 files changed, 0 insertions, 1254 deletions
diff --git a/sys/lib/python/mercurial/cmdutil.py b/sys/lib/python/mercurial/cmdutil.py deleted file mode 100644 index 1c58d6bdc..000000000 --- a/sys/lib/python/mercurial/cmdutil.py +++ /dev/null @@ -1,1254 +0,0 @@ -# cmdutil.py - help for command processing in mercurial -# -# Copyright 2005-2007 Matt Mackall <mpm@selenic.com> -# -# This software may be used and distributed according to the terms of the -# GNU General Public License version 2, incorporated herein by reference. - -from node import hex, nullid, nullrev, short -from i18n import _ -import os, sys, errno, re, glob -import mdiff, bdiff, util, templater, patch, error, encoding -import match as _match - -revrangesep = ':' - -def findpossible(cmd, table, strict=False): - """ - Return cmd -> (aliases, command table entry) - for each matching command. - Return debug commands (or their aliases) only if no normal command matches. - """ - choice = {} - debugchoice = {} - for e in table.keys(): - aliases = e.lstrip("^").split("|") - found = None - if cmd in aliases: - found = cmd - elif not strict: - for a in aliases: - if a.startswith(cmd): - found = a - break - if found is not None: - if aliases[0].startswith("debug") or found.startswith("debug"): - debugchoice[found] = (aliases, table[e]) - else: - choice[found] = (aliases, table[e]) - - if not choice and debugchoice: - choice = debugchoice - - return choice - -def findcmd(cmd, table, strict=True): - """Return (aliases, command table entry) for command string.""" - choice = findpossible(cmd, table, strict) - - if cmd in choice: - return choice[cmd] - - if len(choice) > 1: - clist = choice.keys() - clist.sort() - raise error.AmbiguousCommand(cmd, clist) - - if choice: - return choice.values()[0] - - raise error.UnknownCommand(cmd) - -def bail_if_changed(repo): - if repo.dirstate.parents()[1] != nullid: - raise util.Abort(_('outstanding uncommitted merge')) - modified, added, removed, deleted = repo.status()[:4] - if modified or added or removed or deleted: - raise util.Abort(_("outstanding uncommitted changes")) - -def logmessage(opts): - """ get the log message according to -m and -l option """ - message = opts.get('message') - logfile = opts.get('logfile') - - if message and logfile: - raise util.Abort(_('options --message and --logfile are mutually ' - 'exclusive')) - if not message and logfile: - try: - if logfile == '-': - message = sys.stdin.read() - else: - message = open(logfile).read() - except IOError, inst: - raise util.Abort(_("can't read commit message '%s': %s") % - (logfile, inst.strerror)) - return message - -def loglimit(opts): - """get the log limit according to option -l/--limit""" - limit = opts.get('limit') - if limit: - try: - limit = int(limit) - except ValueError: - raise util.Abort(_('limit must be a positive integer')) - if limit <= 0: raise util.Abort(_('limit must be positive')) - else: - limit = sys.maxint - return limit - -def remoteui(src, opts): - 'build a remote ui from ui or repo and opts' - if hasattr(src, 'baseui'): # looks like a repository - dst = src.baseui.copy() # drop repo-specific config - src = src.ui # copy target options from repo - else: # assume it's a global ui object - dst = src.copy() # keep all global options - - # copy ssh-specific options - for o in 'ssh', 'remotecmd': - v = opts.get(o) or src.config('ui', o) - if v: - dst.setconfig("ui", o, v) - # copy bundle-specific options - r = src.config('bundle', 'mainreporoot') - if r: - dst.setconfig('bundle', 'mainreporoot', r) - - return dst - -def revpair(repo, revs): - '''return pair of nodes, given list of revisions. second item can - be None, meaning use working dir.''' - - def revfix(repo, val, defval): - if not val and val != 0 and defval is not None: - val = defval - return repo.lookup(val) - - if not revs: - return repo.dirstate.parents()[0], None - end = None - if len(revs) == 1: - if revrangesep in revs[0]: - start, end = revs[0].split(revrangesep, 1) - start = revfix(repo, start, 0) - end = revfix(repo, end, len(repo) - 1) - else: - start = revfix(repo, revs[0], None) - elif len(revs) == 2: - if revrangesep in revs[0] or revrangesep in revs[1]: - raise util.Abort(_('too many revisions specified')) - start = revfix(repo, revs[0], None) - end = revfix(repo, revs[1], None) - else: - raise util.Abort(_('too many revisions specified')) - return start, end - -def revrange(repo, revs): - """Yield revision as strings from a list of revision specifications.""" - - def revfix(repo, val, defval): - if not val and val != 0 and defval is not None: - return defval - return repo.changelog.rev(repo.lookup(val)) - - seen, l = set(), [] - for spec in revs: - if revrangesep in spec: - start, end = spec.split(revrangesep, 1) - start = revfix(repo, start, 0) - end = revfix(repo, end, len(repo) - 1) - step = start > end and -1 or 1 - for rev in xrange(start, end+step, step): - if rev in seen: - continue - seen.add(rev) - l.append(rev) - else: - rev = revfix(repo, spec, None) - if rev in seen: - continue - seen.add(rev) - l.append(rev) - - return l - -def make_filename(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), - } - expander = { - '%': lambda: '%', - 'b': lambda: os.path.basename(repo.root), - } - - try: - if node: - expander.update(node_expander) - if node: - expander['r'] = (lambda: - str(repo.changelog.rev(node)).zfill(revwidth or 0)) - if total is not None: - expander['N'] = lambda: str(total) - if seqno is not None: - expander['n'] = lambda: str(seqno) - if total is not None and seqno is not None: - expander['n'] = lambda: str(seqno).zfill(len(str(total))) - if pathname is not None: - expander['s'] = lambda: os.path.basename(pathname) - expander['d'] = lambda: os.path.dirname(pathname) or '.' - expander['p'] = lambda: pathname - - newname = [] - patlen = len(pat) - i = 0 - while i < patlen: - c = pat[i] - if c == '%': - i += 1 - c = pat[i] - c = expander[c]() - newname.append(c) - i += 1 - return ''.join(newname) - except KeyError, inst: - raise util.Abort(_("invalid format spec '%%%s' in output filename") % - inst.args[0]) - -def make_file(repo, pat, node=None, - total=None, seqno=None, revwidth=None, mode='wb', pathname=None): - - writable = 'w' in mode or 'a' in mode - - if not pat or pat == '-': - return writable and sys.stdout or sys.stdin - if hasattr(pat, 'write') and writable: - return pat - if hasattr(pat, 'read') and 'r' in mode: - return pat - return open(make_filename(repo, pat, node, total, seqno, revwidth, - pathname), - mode) - -def expandpats(pats): - if not util.expandglobs: - return list(pats) - ret = [] - for p in pats: - kind, name = _match._patsplit(p, None) - if kind is None: - try: - globbed = glob.glob(name) - except re.error: - globbed = [name] - if globbed: - ret.extend(globbed) - continue - ret.append(p) - return ret - -def match(repo, pats=[], opts={}, globbed=False, default='relpath'): - if not globbed and default == 'relpath': - pats = expandpats(pats or []) - m = _match.match(repo.root, repo.getcwd(), pats, - opts.get('include'), opts.get('exclude'), default) - def badfn(f, msg): - repo.ui.warn("%s: %s\n" % (m.rel(f), msg)) - m.bad = badfn - return m - -def matchall(repo): - return _match.always(repo.root, repo.getcwd()) - -def matchfiles(repo, files): - return _match.exact(repo.root, repo.getcwd(), files) - -def findrenames(repo, added, removed, threshold): - '''find renamed files -- yields (before, after, score) tuples''' - ctx = repo['.'] - for a in added: - aa = repo.wread(a) - bestname, bestscore = None, threshold - for r in removed: - if r not in ctx: - continue - rr = ctx.filectx(r).data() - - # bdiff.blocks() returns blocks of matching lines - # count the number of bytes in each - equal = 0 - alines = mdiff.splitnewlines(aa) - matches = bdiff.blocks(aa, rr) - for x1,x2,y1,y2 in matches: - for line in alines[x1:x2]: - equal += len(line) - - lengths = len(aa) + len(rr) - if lengths: - myscore = equal*2.0 / lengths - if myscore >= bestscore: - bestname, bestscore = r, myscore - if bestname: - yield bestname, a, bestscore - -def addremove(repo, pats=[], opts={}, dry_run=None, similarity=None): - if dry_run is None: - dry_run = opts.get('dry_run') - if similarity is None: - similarity = float(opts.get('similarity') or 0) - # we'd use status here, except handling of symlinks and ignore is tricky - added, unknown, deleted, removed = [], [], [], [] - audit_path = util.path_auditor(repo.root) - m = match(repo, pats, opts) - for abs in repo.walk(m): - target = repo.wjoin(abs) - good = True - try: - audit_path(abs) - except: - good = False - rel = m.rel(abs) - exact = m.exact(abs) - if good and abs not in repo.dirstate: - unknown.append(abs) - if repo.ui.verbose or not exact: - repo.ui.status(_('adding %s\n') % ((pats and rel) or abs)) - elif repo.dirstate[abs] != 'r' and (not good or not util.lexists(target) - or (os.path.isdir(target) and not os.path.islink(target))): - deleted.append(abs) - if repo.ui.verbose or not exact: - repo.ui.status(_('removing %s\n') % ((pats and rel) or abs)) - # for finding renames - elif repo.dirstate[abs] == 'r': - removed.append(abs) - elif repo.dirstate[abs] == 'a': - added.append(abs) - if not dry_run: - repo.remove(deleted) - repo.add(unknown) - if similarity > 0: - for old, new, score in findrenames(repo, added + unknown, - removed + deleted, similarity): - if repo.ui.verbose or not m.exact(old) or not m.exact(new): - repo.ui.status(_('recording removal of %s as rename to %s ' - '(%d%% similar)\n') % - (m.rel(old), m.rel(new), score * 100)) - if not dry_run: - repo.copy(old, new) - -def copy(ui, repo, pats, opts, rename=False): - # called with the repo lock held - # - # hgsep => pathname that uses "/" to separate directories - # ossep => pathname that uses os.sep to separate directories - cwd = repo.getcwd() - targets = {} - after = opts.get("after") - dryrun = opts.get("dry_run") - - def walkpat(pat): - srcs = [] - m = match(repo, [pat], opts, globbed=True) - for abs in repo.walk(m): - state = repo.dirstate[abs] - rel = m.rel(abs) - exact = m.exact(abs) - if state in '?r': - if exact and state == '?': - ui.warn(_('%s: not copying - file is not managed\n') % rel) - if exact and state == 'r': - ui.warn(_('%s: not copying - file has been marked for' - ' remove\n') % rel) - continue - # abs: hgsep - # rel: ossep - srcs.append((abs, rel, exact)) - return srcs - - # abssrc: hgsep - # relsrc: ossep - # otarget: ossep - def copyfile(abssrc, relsrc, otarget, exact): - abstarget = util.canonpath(repo.root, cwd, otarget) - reltarget = repo.pathto(abstarget, cwd) - target = repo.wjoin(abstarget) - src = repo.wjoin(abssrc) - state = repo.dirstate[abstarget] - - # check for collisions - prevsrc = targets.get(abstarget) - if prevsrc is not None: - ui.warn(_('%s: not overwriting - %s collides with %s\n') % - (reltarget, repo.pathto(abssrc, cwd), - repo.pathto(prevsrc, cwd))) - return - - # check for overwrites - exists = os.path.exists(target) - if not after and exists or after and state in 'mn': - if not opts['force']: - ui.warn(_('%s: not overwriting - file exists\n') % - reltarget) - return - - if after: - if not exists: - return - elif not dryrun: - try: - if exists: - os.unlink(target) - targetdir = os.path.dirname(target) or '.' - if not os.path.isdir(targetdir): - os.makedirs(targetdir) - util.copyfile(src, target) - except IOError, inst: - if inst.errno == errno.ENOENT: - ui.warn(_('%s: deleted in working copy\n') % relsrc) - else: - ui.warn(_('%s: cannot copy - %s\n') % - (relsrc, inst.strerror)) - return True # report a failure - - if ui.verbose or not exact: - if rename: - ui.status(_('moving %s to %s\n') % (relsrc, reltarget)) - else: - ui.status(_('copying %s to %s\n') % (relsrc, reltarget)) - - targets[abstarget] = abssrc - - # fix up dirstate - origsrc = repo.dirstate.copied(abssrc) or abssrc - if abstarget == origsrc: # copying back a copy? - if state not in 'mn' and not dryrun: - repo.dirstate.normallookup(abstarget) - else: - if repo.dirstate[origsrc] == 'a' and origsrc == abssrc: - if not ui.quiet: - ui.warn(_("%s has not been committed yet, so no copy " - "data will be stored for %s.\n") - % (repo.pathto(origsrc, cwd), reltarget)) - if repo.dirstate[abstarget] in '?r' and not dryrun: - repo.add([abstarget]) - elif not dryrun: - repo.copy(origsrc, abstarget) - - if rename and not dryrun: - repo.remove([abssrc], not after) - - # pat: ossep - # dest ossep - # srcs: list of (hgsep, hgsep, ossep, bool) - # return: function that takes hgsep and returns ossep - def targetpathfn(pat, dest, srcs): - if os.path.isdir(pat): - abspfx = util.canonpath(repo.root, cwd, pat) - abspfx = util.localpath(abspfx) - if destdirexists: - striplen = len(os.path.split(abspfx)[0]) - else: - striplen = len(abspfx) - if striplen: - striplen += len(os.sep) - res = lambda p: os.path.join(dest, util.localpath(p)[striplen:]) - elif destdirexists: - res = lambda p: os.path.join(dest, - os.path.basename(util.localpath(p))) - else: - res = lambda p: dest - return res - - # pat: ossep - # dest ossep - # srcs: list of (hgsep, hgsep, ossep, bool) - # return: function that takes hgsep and returns ossep - def targetpathafterfn(pat, dest, srcs): - if _match.patkind(pat): - # a mercurial pattern - res = lambda p: os.path.join(dest, - os.path.basename(util.localpath(p))) - else: - abspfx = util.canonpath(repo.root, cwd, pat) - if len(abspfx) < len(srcs[0][0]): - # A directory. Either the target path contains the last - # component of the source path or it does not. - def evalpath(striplen): - score = 0 - for s in srcs: - t = os.path.join(dest, util.localpath(s[0])[striplen:]) - if os.path.exists(t): - score += 1 - return score - - abspfx = util.localpath(abspfx) - striplen = len(abspfx) - if striplen: - striplen += len(os.sep) - if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])): - score = evalpath(striplen) - striplen1 = len(os.path.split(abspfx)[0]) - if striplen1: - striplen1 += len(os.sep) - if evalpath(striplen1) > score: - striplen = striplen1 - res = lambda p: os.path.join(dest, - util.localpath(p)[striplen:]) - else: - # a file - if destdirexists: - res = lambda p: os.path.join(dest, - os.path.basename(util.localpath(p))) - else: - res = lambda p: dest - return res - - - pats = expandpats(pats) - if not pats: - raise util.Abort(_('no source or destination specified')) - if len(pats) == 1: - raise util.Abort(_('no destination specified')) - dest = pats.pop() - destdirexists = os.path.isdir(dest) and not os.path.islink(dest) - if not destdirexists: - if len(pats) > 1 or _match.patkind(pats[0]): - raise util.Abort(_('with multiple sources, destination must be an ' - 'existing directory')) - if util.endswithsep(dest): - raise util.Abort(_('destination %s is not a directory') % dest) - - tfn = targetpathfn - if after: - tfn = targetpathafterfn - copylist = [] - for pat in pats: - srcs = walkpat(pat) - if not srcs: - continue - copylist.append((tfn(pat, dest, srcs), srcs)) - if not copylist: - raise util.Abort(_('no files to copy')) - - errors = 0 - for targetpath, srcs in copylist: - for abssrc, relsrc, exact in srcs: - if copyfile(abssrc, relsrc, targetpath(abssrc), exact): - errors += 1 - - if errors: - ui.warn(_('(consider using --after)\n')) - - return errors - -def service(opts, parentfn=None, initfn=None, runfn=None, logfile=None): - '''Run a command as a service.''' - - if opts['daemon'] and not opts['daemon_pipefds']: - rfd, wfd = os.pipe() - args = sys.argv[:] - args.append('--daemon-pipefds=%d,%d' % (rfd, wfd)) - # Don't pass --cwd to the child process, because we've already - # changed directory. - for i in xrange(1,len(args)): - if args[i].startswith('--cwd='): - del args[i] - break - elif args[i].startswith('--cwd'): - del args[i:i+2] - break - pid = os.spawnvp(os.P_NOWAIT | getattr(os, 'P_DETACH', 0), - args[0], args) - os.close(wfd) - os.read(rfd, 1) - if parentfn: - return parentfn(pid) - else: - os._exit(0) - - if initfn: - initfn() - - if opts['pid_file']: - fp = open(opts['pid_file'], 'w') - fp.write(str(os.getpid()) + '\n') - fp.close() - - if opts['daemon_pipefds']: - rfd, wfd = [int(x) for x in opts['daemon_pipefds'].split(',')] - os.close(rfd) - try: - os.setsid() - except AttributeError: - pass - os.write(wfd, 'y') - os.close(wfd) - sys.stdout.flush() - sys.stderr.flush() - - nullfd = os.open(util.nulldev, os.O_RDWR) - logfilefd = nullfd - if logfile: - logfilefd = os.open(logfile, os.O_RDWR | os.O_CREAT | os.O_APPEND) - os.dup2(nullfd, 0) - os.dup2(logfilefd, 1) - os.dup2(logfilefd, 2) - if nullfd not in (0, 1, 2): - os.close(nullfd) - if logfile and logfilefd not in (0, 1, 2): - os.close(logfilefd) - - if runfn: - return runfn() - -class changeset_printer(object): - '''show changeset information when templating not requested.''' - - def __init__(self, ui, repo, patch, diffopts, buffered): - self.ui = ui - self.repo = repo - self.buffered = buffered - self.patch = patch - self.diffopts = diffopts - self.header = {} - self.hunk = {} - self.lastheader = None - - def flush(self, rev): - if rev in self.header: - h = self.header[rev] - if h != self.lastheader: - self.lastheader = h - self.ui.write(h) - del self.header[rev] - if rev in self.hunk: - self.ui.write(self.hunk[rev]) - del self.hunk[rev] - return 1 - return 0 - - def show(self, ctx, copies=(), **props): - if self.buffered: - self.ui.pushbuffer() - self._show(ctx, copies, props) - self.hunk[ctx.rev()] = self.ui.popbuffer() - else: - self._show(ctx, copies, props) - - def _show(self, ctx, copies, props): - '''show a single changeset or file revision''' - changenode = ctx.node() - rev = ctx.rev() - - if self.ui.quiet: - self.ui.write("%d:%s\n" % (rev, short(changenode))) - return - - log = self.repo.changelog - changes = log.read(changenode) - date = util.datestr(changes[2]) - extra = changes[5] - branch = extra.get("branch") - - hexfunc = self.ui.debugflag and hex or short - - parents = [(p, hexfunc(log.node(p))) - for p in self._meaningful_parentrevs(log, rev)] - - self.ui.write(_("changeset: %d:%s\n") % (rev, hexfunc(changenode))) - - # don't show the default branch name - if branch != 'default': - branch = encoding.tolocal(branch) - self.ui.write(_("branch: %s\n") % branch) - for tag in self.repo.nodetags(changenode): - self.ui.write(_("tag: %s\n") % tag) - for parent in parents: - self.ui.write(_("parent: %d:%s\n") % parent) - - if self.ui.debugflag: - self.ui.write(_("manifest: %d:%s\n") % - (self.repo.manifest.rev(changes[0]), hex(changes[0]))) - self.ui.write(_("user: %s\n") % changes[1]) - self.ui.write(_("date: %s\n") % date) - - if self.ui.debugflag: - files = self.repo.status(log.parents(changenode)[0], changenode)[:3] - for key, value in zip([_("files:"), _("files+:"), _("files-:")], - files): - if value: - self.ui.write("%-12s %s\n" % (key, " ".join(value))) - elif changes[3] and self.ui.verbose: - self.ui.write(_("files: %s\n") % " ".join(changes[3])) - if copies and self.ui.verbose: - copies = ['%s (%s)' % c for c in copies] - self.ui.write(_("copies: %s\n") % ' '.join(copies)) - - if extra and self.ui.debugflag: - for key, value in sorted(extra.items()): - self.ui.write(_("extra: %s=%s\n") - % (key, value.encode('string_escape'))) - - description = changes[4].strip() - if description: - if self.ui.verbose: - self.ui.write(_("description:\n")) - self.ui.write(description) - self.ui.write("\n\n") - else: - self.ui.write(_("summary: %s\n") % - description.splitlines()[0]) - self.ui.write("\n") - - self.showpatch(changenode) - - def showpatch(self, node): - if self.patch: - prev = self.repo.changelog.parents(node)[0] - chunks = patch.diff(self.repo, prev, node, match=self.patch, - opts=patch.diffopts(self.ui, self.diffopts)) - for chunk in chunks: - self.ui.write(chunk) - self.ui.write("\n") - - def _meaningful_parentrevs(self, log, rev): - """Return list of meaningful (or all if debug) parentrevs for rev. - - For merges (two non-nullrev revisions) both parents are meaningful. - Otherwise the first parent revision is considered meaningful if it - is not the preceding revision. - """ - parents = log.parentrevs(rev) - if not self.ui.debugflag and parents[1] == nullrev: - if parents[0] >= rev - 1: - parents = [] - else: - parents = [parents[0]] - return parents - - -class changeset_templater(changeset_printer): - '''format changeset information.''' - - def __init__(self, ui, repo, patch, diffopts, mapfile, buffered): - changeset_printer.__init__(self, ui, repo, patch, diffopts, buffered) - formatnode = ui.debugflag and (lambda x: x) or (lambda x: x[:12]) - self.t = templater.templater(mapfile, {'formatnode': formatnode}, - cache={ - 'parent': '{rev}:{node|formatnode} ', - 'manifest': '{rev}:{node|formatnode}', - 'filecopy': '{name} ({source})'}) - - def use_template(self, t): - '''set template string to use''' - self.t.cache['changeset'] = t - - def _meaningful_parentrevs(self, ctx): - """Return list of meaningful (or all if debug) parentrevs for rev. - """ - parents = ctx.parents() - if len(parents) > 1: - return parents - if self.ui.debugflag: - return [parents[0], self.repo['null']] - if parents[0].rev() >= ctx.rev() - 1: - return [] - return parents - - def _show(self, ctx, copies, props): - '''show a single changeset or file revision''' - - def showlist(name, values, plural=None, **args): - '''expand set of values. - name is name of key in template map. - values is list of strings or dicts. - plural is plural of name, if not simply name + 's'. - - expansion works like this, given name 'foo'. - - if values is empty, expand 'no_foos'. - - if 'foo' not in template map, return values as a string, - joined by space. - - expand 'start_foos'. - - for each value, expand 'foo'. if 'last_foo' in template - map, expand it instead of 'foo' for last key. - - expand 'end_foos'. - ''' - if plural: names = plural - else: names = name + 's' - if not values: - noname = 'no_' + names - if noname in self.t: - yield self.t(noname, **args) - return - if name not in self.t: - if isinstance(values[0], str): - yield ' '.join(values) - else: - for v in values: - yield dict(v, **args) - return - startname = 'start_' + names - if startname in self.t: - yield self.t(startname, **args) - vargs = args.copy() - def one(v, tag=name): - try: - vargs.update(v) - except (AttributeError, ValueError): - try: - for a, b in v: - vargs[a] = b - except ValueError: - vargs[name] = v - return self.t(tag, **vargs) - lastname = 'last_' + name - if lastname in self.t: - last = values.pop() - else: - last = None - for v in values: - yield one(v) - if last is not None: - yield one(last, tag=lastname) - endname = 'end_' + names - if endname in self.t: - yield self.t(endname, **args) - - def showbranches(**args): - branch = ctx.branch() - if branch != 'default': - branch = encoding.tolocal(branch) - return showlist('branch', [branch], plural='branches', **args) - - def showparents(**args): - parents = [[('rev', p.rev()), ('node', p.hex())] - for p in self._meaningful_parentrevs(ctx)] - return showlist('parent', parents, **args) - - def showtags(**args): - return showlist('tag', ctx.tags(), **args) - - def showextras(**args): - for key, value in sorted(ctx.extra().items()): - args = args.copy() - args.update(dict(key=key, value=value)) - yield self.t('extra', **args) - - def showcopies(**args): - c = [{'name': x[0], 'source': x[1]} for x in copies] - return showlist('file_copy', c, plural='file_copies', **args) - - files = [] - def getfiles(): - if not files: - files[:] = self.repo.status(ctx.parents()[0].node(), - ctx.node())[:3] - return files - def showfiles(**args): - return showlist('file', ctx.files(), **args) - def showmods(**args): - return showlist('file_mod', getfiles()[0], **args) - def showadds(**args): - return showlist('file_add', getfiles()[1], **args) - def showdels(**args): - return showlist('file_del', getfiles()[2], **args) - def showmanifest(**args): - args = args.copy() - args.update(dict(rev=self.repo.manifest.rev(ctx.changeset()[0]), - node=hex(ctx.changeset()[0]))) - return self.t('manifest', **args) - - def showdiffstat(**args): - diff = patch.diff(self.repo, ctx.parents()[0].node(), ctx.node()) - files, adds, removes = 0, 0, 0 - for i in patch.diffstatdata(util.iterlines(diff)): - files += 1 - adds += i[1] - removes += i[2] - return '%s: +%s/-%s' % (files, adds, removes) - - defprops = { - 'author': ctx.user(), - 'branches': showbranches, - 'date': ctx.date(), - 'desc': ctx.description().strip(), - 'file_adds': showadds, - 'file_dels': showdels, - 'file_mods': showmods, - 'files': showfiles, - 'file_copies': showcopies, - 'manifest': showmanifest, - 'node': ctx.hex(), - 'parents': showparents, - 'rev': ctx.rev(), - 'tags': showtags, - 'extras': showextras, - 'diffstat': showdiffstat, - } - props = props.copy() - props.update(defprops) - - # find correct templates for current mode - - tmplmodes = [ - (True, None), - (self.ui.verbose, 'verbose'), - (self.ui.quiet, 'quiet'), - (self.ui.debugflag, 'debug'), - ] - - types = {'header': '', 'changeset': 'changeset'} - for mode, postfix in tmplmodes: - for type in types: - cur = postfix and ('%s_%s' % (type, postfix)) or type - if mode and cur in self.t: - types[type] = cur - - try: - - # write header - if types['header']: - h = templater.stringify(self.t(types['header'], **props)) - if self.buffered: - self.header[ctx.rev()] = h - else: - self.ui.write(h) - - # write changeset metadata, then patch if requested - key = types['changeset'] - self.ui.write(templater.stringify(self.t(key, **props))) - self.showpatch(ctx.node()) - - except KeyError, inst: - msg = _("%s: no key named '%s'") - raise util.Abort(msg % (self.t.mapfile, inst.args[0])) - except SyntaxError, inst: - raise util.Abort(_('%s: %s') % (self.t.mapfile, inst.args[0])) - -def show_changeset(ui, repo, opts, buffered=False, matchfn=False): - """show one changeset using template or regular display. - - Display format will be the first non-empty hit of: - 1. option 'template' - 2. option 'style' - 3. [ui] setting 'logtemplate' - 4. [ui] setting 'style' - If all of these values are either the unset or the empty string, - regular display via changeset_printer() is done. - """ - # options - patch = False - if opts.get('patch'): - patch = matchfn or matchall(repo) - - tmpl = opts.get('template') - style = None - if tmpl: - tmpl = templater.parsestring(tmpl, quoted=False) - else: - style = opts.get('style') - - # ui settings - if not (tmpl or style): - tmpl = ui.config('ui', 'logtemplate') - if tmpl: - tmpl = templater.parsestring(tmpl) - else: - style = ui.config('ui', 'style') - - if not (tmpl or style): - return changeset_printer(ui, repo, patch, opts, buffered) - - mapfile = None - if style and not tmpl: - mapfile = style - if not os.path.split(mapfile)[0]: - mapname = (templater.templatepath('map-cmdline.' + mapfile) - or templater.templatepath(mapfile)) - if mapname: mapfile = mapname - - try: - t = changeset_templater(ui, repo, patch, opts, mapfile, buffered) - except SyntaxError, inst: - raise util.Abort(inst.args[0]) - if tmpl: t.use_template(tmpl) - return t - -def finddate(ui, repo, date): - """Find the tipmost changeset that matches the given date spec""" - df = util.matchdate(date) - get = util.cachefunc(lambda r: repo[r].changeset()) - changeiter, matchfn = walkchangerevs(ui, repo, [], get, {'rev':None}) - results = {} - for st, rev, fns in changeiter: - if st == 'add': - d = get(rev)[2] - if df(d[0]): - results[rev] = d - elif st == 'iter': - if rev in results: - 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 walkchangerevs(ui, repo, pats, change, opts): - '''Iterate over files and the revs in which they changed. - - Callers most commonly need to iterate backwards over the history - in which they are interested. Doing so has awful (quadratic-looking) - performance, so we use iterators in a "windowed" way. - - We walk a window of revisions in the desired order. Within the - window, we first walk forwards to gather data, then in the desired - order (usually backwards) to display it. - - This function returns an (iterator, matchfn) tuple. The iterator - yields 3-tuples. They will be of one of the following forms: - - "window", incrementing, lastrev: stepping through a window, - positive if walking forwards through revs, last rev in the - sequence iterated over - use to reset state for the current window - - "add", rev, fns: out-of-order traversal of the given filenames - fns, which changed during revision rev - use to gather data for - possible display - - "iter", rev, None: in-order traversal of the revs earlier iterated - over with "add" - use to display data''' - - 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 - - m = match(repo, pats, opts) - follow = opts.get('follow') or opts.get('follow_first') - - if not len(repo): - return [], m - - if follow: - defrange = '%s:0' % repo['.'].rev() - else: - defrange = '-1:0' - revs = revrange(repo, opts['rev'] or [defrange]) - wanted = set() - slowpath = m.anypats() or (m.files() and opts.get('removed')) - fncache = {} - - if not slowpath and not m.files(): - # No files, no patterns. Display all revs. - wanted = set(revs) - copies = [] - if not slowpath: - # Only files, no patterns. Check the history of each file. - def filerevgen(filelog, node): - cl_count = len(repo) - if node is None: - last = len(filelog) - 1 - else: - last = filelog.rev(node) - for i, window in increasing_windows(last, nullrev): - revs = [] - for j in xrange(i - window, i + 1): - n = filelog.node(j) - revs.append((filelog.linkrev(j), - follow and filelog.renamed(n))) - for rev in reversed(revs): - # only yield rev for which we have the changelog, it can - # happen while doing "hg log" during a pull or commit - if rev[0] < cl_count: - yield rev - def iterfiles(): - for filename in m.files(): - yield filename, None - for filename_node in copies: - yield filename_node - minrev, maxrev = min(revs), max(revs) - for file_, node in iterfiles(): - filelog = repo.file(file_) - if not len(filelog): - if node is None: - # 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"') % file_) - slowpath = True - break - else: - ui.warn(_('%s:%s copy source revision cannot be found!\n') - % (file_, short(node))) - continue - for rev, copied in filerevgen(filelog, node): - if rev <= maxrev: - if rev < minrev: - break - fncache.setdefault(rev, []) - fncache[rev].append(file_) - wanted.add(rev) - if follow and copied: - copies.append(copied) - if slowpath: - if follow: - raise util.Abort(_('can only follow copies/renames for explicit ' - 'filenames')) - - # The slow path checks files modified in every changeset. - def changerevgen(): - for i, window in increasing_windows(len(repo) - 1, nullrev): - for j in xrange(i - window, i + 1): - yield j, change(j)[3] - - for rev, changefiles in changerevgen(): - matches = filter(m, changefiles) - if matches: - fncache[rev] = matches - wanted.add(rev) - - class followfilter(object): - def __init__(self, onlyfirst=False): - self.startrev = nullrev - self.roots = [] - self.onlyfirst = onlyfirst - - def match(self, rev): - def realparents(rev): - if self.onlyfirst: - return repo.changelog.parentrevs(rev)[0:1] - else: - return filter(lambda x: x != nullrev, - repo.changelog.parentrevs(rev)) - - if self.startrev == nullrev: - self.startrev = rev - return True - - if rev > self.startrev: - # forward: all descendants - if not self.roots: - self.roots.append(self.startrev) - for parent in realparents(rev): - if parent in self.roots: - self.roots.append(rev) - return True - else: - # backwards: all parents - if not self.roots: - self.roots.extend(realparents(self.startrev)) - if rev in self.roots: - self.roots.remove(rev) - self.roots.extend(realparents(rev)) - return True - - return False - - # 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.changelog.rev(repo.lookup(rev)) - ff = followfilter() - stop = min(revs[0], revs[-1]) - for x in xrange(rev, stop-1, -1): - if ff.match(x): - wanted.discard(x) - - def iterate(): - if follow and not m.files(): - ff = followfilter(onlyfirst=opts.get('follow_first')) - def want(rev): - return ff.match(rev) and rev in wanted - else: - def want(rev): - return rev in wanted - - for i, window in increasing_windows(0, len(revs)): - yield 'window', revs[0] < revs[-1], revs[-1] - nrevs = [rev for rev in revs[i:i+window] if want(rev)] - for rev in sorted(nrevs): - fns = fncache.get(rev) - if not fns: - def fns_generator(): - for f in change(rev)[3]: - if m(f): - yield f - fns = fns_generator() - yield 'add', rev, fns - for rev in nrevs: - yield 'iter', rev, None - return iterate(), m - -def commit(ui, repo, commitfunc, pats, opts): - '''commit the specified files or all outstanding changes''' - date = opts.get('date') - if date: - opts['date'] = util.parsedate(date) - message = logmessage(opts) - - # extract addremove carefully -- this function can be called from a command - # that doesn't support addremove - if opts.get('addremove'): - addremove(repo, pats, opts) - - return commitfunc(ui, repo, message, match(repo, pats, opts), opts) - -def commiteditor(repo, ctx, subs): - if ctx.description(): - return ctx.description() - return commitforceeditor(repo, ctx, subs) - -def commitforceeditor(repo, ctx, subs): - edittext = [] - modified, added, removed = ctx.modified(), ctx.added(), ctx.removed() - if ctx.description(): - edittext.append(ctx.description()) - edittext.append("") - edittext.append("") # Empty line between message and comments. - edittext.append(_("HG: Enter commit message." - " Lines beginning with 'HG:' are removed.")) - edittext.append(_("HG: Leave message empty to abort commit.")) - edittext.append("HG: --") - edittext.append(_("HG: user: %s") % ctx.user()) - if ctx.p2(): - edittext.append(_("HG: branch merge")) - if ctx.branch(): - edittext.append(_("HG: branch '%s'") - % encoding.tolocal(ctx.branch())) - edittext.extend([_("HG: subrepo %s") % s for s in subs]) - edittext.extend([_("HG: added %s") % f for f in added]) - edittext.extend([_("HG: changed %s") % f for f in modified]) - edittext.extend([_("HG: removed %s") % f for f in removed]) - if not added and not modified and not removed: - edittext.append(_("HG: no files changed")) - edittext.append("") - # run editor in the repository root - olddir = os.getcwd() - os.chdir(repo.root) - text = repo.ui.edit("\n".join(edittext), ctx.user()) - text = re.sub("(?m)^HG:.*\n", "", text) - os.chdir(olddir) - - if not text.strip(): - raise util.Abort(_("empty commit message")) - - return text |