Mercurial > hg > gitweb
view mercurial/commands.py @ 2167:f5c2c6e69fd7
merge with crew.
author | Vadim Gelfer <vadim.gelfer@gmail.com> |
---|---|
date | Sun, 30 Apr 2006 16:30:57 -0700 |
parents | d0c02b4dce9a d821918e3bee |
children | b2ae81a7df29 |
line wrap: on
line source
# commands.py - command processing for mercurial # # Copyright 2005 Matt Mackall <mpm@selenic.com> # # This software may be used and distributed according to the terms # of the GNU General Public License, incorporated herein by reference. from demandload import demandload from node import * from i18n import gettext as _ demandload(globals(), "os re sys signal shutil imp urllib pdb") demandload(globals(), "fancyopts ui hg util lock revlog templater bundlerepo") demandload(globals(), "fnmatch hgweb mdiff random signal tempfile time") demandload(globals(), "traceback errno socket version struct atexit sets bz2") demandload(globals(), "archival changegroup") class UnknownCommand(Exception): """Exception raised if command is not in the command table.""" class AmbiguousCommand(Exception): """Exception raised if command shortcut matches more than one command.""" def bail_if_changed(repo): modified, added, removed, deleted, unknown = repo.changes() if modified or added or removed or deleted: raise util.Abort(_("outstanding uncommitted changes")) def filterfiles(filters, files): l = [x for x in files if x in filters] for t in filters: if t and t[-1] != "/": t += "/" l += [x for x in files if x.startswith(t)] return l def relpath(repo, args): cwd = repo.getcwd() if cwd: return [util.normpath(os.path.join(cwd, x)) for x in args] return args def matchpats(repo, pats=[], opts={}, head=''): cwd = repo.getcwd() if not pats and cwd: opts['include'] = [os.path.join(cwd, i) for i in opts['include']] opts['exclude'] = [os.path.join(cwd, x) for x in opts['exclude']] cwd = '' return util.cmdmatcher(repo.root, cwd, pats or ['.'], opts.get('include'), opts.get('exclude'), head) def makewalk(repo, pats, opts, node=None, head='', badmatch=None): files, matchfn, anypats = matchpats(repo, pats, opts, head) exact = dict(zip(files, files)) def walk(): for src, fn in repo.walk(node=node, files=files, match=matchfn, badmatch=badmatch): yield src, fn, util.pathto(repo.getcwd(), fn), fn in exact return files, matchfn, walk() def walk(repo, pats, opts, node=None, head='', badmatch=None): files, matchfn, results = makewalk(repo, pats, opts, node, head, badmatch) for r in results: yield r def walkchangerevs(ui, repo, pats, opts): '''Iterate over files and the revs they changed in. Callers most commonly need to iterate backwards over the history it is interested in. 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, getchange, matchfn) tuple. The getchange function returns the changelog entry for a numeric revision. 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 file names 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 files, matchfn, anypats = matchpats(repo, pats, opts) if repo.changelog.count() == 0: return [], False, matchfn revs = map(int, revrange(ui, repo, opts['rev'] or ['tip:0'])) wanted = {} slowpath = anypats fncache = {} chcache = {} def getchange(rev): ch = chcache.get(rev) if ch is None: chcache[rev] = ch = repo.changelog.read(repo.lookup(str(rev))) return ch if not slowpath and not files: # No files, no patterns. Display all revs. wanted = dict(zip(revs, revs)) if not slowpath: # Only files, no patterns. Check the history of each file. def filerevgen(filelog): for i, window in increasing_windows(filelog.count()-1, -1): revs = [] for j in xrange(i - window, i + 1): revs.append(filelog.linkrev(filelog.node(j))) revs.reverse() for rev in revs: yield rev minrev, maxrev = min(revs), max(revs) for file_ in files: filelog = repo.file(file_) # A zero count may be a directory or deleted file, so # try to find matching entries on the slow path. if filelog.count() == 0: slowpath = True break for rev in filerevgen(filelog): if rev <= maxrev: if rev < minrev: break fncache.setdefault(rev, []) fncache[rev].append(file_) wanted[rev] = 1 if slowpath: # The slow path checks files modified in every changeset. def changerevgen(): for i, window in increasing_windows(repo.changelog.count()-1, -1): for j in xrange(i - window, i + 1): yield j, getchange(j)[3] for rev, changefiles in changerevgen(): matches = filter(matchfn, changefiles) if matches: fncache[rev] = matches wanted[rev] = 1 def iterate(): 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 rev in wanted] srevs = list(nrevs) srevs.sort() for rev in srevs: fns = fncache.get(rev) or filter(matchfn, getchange(rev)[3]) yield 'add', rev, fns for rev in nrevs: yield 'iter', rev, None return iterate(), getchange, matchfn revrangesep = ':' def revrange(ui, repo, revs, revlog=None): """Yield revision as strings from a list of revision specifications.""" if revlog is None: revlog = repo.changelog revcount = revlog.count() def fix(val, defval): if not val: return defval try: num = int(val) if str(num) != val: raise ValueError if num < 0: num += revcount if num < 0: num = 0 elif num >= revcount: raise ValueError except ValueError: try: num = repo.changelog.rev(repo.lookup(val)) except KeyError: try: num = revlog.rev(revlog.lookup(val)) except KeyError: raise util.Abort(_('invalid revision identifier %s'), val) return num seen = {} for spec in revs: if spec.find(revrangesep) >= 0: start, end = spec.split(revrangesep, 1) start = fix(start, 0) end = fix(end, revcount - 1) step = start > end and -1 or 1 for rev in xrange(start, end+step, step): if rev in seen: continue seen[rev] = 1 yield str(rev) else: rev = fix(spec, None) if rev in seen: continue seen[rev] = 1 yield str(rev) def make_filename(repo, r, pat, node=None, total=None, seqno=None, revwidth=None, pathname=None): node_expander = { 'H': lambda: hex(node), 'R': lambda: str(r.rev(node)), 'h': lambda: short(node), } expander = { '%': lambda: '%', 'b': lambda: os.path.basename(repo.root), } try: if node: expander.update(node_expander) if node and revwidth is not None: expander['r'] = lambda: str(r.rev(node)).zfill(revwidth) 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 file name"), inst.args[0]) def make_file(repo, r, pat, node=None, total=None, seqno=None, revwidth=None, mode='wb', pathname=None): if not pat or pat == '-': return 'w' in mode and sys.stdout or sys.stdin if hasattr(pat, 'write') and 'w' in mode: return pat if hasattr(pat, 'read') and 'r' in mode: return pat return open(make_filename(repo, r, pat, node, total, seqno, revwidth, pathname), mode) def write_bundle(cg, filename=None, compress=True): """Write a bundle file and return its filename. Existing files will not be overwritten. If no filename is specified, a temporary file is created. bz2 compression can be turned off. The bundle file will be deleted in case of errors. """ class nocompress(object): def compress(self, x): return x def flush(self): return "" fh = None cleanup = None try: if filename: if os.path.exists(filename): raise util.Abort(_("file '%s' already exists"), filename) fh = open(filename, "wb") else: fd, filename = tempfile.mkstemp(prefix="hg-bundle-", suffix=".hg") fh = os.fdopen(fd, "wb") cleanup = filename if compress: fh.write("HG10") z = bz2.BZ2Compressor(9) else: fh.write("HG10UN") z = nocompress() # parse the changegroup data, otherwise we will block # in case of sshrepo because we don't know the end of the stream # an empty chunkiter is the end of the changegroup empty = False while not empty: empty = True for chunk in changegroup.chunkiter(cg): empty = False fh.write(z.compress(changegroup.genchunk(chunk))) fh.write(z.compress(changegroup.closechunk())) fh.write(z.flush()) cleanup = None return filename finally: if fh is not None: fh.close() if cleanup is not None: os.unlink(cleanup) def dodiff(fp, ui, repo, node1, node2, files=None, match=util.always, changes=None, text=False, opts={}): if not node1: node1 = repo.dirstate.parents()[0] # reading the data for node1 early allows it to play nicely # with repo.changes and the revlog cache. change = repo.changelog.read(node1) mmap = repo.manifest.read(change[0]) date1 = util.datestr(change[2]) if not changes: changes = repo.changes(node1, node2, files, match=match) modified, added, removed, deleted, unknown = changes if files: modified, added, removed = map(lambda x: filterfiles(files, x), (modified, added, removed)) if not modified and not added and not removed: return if node2: change = repo.changelog.read(node2) mmap2 = repo.manifest.read(change[0]) date2 = util.datestr(change[2]) def read(f): return repo.file(f).read(mmap2[f]) else: date2 = util.datestr() def read(f): return repo.wread(f) if ui.quiet: r = None else: hexfunc = ui.verbose and hex or short r = [hexfunc(node) for node in [node1, node2] if node] diffopts = ui.diffopts() showfunc = opts.get('show_function') or diffopts['showfunc'] ignorews = opts.get('ignore_all_space') or diffopts['ignorews'] for f in modified: to = None if f in mmap: to = repo.file(f).read(mmap[f]) tn = read(f) fp.write(mdiff.unidiff(to, date1, tn, date2, f, r, text=text, showfunc=showfunc, ignorews=ignorews)) for f in added: to = None tn = read(f) fp.write(mdiff.unidiff(to, date1, tn, date2, f, r, text=text, showfunc=showfunc, ignorews=ignorews)) for f in removed: to = repo.file(f).read(mmap[f]) tn = None fp.write(mdiff.unidiff(to, date1, tn, date2, f, r, text=text, showfunc=showfunc, ignorews=ignorews)) def trimuser(ui, name, rev, revcache): """trim the name of the user who committed a change""" user = revcache.get(rev) if user is None: user = revcache[rev] = ui.shortuser(name) return user class changeset_templater(object): '''use templater module to format changeset information.''' def __init__(self, ui, repo, mapfile): self.t = templater.templater(mapfile, templater.common_filters, cache={'parent': '{rev}:{node|short} ', 'manifest': '{rev}:{node|short}'}) self.ui = ui self.repo = repo def use_template(self, t): '''set template string to use''' self.t.cache['changeset'] = t def write(self, thing, header=False): '''write expanded template. uses in-order recursive traverse of iterators.''' for t in thing: if hasattr(t, '__iter__'): self.write(t, header=header) elif header: self.ui.write_header(t) else: self.ui.write(t) def write_header(self, thing): self.write(thing, header=True) def show(self, rev=0, changenode=None, brinfo=None): '''show a single changeset or file revision''' log = self.repo.changelog if changenode is None: changenode = log.node(rev) elif not rev: rev = log.rev(changenode) changes = log.read(changenode) 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) if brinfo: def showbranches(**args): if changenode in brinfo: for x in showlist('branch', brinfo[changenode], plural='branches', **args): yield x else: showbranches = '' if self.ui.debugflag: def showmanifest(**args): args = args.copy() args.update(dict(rev=self.repo.manifest.rev(changes[0]), node=hex(changes[0]))) yield self.t('manifest', **args) else: showmanifest = '' def showparents(**args): parents = [[('rev', log.rev(p)), ('node', hex(p))] for p in log.parents(changenode) if self.ui.debugflag or p != nullid] if (not self.ui.debugflag and len(parents) == 1 and parents[0][0][1] == rev - 1): return for x in showlist('parent', parents, **args): yield x def showtags(**args): for x in showlist('tag', self.repo.nodetags(changenode), **args): yield x if self.ui.debugflag: files = self.repo.changes(log.parents(changenode)[0], changenode) def showfiles(**args): for x in showlist('file', files[0], **args): yield x def showadds(**args): for x in showlist('file_add', files[1], **args): yield x def showdels(**args): for x in showlist('file_del', files[2], **args): yield x else: def showfiles(**args): for x in showlist('file', changes[3], **args): yield x showadds = '' showdels = '' props = { 'author': changes[1], 'branches': showbranches, 'date': changes[2], 'desc': changes[4], 'file_adds': showadds, 'file_dels': showdels, 'files': showfiles, 'manifest': showmanifest, 'node': hex(changenode), 'parents': showparents, 'rev': rev, 'tags': showtags, } try: if self.ui.debugflag and 'header_debug' in self.t: key = 'header_debug' elif self.ui.quiet and 'header_quiet' in self.t: key = 'header_quiet' elif self.ui.verbose and 'header_verbose' in self.t: key = 'header_verbose' elif 'header' in self.t: key = 'header' else: key = '' if key: self.write_header(self.t(key, **props)) if self.ui.debugflag and 'changeset_debug' in self.t: key = 'changeset_debug' elif self.ui.quiet and 'changeset_quiet' in self.t: key = 'changeset_quiet' elif self.ui.verbose and 'changeset_verbose' in self.t: key = 'changeset_verbose' else: key = 'changeset' self.write(self.t(key, **props)) except KeyError, inst: raise util.Abort(_("%s: no key named '%s'") % (self.t.mapfile, inst.args[0])) except SyntaxError, inst: raise util.Abort(_('%s: %s') % (self.t.mapfile, inst.args[0])) class changeset_printer(object): '''show changeset information when templating not requested.''' def __init__(self, ui, repo): self.ui = ui self.repo = repo def show(self, rev=0, changenode=None, brinfo=None): '''show a single changeset or file revision''' log = self.repo.changelog if changenode is None: changenode = log.node(rev) elif not rev: rev = log.rev(changenode) if self.ui.quiet: self.ui.write("%d:%s\n" % (rev, short(changenode))) return changes = log.read(changenode) date = util.datestr(changes[2]) parents = [(log.rev(p), self.ui.verbose and hex(p) or short(p)) for p in log.parents(changenode) if self.ui.debugflag or p != nullid] if (not self.ui.debugflag and len(parents) == 1 and parents[0][0] == rev-1): parents = [] if self.ui.verbose: self.ui.write(_("changeset: %d:%s\n") % (rev, hex(changenode))) else: self.ui.write(_("changeset: %d:%s\n") % (rev, short(changenode))) for tag in self.repo.nodetags(changenode): self.ui.status(_("tag: %s\n") % tag) for parent in parents: self.ui.write(_("parent: %d:%s\n") % parent) if brinfo and changenode in brinfo: br = brinfo[changenode] self.ui.write(_("branch: %s\n") % " ".join(br)) self.ui.debug(_("manifest: %d:%s\n") % (self.repo.manifest.rev(changes[0]), hex(changes[0]))) self.ui.status(_("user: %s\n") % changes[1]) self.ui.status(_("date: %s\n") % date) if self.ui.debugflag: files = self.repo.changes(log.parents(changenode)[0], changenode) for key, value in zip([_("files:"), _("files+:"), _("files-:")], files): if value: self.ui.note("%-12s %s\n" % (key, " ".join(value))) else: self.ui.note(_("files: %s\n") % " ".join(changes[3])) description = changes[4].strip() if description: if self.ui.verbose: self.ui.status(_("description:\n")) self.ui.status(description) self.ui.status("\n\n") else: self.ui.status(_("summary: %s\n") % description.splitlines()[0]) self.ui.status("\n") def show_changeset(ui, repo, opts): '''show one changeset. uses template or regular display. caller can pass in 'style' and 'template' options in opts.''' tmpl = opts.get('template') if tmpl: tmpl = templater.parsestring(tmpl, quoted=False) else: tmpl = ui.config('ui', 'logtemplate') if tmpl: tmpl = templater.parsestring(tmpl) mapfile = opts.get('style') or ui.config('ui', 'style') if tmpl or mapfile: if mapfile: if not os.path.isfile(mapfile): mapname = templater.templatepath('map-cmdline.' + mapfile) if not mapname: mapname = templater.templatepath(mapfile) if mapname: mapfile = mapname try: t = changeset_templater(ui, repo, mapfile) except SyntaxError, inst: raise util.Abort(inst.args[0]) if tmpl: t.use_template(tmpl) return t return changeset_printer(ui, repo) def show_version(ui): """output version and copyright information""" ui.write(_("Mercurial Distributed SCM (version %s)\n") % version.get_version()) ui.status(_( "\nCopyright (C) 2005 Matt Mackall <mpm@selenic.com>\n" "This is free software; see the source for copying conditions. " "There is NO\nwarranty; " "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n" )) def help_(ui, cmd=None, with_version=False): """show help for a given command or all commands""" option_lists = [] if cmd and cmd != 'shortlist': if with_version: show_version(ui) ui.write('\n') aliases, i = find(cmd) # synopsis ui.write("%s\n\n" % i[2]) # description doc = i[0].__doc__ if not doc: doc = _("(No help text available)") if ui.quiet: doc = doc.splitlines(0)[0] ui.write("%s\n" % doc.rstrip()) if not ui.quiet: # aliases if len(aliases) > 1: ui.write(_("\naliases: %s\n") % ', '.join(aliases[1:])) # options if i[1]: option_lists.append(("options", i[1])) else: # program name if ui.verbose or with_version: show_version(ui) else: ui.status(_("Mercurial Distributed SCM\n")) ui.status('\n') # list of commands if cmd == "shortlist": ui.status(_('basic commands (use "hg help" ' 'for the full list or option "-v" for details):\n\n')) elif ui.verbose: ui.status(_('list of commands:\n\n')) else: ui.status(_('list of commands (use "hg help -v" ' 'to show aliases and global options):\n\n')) h = {} cmds = {} for c, e in table.items(): f = c.split("|")[0] if cmd == "shortlist" and not f.startswith("^"): continue f = f.lstrip("^") if not ui.debugflag and f.startswith("debug"): continue doc = e[0].__doc__ if not doc: doc = _("(No help text available)") h[f] = doc.splitlines(0)[0].rstrip() cmds[f] = c.lstrip("^") fns = h.keys() fns.sort() m = max(map(len, fns)) for f in fns: if ui.verbose: commands = cmds[f].replace("|",", ") ui.write(" %s:\n %s\n"%(commands, h[f])) else: ui.write(' %-*s %s\n' % (m, f, h[f])) # global options if ui.verbose: option_lists.append(("global options", globalopts)) # list all option lists opt_output = [] for title, options in option_lists: opt_output.append(("\n%s:\n" % title, None)) for shortopt, longopt, default, desc in options: