# HG changeset patch # User Matt Mackall # Date 1140219545 21600 # Node ID 2c9872a4f3fdde6b2960234c100fbd09e1e946a9 # Parent ef8cd889a78b30148b84d5dd71f585149e9a76c7# Parent 50de0887bbcd90a8d375823b2a338f58e3cbe7c8 Merge with crew diff --git a/doc/hg.1.txt b/doc/hg.1.txt --- a/doc/hg.1.txt +++ b/doc/hg.1.txt @@ -223,9 +223,11 @@ diff [-a] [-r revision] [-r revision] [f probably with undesirable results. options: - -a, --text treat all files as text - -I, --include include names matching the given patterns - -X, --exclude exclude names matching the given patterns + -a, --text treat all files as text + -I, --include include names matching the given patterns + -p, --show-function show which function each change is in + -X, --exclude exclude names matching the given patterns + -w, --ignore-all-space ignore white space when comparing lines export [-o filespec] [revision] ...:: Print the changeset header and diffs for one or more revisions. @@ -600,9 +602,12 @@ tags:: This lists both regular and local tags. -tip:: +tip [-p]:: Show the tip revision. + options: + -p, --patch show patch + unbundle :: (EXPERIMENTAL) diff --git a/doc/hgrc.5.txt b/doc/hgrc.5.txt --- a/doc/hgrc.5.txt +++ b/doc/hgrc.5.txt @@ -145,20 +145,71 @@ hooks:: incoming.email = /my/email/hook incoming.autobuild = /my/build/hook + Most hooks are run with environment variables set that give added + useful information. For each hook below, the environment variables + it is passed are listed with names of the form "$HG_foo". + changegroup;; - Run after a changegroup has been added via push or pull. Passed - the ID of the first new changeset in $NODE. + Run after a changegroup has been added via push, pull or + unbundle. ID of the first new changeset is in $HG_NODE. commit;; Run after a changeset has been created in the local repository. - Passed the ID of the newly created changeset in environment - variable $NODE. + ID of the newly created changeset is in $HG_NODE. Parent + changeset IDs are in $HG_PARENT1 and $HG_PARENT2. incoming;; Run after a changeset has been pulled, pushed, or unbundled into - the local repository. Passed the ID of the newly arrived - changeset in environment variable $NODE. + the local repository. The ID of the newly arrived changeset is in + $HG_NODE. + outgoing;; + Run after sending changes from local repository to another. ID of + first changeset sent is in $HG_NODE. Source of operation is in + $HG_SOURCE; see "preoutgoing" hook for description. + prechangegroup;; + Run before a changegroup is added via push, pull or unbundle. + Exit status 0 allows the changegroup to proceed. Non-zero status + will cause the push, pull or unbundle to fail. precommit;; - Run before starting a commit. Exit status 0 allows the commit to - proceed. Non-zero status will cause the commit to fail. + Run before starting a local commit. Exit status 0 allows the + commit to proceed. Non-zero status will cause the commit to fail. + Parent changeset IDs are in $HG_PARENT1 and $HG_PARENT2. + preoutgoing;; + Run before computing changes to send from the local repository to + another. Non-zero status will cause failure. This lets you + prevent pull over http or ssh. Also prevents against local pull, + push (outbound) or bundle commands, but not effective, since you + can just copy files instead then. Source of operation is in + $HG_SOURCE. If "serve", operation is happening on behalf of + remote ssh or http repository. If "push", "pull" or "bundle", + operation is happening on behalf of repository on same system. + pretag;; + Run before creating a tag. Exit status 0 allows the tag to be + created. Non-zero status will cause the tag to fail. ID of + changeset to tag is in $HG_NODE. Name of tag is in $HG_TAG. Tag + is local if $HG_LOCAL=1, in repo if $HG_LOCAL=0. + pretxnchangegroup;; + Run after a changegroup has been added via push, pull or unbundle, + but before the transaction has been committed. Changegroup is + visible to hook program. This lets you validate incoming changes + before accepting them. Passed the ID of the first new changeset + in $HG_NODE. Exit status 0 allows the transaction to commit. + Non-zero status will cause the transaction to be rolled back and + the push, pull or unbundle will fail. + pretxncommit;; + Run after a changeset has been created but the transaction not yet + committed. Changeset is visible to hook program. This lets you + validate commit message and changes. Exit status 0 allows the + commit to proceed. Non-zero status will cause the transaction to + be rolled back. ID of changeset is in $HG_NODE. Parent changeset + IDs are in $HG_PARENT1 and $HG_PARENT2. + tag;; + Run after a tag is created. ID of tagged changeset is in + $HG_NODE. Name of tag is in $HG_TAG. Tag is local if + $HG_LOCAL=1, in repo if $HG_LOCAL=0. + + In earlier releases, the names of hook environment variables did not + have a "HG_" prefix. These unprefixed names are still provided in + the environment for backwards compatibility, but their use is + deprecated, and they will be removed in a future release. http_proxy:: Used to access web-based Mercurial repositories through a HTTP diff --git a/mercurial/commands.py b/mercurial/commands.py --- a/mercurial/commands.py +++ b/mercurial/commands.py @@ -261,7 +261,7 @@ def make_file(repo, r, pat, node=None, mode) def dodiff(fp, ui, repo, node1, node2, files=None, match=util.always, - changes=None, text=False): + changes=None, text=False, opts={}): if not changes: changes = repo.changes(node1, node2, files, match=match) modified, added, removed, deleted, unknown = changes @@ -296,8 +296,8 @@ def dodiff(fp, ui, repo, node1, node2, f date1 = util.datestr(change[2]) diffopts = ui.diffopts() - showfunc = diffopts['showfunc'] - ignorews = diffopts['ignorews'] + 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: @@ -622,7 +622,7 @@ def bundle(ui, repo, fname, dest="defaul dest = ui.expandpath(dest, repo.root) other = hg.repository(ui, dest) o = repo.findoutgoing(other) - cg = repo.changegroup(o) + cg = repo.changegroup(o, 'bundle') try: f.write("HG10") @@ -1140,7 +1140,7 @@ def diff(ui, repo, *pats, **opts): fns, matchfn, anypats = matchpats(repo, pats, opts) dodiff(sys.stdout, ui, repo, node1, node2, fns, match=matchfn, - text=opts['text']) + text=opts['text'], opts=opts) def doexport(ui, repo, changeset, seqno, total, revwidth, opts): node = repo.lookup(changeset) @@ -1681,7 +1681,7 @@ def outgoing(ui, repo, dest="default-pus dodiff(ui, ui, repo, prev, n) ui.write("\n") -def parents(ui, repo, rev=None): +def parents(ui, repo, rev=None, branch=None): """show the parents of the working dir or revision Print the working directory's parent revisions. @@ -1691,9 +1691,12 @@ def parents(ui, repo, rev=None): else: p = repo.dirstate.parents() + br = None + if branch is not None: + br = repo.branchlookup(p) for n in p: if n != nullid: - show_changeset(ui, repo, changenode=n) + show_changeset(ui, repo, changenode=n, brinfo=br) def paths(ui, search=None): """show definition of symbolic path names @@ -1996,7 +1999,7 @@ def serve(ui, repo, **opts): arg, roots = getarg() nodes = map(bin, roots.split(" ")) - cg = repo.changegroup(nodes) + cg = repo.changegroup(nodes, 'serve') while 1: d = cg.read(4096) if not d: @@ -2113,8 +2116,12 @@ def tag(ui, repo, name, rev_=None, **opt if name.find(c) >= 0: raise util.Abort(_("%s cannot be used in a tag name") % repr(c)) + repo.hook('pretag', throw=True, node=r, tag=name, + local=int(not not opts['local'])) + if opts['local']: repo.opener("localtags", "a").write("%s %s\n" % (r, name)) + repo.hook('tag', node=r, tag=name, local=1) return for x in repo.changes(): @@ -2130,6 +2137,7 @@ def tag(ui, repo, name, rev_=None, **opt _("Added tag %s for changeset %s") % (name, r)) try: repo.commit([".hgtags"], message, opts['user'], opts['date']) + repo.hook('tag', node=r, tag=name, local=0) except ValueError, inst: raise util.Abort(str(inst)) @@ -2150,13 +2158,15 @@ def tags(ui, repo): r = " ?:?" ui.write("%-30s %s\n" % (t, r)) -def tip(ui, repo): +def tip(ui, repo, **opts): """show the tip revision Show the tip revision. """ n = repo.changelog.tip() show_changeset(ui, repo, changenode=n) + if opts['patch']: + dodiff(ui, ui, repo, repo.changelog.parents(n)[0], n) def unbundle(ui, repo, fname, **opts): """apply a changegroup file @@ -2332,7 +2342,12 @@ table = { [('r', 'rev', [], _('revision')), ('a', 'text', None, _('treat all files as text')), ('I', 'include', [], _('include names matching the given patterns')), - ('X', 'exclude', [], _('exclude names matching the given patterns'))], + ('p', 'show-function', None, + _('show which function each change is in')), + ('w', 'ignore-all-space', None, + _('ignore white space when comparing lines')), + ('X', 'exclude', [], + _('exclude names matching the given patterns'))], _('hg diff [-a] [-I] [-X] [-r REV1 [-r REV2]] [FILE]...')), "^export": (export, @@ -2407,7 +2422,10 @@ table = { ('p', 'patch', None, _('show patch')), ('n', 'newest-first', None, _('show newest record first'))], _('hg outgoing [-p] [-n] [-M] [DEST]')), - "^parents": (parents, [], _('hg parents [REV]')), + "^parents": + (parents, + [('b', 'branch', None, _('show branches'))], + _('hg parents [-b] [REV]')), "paths": (paths, [], _('hg paths [NAME]')), "^pull": (pull, @@ -2490,7 +2508,7 @@ table = { ('r', 'rev', '', _('revision to tag'))], _('hg tag [-r REV] [OPTION]... NAME')), "tags": (tags, [], _('hg tags')), - "tip": (tip, [], _('hg tip')), + "tip": (tip, [('p', 'patch', None, _('show patch'))], _('hg tip')), "unbundle": (unbundle, [('u', 'update', None, diff --git a/mercurial/hgweb.py b/mercurial/hgweb.py --- a/mercurial/hgweb.py +++ b/mercurial/hgweb.py @@ -962,7 +962,7 @@ class hgweb(object): nodes = map(bin, req.form['roots'][0].split(" ")) z = zlib.compressobj() - f = self.repo.changegroup(nodes) + f = self.repo.changegroup(nodes, 'serve') while 1: chunk = f.read(4096) if not chunk: diff --git a/mercurial/httprepo.py b/mercurial/httprepo.py --- a/mercurial/httprepo.py +++ b/mercurial/httprepo.py @@ -119,7 +119,7 @@ class httprepository(remoterepository): self.ui.warn(_("unexpected response:\n") + d[:400] + "\n...\n") raise - def changegroup(self, nodes): + def changegroup(self, nodes, kind): n = " ".join(map(hex, nodes)) f = self.do_cmd("changegroup", roots=n) bytes = 0 diff --git a/mercurial/localrepo.py b/mercurial/localrepo.py --- a/mercurial/localrepo.py +++ b/mercurial/localrepo.py @@ -48,30 +48,36 @@ class localrepository(object): except IOError: pass - def hook(self, name, **args): + def hook(self, name, throw=False, **args): def runhook(name, cmd): self.ui.note(_("running hook %s: %s\n") % (name, cmd)) old = {} for k, v in args.items(): k = k.upper() + old['HG_' + k] = os.environ.get(k, None) old[k] = os.environ.get(k, None) - os.environ[k] = v + os.environ['HG_' + k] = str(v) + os.environ[k] = str(v) - # Hooks run in the repository root - olddir = os.getcwd() - os.chdir(self.root) - r = os.system(cmd) - os.chdir(olddir) + try: + # Hooks run in the repository root + olddir = os.getcwd() + os.chdir(self.root) + r = os.system(cmd) + finally: + for k, v in old.items(): + if v is not None: + os.environ[k] = v + else: + del os.environ[k] - for k, v in old.items(): - if v != None: - os.environ[k] = v - else: - del os.environ[k] + os.chdir(olddir) if r: - self.ui.warn(_("abort: %s hook failed with status %d!\n") % - (name, r)) + desc, r = util.explain_exit(r) + if throw: + raise util.Abort(_('%s hook %s') % (name, desc)) + self.ui.warn(_('error: %s hook %s\n') % (name, desc)) return False return True @@ -374,8 +380,11 @@ class localrepository(object): self.ui.status(_("nothing changed\n")) return None - if not self.hook("precommit"): - return None + xp1 = hex(p1) + if p2 == nullid: xp2 = '' + else: xp2 = hex(p2) + + self.hook("precommit", throw=True, parent1=xp1, parent2=xp2) if not wlock: wlock = self.wlock() @@ -448,14 +457,15 @@ class localrepository(object): user = user or self.ui.username() n = self.changelog.add(mn, changed + remove, text, tr, p1, p2, user, date) + self.hook('pretxncommit', throw=True, node=hex(n), parent1=xp1, + parent2=xp2) tr.close() self.dirstate.setparents(n) self.dirstate.update(new, "n") self.dirstate.forget(remove) - if not self.hook("commit", node=hex(n)): - return None + self.hook("commit", node=hex(n), parent1=xp1, parent2=xp2) return n def walk(self, node=None, files=[], match=util.always): @@ -936,9 +946,9 @@ class localrepository(object): return 1 if heads is None: - cg = remote.changegroup(fetch) + cg = remote.changegroup(fetch, 'pull') else: - cg = remote.changegroupsubset(fetch, heads) + cg = remote.changegroupsubset(fetch, heads, 'pull') return self.addchangegroup(cg) def push(self, remote, force=False): @@ -963,10 +973,10 @@ class localrepository(object): " use push -f to force)\n")) return 1 - cg = self.changegroup(update) + cg = self.changegroup(update, 'push') return remote.addchangegroup(cg) - def changegroupsubset(self, bases, heads): + def changegroupsubset(self, bases, heads, source): """This function generates a changegroup consisting of all the nodes that are descendents of any of the bases, and ancestors of any of the heads. @@ -978,6 +988,8 @@ class localrepository(object): Another wrinkle is doing the reverse, figuring out which changeset in the changegroup a particular filenode or manifestnode belongs to.""" + self.hook('preoutgoing', throw=True, source=source) + # Set up some initial variables # Make it easy to refer to self.changelog cl = self.changelog @@ -1230,14 +1242,19 @@ class localrepository(object): # Signal that no more groups are left. yield struct.pack(">l", 0) + self.hook('outgoing', node=hex(msng_cl_lst[0]), source=source) + return util.chunkbuffer(gengroup()) - def changegroup(self, basenodes): + def changegroup(self, basenodes, source): """Generate a changegroup of all nodes that we have that a recipient doesn't. This is much easier than the previous function as we can assume that the recipient has any changenode we aren't sending them.""" + + self.hook('preoutgoing', throw=True, source=source) + cl = self.changelog nodes = cl.nodesbetween(basenodes, None)[0] revset = dict.fromkeys([cl.rev(n) for n in nodes]) @@ -1289,6 +1306,7 @@ class localrepository(object): yield chnk yield struct.pack(">l", 0) + self.hook('outgoing', node=hex(nodes[0]), source=source) return util.chunkbuffer(gengroup()) @@ -1324,6 +1342,9 @@ class localrepository(object): if not source: return + + self.hook('prechangegroup', throw=True) + changesets = files = revisions = 0 tr = self.transaction() @@ -1366,19 +1387,17 @@ class localrepository(object): " with %d changes to %d files%s\n") % (changesets, revisions, files, heads)) + self.hook('pretxnchangegroup', throw=True, + node=hex(self.changelog.node(cor+1))) + tr.close() if changesets > 0: - if not self.hook("changegroup", - node=hex(self.changelog.node(cor+1))): - self.ui.warn(_("abort: changegroup hook returned failure!\n")) - return 1 + self.hook("changegroup", node=hex(self.changelog.node(cor+1))) for i in range(cor + 1, cnr + 1): self.hook("incoming", node=hex(self.changelog.node(i))) - return - def update(self, node, allow=False, force=False, choose=None, moddirstate=True, forcemerge=False, wlock=None): pl = self.dirstate.parents() diff --git a/mercurial/mdiff.py b/mercurial/mdiff.py --- a/mercurial/mdiff.py +++ b/mercurial/mdiff.py @@ -18,16 +18,22 @@ def unidiff(a, ad, b, bd, fn, r=None, te if not text and (util.binary(a) or util.binary(b)): l = ['Binary file %s has changed\n' % fn] - elif a == None: + elif not a: b = b.splitlines(1) - l1 = "--- %s\t%s\n" % ("/dev/null", epoch) + if a is None: + l1 = "--- %s\t%s\n" % ("/dev/null", epoch) + else: + l1 = "--- %s\t%s\n" % ("a/" + fn, ad) l2 = "+++ %s\t%s\n" % ("b/" + fn, bd) l3 = "@@ -0,0 +1,%d @@\n" % len(b) l = [l1, l2, l3] + ["+" + e for e in b] - elif b == None: + elif not b: a = a.splitlines(1) l1 = "--- %s\t%s\n" % ("a/" + fn, ad) - l2 = "+++ %s\t%s\n" % ("/dev/null", epoch) + if b is None: + l2 = "+++ %s\t%s\n" % ("/dev/null", epoch) + else: + l2 = "+++ %s\t%s\n" % ("b/" + fn, bd) l3 = "@@ -1,%d +0,0 @@\n" % len(a) l = [l1, l2, l3] + ["-" + e for e in a] else: diff --git a/mercurial/mpatch.c b/mercurial/mpatch.c --- a/mercurial/mpatch.c +++ b/mercurial/mpatch.c @@ -43,6 +43,7 @@ static uint32_t ntohl(uint32_t x) #endif static char mpatch_doc[] = "Efficient binary patching."; +static PyObject *mpatch_Error; struct frag { int start, end, len; @@ -65,8 +66,11 @@ static struct flist *lalloc(int size) a = NULL; } else a->head = a->tail = a->base; + return a; } - return a; + if (!PyErr_Occurred()) + PyErr_NoMemory(); + return NULL; } static void lfree(struct flist *a) @@ -215,6 +219,9 @@ static struct flist *decode(char *bin, i /* assume worst case size, we won't have many of these lists */ l = lalloc(len / 12); + if (!l) + return NULL; + lt = l->tail; while (bin < end) { @@ -227,6 +234,13 @@ static struct flist *decode(char *bin, i lt++; } + if (bin != end) { + if (!PyErr_Occurred()) + PyErr_SetString(mpatch_Error, "patch cannot be decoded"); + lfree(l); + return NULL; + } + l->tail = lt; return l; } @@ -238,6 +252,12 @@ static int calcsize(int len, struct flis struct frag *f = l->head; while (f != l->tail) { + if (f->start < last || f->end > len) { + if (!PyErr_Occurred()) + PyErr_SetString(mpatch_Error, + "invalid patch"); + return -1; + } outlen += f->start - last; last = f->end; outlen += f->len; @@ -248,13 +268,19 @@ static int calcsize(int len, struct flis return outlen; } -static void apply(char *buf, char *orig, int len, struct flist *l) +static int apply(char *buf, char *orig, int len, struct flist *l) { struct frag *f = l->head; int last = 0; char *p = buf; while (f != l->tail) { + if (f->start < last || f->end > len) { + if (!PyErr_Occurred()) + PyErr_SetString(mpatch_Error, + "invalid patch"); + return 0; + } memcpy(p, orig + last, f->start - last); p += f->start - last; memcpy(p, f->data, f->len); @@ -263,6 +289,7 @@ static void apply(char *buf, char *orig, f++; } memcpy(p, orig + last, len - last); + return 1; } /* recursively generate a patch of all bins between start and end */ @@ -304,16 +331,25 @@ patches(PyObject *self, PyObject *args) patch = fold(bins, 0, len); if (!patch) - return PyErr_NoMemory(); + return NULL; outlen = calcsize(PyString_Size(text), patch); + if (outlen < 0) { + result = NULL; + goto cleanup; + } result = PyString_FromStringAndSize(NULL, outlen); - if (result) { - in = PyString_AsString(text); - out = PyString_AsString(result); - apply(out, in, PyString_Size(text), patch); + if (!result) { + result = NULL; + goto cleanup; } - + in = PyString_AsString(text); + out = PyString_AsString(result); + if (!apply(out, in, PyString_Size(text), patch)) { + Py_DECREF(result); + result = NULL; + } +cleanup: lfree(patch); return result; } @@ -327,5 +363,6 @@ PyMODINIT_FUNC initmpatch(void) { Py_InitModule3("mpatch", methods, mpatch_doc); + mpatch_Error = PyErr_NewException("mpatch.mpatchError", NULL, NULL); } diff --git a/mercurial/sshrepo.py b/mercurial/sshrepo.py --- a/mercurial/sshrepo.py +++ b/mercurial/sshrepo.py @@ -110,7 +110,7 @@ class sshrepository(remoterepository): except: raise hg.RepoError(_("unexpected response '%s'") % (d[:400] + "...")) - def changegroup(self, nodes): + def changegroup(self, nodes, kind): n = " ".join(map(hex, nodes)) f = self.do_cmd("changegroup", roots=n) return self.pipei diff --git a/tests/test-diffdir b/tests/test-diffdir --- a/tests/test-diffdir +++ b/tests/test-diffdir @@ -12,3 +12,7 @@ hg diff | sed -e "s/\(+++ [a-zA-Z0-9_/.- hg diff -r tip | sed -e "s/\(+++ [a-zA-Z0-9_/.-]*\).*/\1/" \ -e "s/\(--- [a-zA-Z0-9_/.-]*\).*/\1/" + +echo foo > a +hg diff | sed -e "s/\(+++ [a-zA-Z0-9_/.-]*\).*/\1/" \ + -e "s/\(--- [a-zA-Z0-9_/.-]*\).*/\1/" diff --git a/tests/test-diffdir.out b/tests/test-diffdir.out --- a/tests/test-diffdir.out +++ b/tests/test-diffdir.out @@ -8,3 +8,13 @@ diff -r 3903775176ed b +++ b/b @@ -0,0 +1,1 @@ +123 +diff -r 3903775176ed a +--- a/a ++++ b/a +@@ -0,0 +1,1 @@ ++foo +diff -r 3903775176ed b +--- /dev/null ++++ b/b +@@ -0,0 +1,1 @@ ++123 diff --git a/tests/test-help.out b/tests/test-help.out --- a/tests/test-help.out +++ b/tests/test-help.out @@ -171,10 +171,12 @@ diff repository (or selected files) options: - -r --rev revision - -a --text treat all files as text - -I --include include names matching the given patterns - -X --exclude exclude names matching the given patterns + -r --rev revision + -a --text treat all files as text + -I --include include names matching the given patterns + -p --show-function show which function each change is in + -w --ignore-all-space ignore white space when comparing lines + -X --exclude exclude names matching the given patterns hg status [OPTION]... [FILE]... show changed files in the working directory diff --git a/tests/test-hook b/tests/test-hook --- a/tests/test-hook +++ b/tests/test-hook @@ -1,10 +1,90 @@ #!/bin/sh -hg init +# commit hooks can see env vars +hg init a +cd a echo "[hooks]" > .hg/hgrc -echo 'precommit = echo precommit hook' >> .hg/hgrc -echo 'commit = echo commit hook: $NODE' >> .hg/hgrc +echo 'commit = echo commit hook: n=$HG_NODE p1=$HG_PARENT1 p2=$HG_PARENT2' >> .hg/hgrc echo 'commit.b = echo commit hook b' >> .hg/hgrc +echo 'precommit = echo precommit hook: p1=$HG_PARENT1 p2=$HG_PARENT2' >> .hg/hgrc +echo 'pretxncommit = echo pretxncommit hook: n=$HG_NODE p1=$HG_PARENT1 p2=$HG_PARENT2; hg -q tip' >> .hg/hgrc echo a > a hg add a -hg commit -m "test" -d "0 0" +hg commit -m a -d "0 0" + +hg clone . ../b +cd ../b + +# changegroup hooks can see env vars +echo '[hooks]' > .hg/hgrc +echo 'prechangegroup = echo prechangegroup hook' >> .hg/hgrc +echo 'changegroup = echo changegroup hook: n=$HG_NODE' >> .hg/hgrc +echo 'incoming = echo incoming hook: n=$HG_NODE' >> .hg/hgrc + +# pretxncommit and commit hooks can see both parents of merge +cd ../a +echo b >> a +hg commit -m a1 -d "1 0" +hg update -C 0 +echo b > b +hg add b +hg commit -m b -d '1 0' +hg update -m 1 +hg commit -m merge -d '2 0' + +cd ../b +hg pull ../a + +# tag hooks can see env vars +cd ../a +echo 'pretag = echo pretag hook: t=$HG_TAG n=$HG_NODE l=$HG_LOCAL' >> .hg/hgrc +echo 'tag = echo tag hook: t=$HG_TAG n=$HG_NODE l=$HG_LOCAL' >> .hg/hgrc +hg tag -d '3 0' a +hg tag -l la + +# pretag hook can forbid tagging +echo 'pretag.forbid = echo pretag.forbid hook; exit 1' >> .hg/hgrc +hg tag -d '4 0' fa +hg tag -l fla + +# pretxncommit hook can see changeset, can roll back txn, changeset +# no more there after +echo 'pretxncommit.forbid = echo pretxncommit.forbid hook: tip=`hg -q tip`; exit 1' >> .hg/hgrc +echo z > z +hg add z +hg -q tip +hg commit -m 'fail' -d '4 0' +hg -q tip + +# precommit hook can prevent commit +echo 'precommit.forbid = echo precommit.forbid hook; exit 1' >> .hg/hgrc +hg commit -m 'fail' -d '4 0' +hg -q tip + +# prechangegroup hook can prevent incoming changes +cd ../b +hg -q tip +echo '[hooks]' > .hg/hgrc +echo 'prechangegroup.forbid = echo prechangegroup.forbid hook; exit 1' >> .hg/hgrc +hg pull ../a + +# pretxnchangegroup hook can see incoming changes, can roll back txn, +# incoming changes no longer there after +echo '[hooks]' > .hg/hgrc +echo 'pretxnchangegroup.forbid = echo pretxnchangegroup.forbid hook: tip=`hg -q tip`; exit 1' >> .hg/hgrc +hg pull ../a +hg -q tip + +# outgoing hooks can see env vars +rm .hg/hgrc +echo '[hooks]' > ../a/.hg/hgrc +echo 'preoutgoing = echo preoutgoing hook: s=$HG_SOURCE' >> ../a/.hg/hgrc +echo 'outgoing = echo outgoing hook: n=$HG_NODE s=$HG_SOURCE' >> ../a/.hg/hgrc +hg pull ../a +hg undo + +# preoutgoing hook can prevent outgoing changes +echo 'preoutgoing.forbid = echo preoutgoing.forbid hook; exit 1' >> ../a/.hg/hgrc +hg pull ../a + +exit 0 diff --git a/tests/test-hook.out b/tests/test-hook.out --- a/tests/test-hook.out +++ b/tests/test-hook.out @@ -1,3 +1,88 @@ -precommit hook +precommit hook: p1=0000000000000000000000000000000000000000 p2= +pretxncommit hook: n=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b p1=0000000000000000000000000000000000000000 p2= +0:cb9a9f314b8b +commit hook b +commit hook: n=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b p1=0000000000000000000000000000000000000000 p2= +precommit hook: p1=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b p2= +pretxncommit hook: n=ab228980c14deea8b9555d91c9581127383e40fd p1=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b p2= +1:ab228980c14d +commit hook b +commit hook: n=ab228980c14deea8b9555d91c9581127383e40fd p1=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b p2= +precommit hook: p1=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b p2= +pretxncommit hook: n=ee9deb46ab31e4cc3310f3cf0c3d668e4d8fffc2 p1=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b p2= +2:ee9deb46ab31 +commit hook b +commit hook: n=ee9deb46ab31e4cc3310f3cf0c3d668e4d8fffc2 p1=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b p2= +precommit hook: p1=ee9deb46ab31e4cc3310f3cf0c3d668e4d8fffc2 p2=ab228980c14deea8b9555d91c9581127383e40fd +pretxncommit hook: n=07f3376c1e655977439df2a814e3cc14b27abac2 p1=ee9deb46ab31e4cc3310f3cf0c3d668e4d8fffc2 p2=ab228980c14deea8b9555d91c9581127383e40fd +3:07f3376c1e65 +commit hook b +commit hook: n=07f3376c1e655977439df2a814e3cc14b27abac2 p1=ee9deb46ab31e4cc3310f3cf0c3d668e4d8fffc2 p2=ab228980c14deea8b9555d91c9581127383e40fd +prechangegroup hook +changegroup hook: n=ab228980c14deea8b9555d91c9581127383e40fd +incoming hook: n=ab228980c14deea8b9555d91c9581127383e40fd +incoming hook: n=ee9deb46ab31e4cc3310f3cf0c3d668e4d8fffc2 +incoming hook: n=07f3376c1e655977439df2a814e3cc14b27abac2 +pulling from ../a +searching for changes +adding changesets +adding manifests +adding file changes +added 3 changesets with 2 changes to 2 files +(run 'hg update' to get a working copy) +pretag hook: t=a n=07f3376c1e655977439df2a814e3cc14b27abac2 l=0 +precommit hook: p1=07f3376c1e655977439df2a814e3cc14b27abac2 p2= +pretxncommit hook: n=3cd2c6a5a36c5908aad3bc0d717c29873a05dfc2 p1=07f3376c1e655977439df2a814e3cc14b27abac2 p2= +4:3cd2c6a5a36c commit hook b -commit hook: acb14030fe0a21b60322c440ad2d20cf7685a376 +commit hook: n=3cd2c6a5a36c5908aad3bc0d717c29873a05dfc2 p1=07f3376c1e655977439df2a814e3cc14b27abac2 p2= +tag hook: t=a n=07f3376c1e655977439df2a814e3cc14b27abac2 l=0 +pretag hook: t=la n=3cd2c6a5a36c5908aad3bc0d717c29873a05dfc2 l=1 +tag hook: t=la n=3cd2c6a5a36c5908aad3bc0d717c29873a05dfc2 l=1 +pretag hook: t=fa n=3cd2c6a5a36c5908aad3bc0d717c29873a05dfc2 l=0 +pretag.forbid hook +abort: pretag.forbid hook exited with status 1 +pretag hook: t=fla n=3cd2c6a5a36c5908aad3bc0d717c29873a05dfc2 l=1 +pretag.forbid hook +abort: pretag.forbid hook exited with status 1 +4:3cd2c6a5a36c +precommit hook: p1=3cd2c6a5a36c5908aad3bc0d717c29873a05dfc2 p2= +pretxncommit.forbid hook: tip=5:469a61fe67d6 +abort: pretxncommit.forbid hook exited with status 1 +transaction abort! +rollback completed +4:3cd2c6a5a36c +precommit.forbid hook +abort: precommit.forbid hook exited with status 1 +4:3cd2c6a5a36c +3:07f3376c1e65 +prechangegroup.forbid hook +pulling from ../a +searching for changes +abort: prechangegroup.forbid hook exited with status 1 +pretxnchangegroup.forbid hook: tip=4:3cd2c6a5a36c +pulling from ../a +searching for changes +adding changesets +adding manifests +adding file changes +added 1 changesets with 1 changes to 1 files +abort: pretxnchangegroup.forbid hook exited with status 1 +transaction abort! +rollback completed +3:07f3376c1e65 +preoutgoing hook: s=pull +outgoing hook: n=3cd2c6a5a36c5908aad3bc0d717c29873a05dfc2 s=pull +pulling from ../a +searching for changes +adding changesets +adding manifests +adding file changes +added 1 changesets with 1 changes to 1 files +(run 'hg update' to get a working copy) +rolling back last transaction +preoutgoing hook: s=pull +preoutgoing.forbid hook +pulling from ../a +searching for changes +abort: preoutgoing.forbid hook exited with status 1