# HG changeset patch # User mpm@selenic.com # Date 1120357794 28800 # Node ID 353a2ce50423697208900f84384c04c6509cb4aa # Parent ffeb2c3a19669102df8110b513fa163d0da36e7f [PATCH] New export patch -----BEGIN PGP SIGNED MESSAGE----- Hash: SHA1 [PATCH] New export patch From: Bryan O'Sullivan Modify export command to accept rev ranges and output file spec. It can now export a range of revisions, and print exported patches to files whose names are generated using format strings. manifest hash: e0085c205cdc31a168bcd25c85772ef00d53031d -----BEGIN PGP SIGNATURE----- Version: GnuPG v1.4.0 (GNU/Linux) iD8DBQFCx02iywK+sNU5EO8RAtCKAJ0V2K9+i1OGa27KyC5/nq3m+OdvtgCgpnav 3vfEODMzJVOZoJt9wzI1UCg= =YAdI -----END PGP SIGNATURE----- diff -r ffeb2c3a1966 -r 353a2ce50423 doc/hg.1.txt --- a/doc/hg.1.txt Sat Jul 02 18:25:15 2005 -0800 +++ b/doc/hg.1.txt Sat Jul 02 18:29:54 2005 -0800 @@ -129,11 +129,27 @@ revisions are specified, the working directory files are compared to its parent. -export [revision]:: - Print the changeset header and diffs for a particular revision. +export [-o filespec] [revision] ...:: + Print the changeset header and diffs for one or more revisions. + + The information shown in the changeset header is: author, + changeset hash, parent and commit comment. + + Output may be to a file, in which case the name of the file is + given using a format string. The formatting rules are as follows: - The information shown in the changeset header is: author, changeset hash, - parent and commit comment. + %% literal "%" character + %H changeset hash (40 bytes of hexadecimal) + %N number of patches being generated + %R changeset revision number + %b basename of the exporting repository + %h short-form changeset hash (12 bytes of hexadecimal) + %n zero-padded sequence number, starting at 1 + %r zero-padded changeset revision number + + Options: + + -o, --output print output to file with formatted named forget [files]:: Undo an 'hg add' scheduled for the next commit. @@ -317,6 +333,49 @@ the changelog, manifest, and tracked files, as well as the integrity of their crosslinks and indices. +SPECIFYING SINGLE REVISIONS +--------------------------- + + Mercurial accepts several notations for identifying individual + revisions. + + A plain integer is treated as a revision number. Negative + integers are treated as offsets from the tip, with -1 denoting the + tip. + + A 40-digit hexadecimal string is treated as a unique revision + identifier. + + A hexadecimal string less than 40 characters long is treated as a + unique revision identifier, and referred to as a short-form + identifier. A short-form identifier is only valid if it is the + prefix of one full-length identifier. + + Any other string is treated as a tag name, which is a symbolic + name associated with a revision identifier. Tag names may not + contain the ":" character. + + The reserved name "tip" is a special tag that always identifies + the most recent revision. + +SPECIFYING MULTIPLE REVISIONS +----------------------------- + + When Mercurial accepts more than one revision, they may be + specified individually, or provided as a continuous range, + separated by the ":" character. + + The syntax of range notation is [BEGIN]:[END], where BEGIN and END + are revision identifiers. Both BEGIN and END are optional. If + BEGIN is not specified, it defaults to revision number 0. If END + is not specified, it defaults to the tip. The range ":" thus + means "all revisions". + + If BEGIN is greater than END, revisions are treated in reverse + order. + + A range acts as an open interval. This means that a range of 3:5 + gives 3, 4 and 5. Similarly, a range of 4:2 gives 4, 3, and 2. ENVIRONMENT VARIABLES --------------------- diff -r ffeb2c3a1966 -r 353a2ce50423 mercurial/commands.py --- a/mercurial/commands.py Sat Jul 02 18:25:15 2005 -0800 +++ b/mercurial/commands.py Sat Jul 02 18:29:54 2005 -0800 @@ -33,7 +33,47 @@ for x in args ] return args -def dodiff(ui, repo, files = None, node1 = None, node2 = None): +revrangesep = ':' + +def revrange(ui, repo, revs = [], revlog = None): + 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 not (0 <= 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: + ui.warn('abort: invalid revision identifier %s\n' % val) + sys.exit(1) + return num + for spec in revs: + if spec.find(revrangesep) >= 0: + start, end = spec.split(revrangesep, 1) + start = fix(start, 0) + end = fix(end, revcount - 1) + if end > start: + end += 1 + step = 1 + else: + end -= 1 + step = -1 + for rev in xrange(start, end, step): + yield str(rev) + else: + yield spec + +def dodiff(fp, ui, repo, files = None, node1 = None, node2 = None): def date(c): return time.asctime(time.gmtime(float(c[2].split(' ')[0]))) @@ -70,15 +110,15 @@ if f in mmap: to = repo.file(f).read(mmap[f]) tn = read(f) - sys.stdout.write(mdiff.unidiff(to, date1, tn, date2, f, r)) + fp.write(mdiff.unidiff(to, date1, tn, date2, f, r)) for f in a: to = None tn = read(f) - sys.stdout.write(mdiff.unidiff(to, date1, tn, date2, f, r)) + fp.write(mdiff.unidiff(to, date1, tn, date2, f, r)) for f in d: to = repo.file(f).read(mmap[f]) tn = None - sys.stdout.write(mdiff.unidiff(to, date1, tn, date2, f, r)) + fp.write(mdiff.unidiff(to, date1, tn, date2, f, r)) def show_changeset(ui, repo, rev=0, changenode=None, filelog=None): """show a single changeset or file revision""" @@ -421,24 +461,68 @@ else: files = relpath(repo, [""]) - dodiff(ui, repo, files, *revs) + dodiff(sys.stdout, ui, repo, files, *revs) -def export(ui, repo, changeset): - """dump the changeset header and diffs for a revision""" +def doexport(ui, repo, changeset, seqno, total, revwidth, opts): node = repo.lookup(changeset) prev, other = repo.changelog.parents(node) change = repo.changelog.read(node) - print "# HG changeset patch" - print "# User %s" % change[1] - print "# Node ID %s" % hg.hex(node) - print "# Parent %s" % hg.hex(prev) - print + + def expand(name): + expansions = { + '%': lambda: '%', + 'H': lambda: hg.hex(node), + 'N': lambda: str(total), + 'R': lambda: str(repo.changelog.rev(node)), + 'b': lambda: os.path.basename(repo.root), + 'h': lambda: hg.short(node), + 'n': lambda: str(seqno).zfill(len(str(total))), + 'r': lambda: str(repo.changelog.rev(node)).zfill(revwidth), + } + newname = [] + namelen = len(name) + i = 0 + while i < namelen: + c = name[i] + if c == '%': + i += 1 + c = name[i] + c = expansions[c]() + newname.append(c) + i += 1 + return ''.join(newname) + + if opts['output'] and opts['output'] != '-': + try: + fp = open(expand(opts['output']), 'w') + except KeyError, inst: + ui.warn("error: invalid format spec '%%%s' in output file name\n" % + inst.args[0]) + sys.exit(1) + else: + fp = sys.stdout + + print >> fp, "# HG changeset patch" + print >> fp, "# User %s" % change[1] + print >> fp, "# Node ID %s" % hg.hex(node) + print >> fp, "# Parent %s" % hg.hex(prev) + print >> fp if other != hg.nullid: - print "# Parent %s" % hg.hex(other) - print change[4].rstrip() - print + print >> fp, "# Parent %s" % hg.hex(other) + print >> fp, change[4].rstrip() + print >> fp + + dodiff(fp, ui, repo, None, prev, node) - dodiff(ui, repo, None, prev, node) +def export(ui, repo, *changesets, **opts): + """dump the header and diffs for one or more changesets""" + seqno = 0 + revs = list(revrange(ui, repo, changesets)) + total = len(revs) + revwidth = max(len(revs[0]), len(revs[-1])) + for cset in revs: + seqno += 1 + doexport(ui, repo, cset, seqno, total, revwidth, opts) def forget(ui, repo, file, *files): """don't add the specified files on the next commit""" @@ -585,7 +669,7 @@ if cg and not r: if opts['update']: return update(ui, repo) - else: + else: ui.status("(run 'hg update' to get a working copy)\n") return r @@ -679,15 +763,18 @@ """add a tag for the current tip or a given revision""" if name == "tip": - ui.warn("abort: 'tip' is a reserved name!\n") - return -1 + ui.warn("abort: 'tip' is a reserved name!\n") + return -1 + if name.find(revrangesep) >= 0: + ui.warn("abort: '%s' cannot be used in a tag name\n" % revrangesep) + return -1 (c, a, d, u) = repo.changes(None, None) for x in (c, a, d, u): - if ".hgtags" in x: - ui.warn("abort: working copy of .hgtags is changed!\n") + if ".hgtags" in x: + ui.warn("abort: working copy of .hgtags is changed!\n") ui.status("(please commit .hgtags manually)\n") - return -1 + return -1 if rev: r = hg.hex(repo.lookup(rev)) @@ -773,7 +860,8 @@ "debugindexdot": (debugindexdot, [], 'debugindexdot '), "diff": (diff, [('r', 'rev', [], 'revision')], 'hg diff [-r A] [-r B] [files]'), - "export": (export, [], "hg export "), + "export": (export, [('o', 'output', "", 'output to file')], + "hg export [-o file] ..."), "forget": (forget, [], "hg forget [files]"), "heads": (heads, [], 'hg heads'), "help": (help, [], 'hg help [command]'), @@ -790,7 +878,7 @@ "parents": (parents, [], 'hg parents [node]'), "pull": (pull, [('u', 'update', None, 'update working directory')], - 'hg pull [options] [source]'), + 'hg pull [options] [source]'), "push": (push, [], 'hg push '), "rawcommit": (rawcommit, [('p', 'parent', [], 'parent'), @@ -943,4 +1031,3 @@ help(u, cmd) sys.exit(-1) -