Pull from TAH
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1
Pull from TAH
manifest hash: 600d04efbd836d555d11a3bd9d821d1d8c0a9790
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.0 (GNU/Linux)
iD8DBQFCuPFxywK+sNU5EO8RAjfzAKC18Zc2EOkXhy1zcpgGnyPHnFMdmgCfW5Ut
I5HSWqZMt8H0WJx1Or7ajNc=
=27D5
-----END PGP SIGNATURE-----
new file mode 100644
--- /dev/null
+++ b/contrib/git-viz/git-cat-file
@@ -0,0 +1,5 @@
+#!/bin/sh
+
+op=`basename $0 | sed -e 's/^git-//'`
+exec hgit $op "$@"
+
new file mode 100644
--- /dev/null
+++ b/contrib/git-viz/git-diff-tree
@@ -0,0 +1,5 @@
+#!/bin/sh
+
+op=`basename $0 | sed -e 's/^git-//'`
+exec hgit $op "$@"
+
new file mode 100644
--- /dev/null
+++ b/contrib/git-viz/git-rev-list
@@ -0,0 +1,5 @@
+#!/bin/sh
+
+op=`basename $0 | sed -e 's/^git-//'`
+exec hgit $op "$@"
+
new file mode 100644
--- /dev/null
+++ b/contrib/git-viz/git-rev-tree
@@ -0,0 +1,5 @@
+#!/bin/sh
+
+op=`basename $0 | sed -e 's/^git-//'`
+exec hgit $op "$@"
+
new file mode 100644
--- /dev/null
+++ b/contrib/git-viz/hg-viz
@@ -0,0 +1,26 @@
+#!/bin/sh
+
+set -e
+
+if test x"$1" != x ; then
+ cd $1
+fi
+
+if [ ! -d ".hg" ]; then
+ echo "${1:-.} is not a mercurial repository" 1>&2
+ echo "Aborting" 1>&2
+ exit 1
+fi
+if [ ! -d ".git" ]; then
+ mkdir -v ".git"
+fi
+if [ -e ".git/HEAD" ]; then
+ if [ ! -e ".git/HEAD.hg-viz-save" ]; then
+ mv -v ".git/HEAD" ".git/HEAD.hg-viz-save"
+ else
+ rm -vf ".git/HEAD"
+ fi
+fi
+hg history | head -1 | awk -F: '{print $3}' > .git/HEAD
+git-viz
+
--- a/contrib/hgit
+++ b/contrib/hgit
@@ -38,12 +38,12 @@ def difftree(args, ui, repo):
for f in c:
# TODO get file permissions
- print ":100664 100664 %s %s %s %s" % (hg.hex(mmap[f]),
+ print ":100664 100664 %s %s M\t%s\t%s" % (hg.hex(mmap[f]),
hg.hex(mmap2[f]), f, f)
for f in a:
- print ":000000 100664 %s %s %s %s" % (empty, hg.hex(mmap2[f]), f, f)
+ print ":000000 100664 %s %s N\t%s\t%s" % (empty, hg.hex(mmap2[f]), f, f)
for f in d:
- print ":100664 000000 %s %s %s %s" % (hg.hex(mmap[f]), empty, f, f)
+ print ":100664 000000 %s %s D\t%s\t%s" % (hg.hex(mmap[f]), empty, f, f)
##
revs = []
new file mode 100644
--- /dev/null
+++ b/mercurial/bdiff.c
@@ -0,0 +1,293 @@
+/*
+ bdiff.c - efficient binary diff extension 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.
+
+ Based roughly on Python difflib
+*/
+
+#include <Python.h>
+#include <stdlib.h>
+#include <string.h>
+#ifdef _WIN32
+
+typedef unsigned long uint32_t;
+
+static uint32_t htonl(uint32_t x)
+{
+ return ((x & 0x000000ffUL) << 24) |
+ ((x & 0x0000ff00UL) << 8) |
+ ((x & 0x00ff0000UL) >> 8) |
+ ((x & 0xff000000UL) >> 24);
+}
+
+#else
+ #include <netinet/in.h>
+ #include <sys/types.h>
+#endif
+
+struct line {
+ int h, len, n;
+ const char *l;
+};
+
+struct hunk {
+ int a1, a2, b1, b2;
+};
+
+struct hunklist {
+ struct hunk *base, *head;
+};
+
+static inline uint32_t rol32(uint32_t word, unsigned int shift)
+{
+ return (word << shift) | (word >> (32 - shift));
+}
+
+int splitlines(const char *a, int len, struct line **lr)
+{
+ int h, i;
+ const char *p, *b = a;
+ struct line *l;
+
+ /* count the lines */
+ i = 1; /* extra line for sentinel */
+ for (p = a; p < a + len; p++)
+ if (*p == '\n' || p == a + len - 1)
+ i++;
+
+ *lr = l = malloc(sizeof(struct line) * i);
+ if (!l)
+ return -1;
+
+ /* build the line array and calculate hashes */
+ h = 0;
+ for (p = a; p < a + len; p++) {
+ h = *p + rol32(h, 7); /* a simple hash from GNU diff */
+ if (*p == '\n' || p == a + len - 1) {
+ l->len = p - b + 1;
+ l->h = h;
+ l->l = b;
+ l->n = -1;
+ l++;
+ b = p + 1;
+ h = 0;
+ }
+ }
+
+ /* set up a sentinel */
+ l->h = l->len = 0;
+ l->l = a + len;
+ return i - 1;
+}
+
+int inline cmp(struct line *a, struct line *b)
+{
+ return a->len != b->len || memcmp(a->l, b->l, a->len);
+}
+
+static int equatelines(struct line *a, int an, struct line *b, int bn)
+{
+ int i, j, buckets = 1, t, *h, *l;
+
+ /* build a hash table of the next highest power of 2 */
+ while (buckets < bn + 1)
+ buckets *= 2;
+
+ h = malloc(buckets * sizeof(int));
+ l = calloc(buckets, sizeof(int));
+ buckets = buckets - 1;
+ if (!h || !l) {
+ free(h);
+ return 0;
+ }
+
+ /* clear the hash table */
+ for (i = 0; i <= buckets; i++)
+ h[i] = -1;
+
+ /* add lines to the hash table chains */
+ for (i = bn - 1; i >= 0; i--) {
+ /* find the equivalence class */
+ for (j = b[i].h & buckets; h[j] != -1; j = (j + 1) & buckets)
+ if (!cmp(b + i, b + h[j]))
+ break;
+
+ /* add to the head of the equivalence class */
+ b[i].n = h[j];
+ b[i].h = j;
+ h[j] = i;
+ l[j]++; /* keep track of popularity */
+ }
+
+ /* compute popularity threshold */
+ t = (bn >= 200) ? bn / 100 : bn + 1;
+
+ /* match items in a to their equivalence class in b */
+ for (i = 0; i < an; i++) {
+ /* find the equivalence class */
+ for (j = a[i].h & buckets; h[j] != -1; j = (j + 1) & buckets)
+ if (!cmp(a + i, b + h[j]))
+ break;
+
+ a[i].h = j; /* use equivalence class for quick compare */
+ if(l[j] <= t)
+ a[i].n = h[j]; /* point to head of match list */
+ else
+ a[i].n = -1; /* too popular */
+ }
+
+ /* discard hash tables */
+ free(h);
+ free(l);
+ return 1;
+}
+
+static int longest_match(struct line *a, struct line *b, int *jpos, int *jlen,
+ int a1, int a2, int b1, int b2, int *omi, int *omj)
+{
+ int mi = a1, mj = b1, mk = 0, mb = 0, i, j, k;
+
+ for (i = a1; i < a2; i++) {
+ /* skip things before the current block */
+ for (j = a[i].n; j != -1 && j < b1; j = b[j].n)
+ ;
+
+ /* loop through all lines match a[i] in b */
+ for (; j != -1 && j < b2; j = b[j].n) {
+ /* does this extend an earlier match? */
+ if (i > a1 && j > b1 && jpos[j - 1] == i)
+ k = jlen[j - 1] + 1;
+ else
+ k = 1;
+ jpos[j] = i + 1;
+ jlen[j] = k;
+
+ /* best match so far? */
+ if (k > mk) {
+ mi = i;
+ mj = j;
+ mk = k;
+ }
+ }
+ }
+
+ if (mk) {
+ mi = mi - mk + 1;
+ mj = mj - mk + 1;
+ }
+
+ /* expand match to include neighboring popular lines */
+ while (mi - mb > a1 && mj - mb > b1 &&
+ a[mi - mb - 1].h == b[mj - mb - 1].h)
+ mb++;
+ while (mi + mk < a2 && mj + mk < b2 &&
+ a[mi + mk].h == b[mj + mk].h)
+ mk++;
+
+ *omi = mi - mb;
+ *omj = mj - mb;
+ return mk + mb;
+}
+
+static void recurse(struct line *a, struct line *b, int *jpos, int *jlen,
+ int a1, int a2, int b1, int b2, struct hunklist *l)
+{
+ int i, j, k;
+
+ /* find the longest match in this chunk */
+ k = longest_match(a, b, jpos, jlen, a1, a2, b1, b2, &i, &j);
+ if (!k)
+ return;
+
+ /* and recurse on the remaining chunks on either side */
+ recurse(a, b, jpos, jlen, a1, i, b1, j, l);
+ l->head->a1 = i;
+ l->head->a2 = i + k;
+ l->head->b1 = j;
+ l->head->b2 = j + k;
+ l->head++;
+ recurse(a, b, jpos, jlen, i + k, a2, j + k, b2, l);
+}
+
+static PyObject *bdiff(PyObject *self, PyObject *args)
+{
+ PyObject *sa, *sb, *result = NULL;
+ struct hunklist l;
+ struct hunk *h;
+ struct line *al, *bl;
+ char encode[12], *rb;
+ int an, bn, len = 0, t, la = 0, lb = 0, *jpos, *jlen;
+
+ if (!PyArg_ParseTuple(args, "SS:bdiff", &sa, &sb))
+ return NULL;
+
+ /* allocate and fill arrays */
+ an = splitlines(PyString_AsString(sa), PyString_Size(sa), &al);
+ bn = splitlines(PyString_AsString(sb), PyString_Size(sb), &bl);
+ t = equatelines(al, an, bl, bn);
+ jpos = calloc(bn, sizeof(int));
+ jlen = calloc(bn, sizeof(int));
+ l.head = l.base = malloc(sizeof(struct hunk) * ((an + bn) / 4 + 2));
+ if (!al || !bl || !jpos || !jlen || !l.base || !t)
+ goto nomem;
+
+ /* generate the matching block list */
+ recurse(al, bl, jpos, jlen, 0, an, 0, bn, &l);
+ l.head->a1 = an;
+ l.head->b1 = bn;
+ l.head++;
+
+ /* calculate length of output */
+ for(h = l.base; h != l.head; h++) {
+ if (h->a1 != la || h->b1 != lb)
+ len += 12 + bl[h->b1].l - bl[lb].l;
+ la = h->a2;
+ lb = h->b2;
+ }
+
+ result = PyString_FromStringAndSize(NULL, len);
+ if (!result)
+ goto nomem;
+
+ /* build binary patch */
+ rb = PyString_AsString(result);
+ la = lb = 0;
+
+ for(h = l.base; h != l.head; h++) {
+ if (h->a1 != la || h->b1 != lb) {
+ len = bl[h->b1].l - bl[lb].l;
+ *(uint32_t *)(encode) = htonl(al[la].l - al->l);
+ *(uint32_t *)(encode + 4) = htonl(al[h->a1].l - al->l);
+ *(uint32_t *)(encode + 8) = htonl(len);
+ memcpy(rb, encode, 12);
+ memcpy(rb + 12, bl[lb].l, len);
+ rb += 12 + len;
+ }
+ la = h->a2;
+ lb = h->b2;
+ }
+
+nomem:
+ free(al);
+ free(bl);
+ free(jpos);
+ free(jlen);
+ free(l.base);
+ return result ? result : PyErr_NoMemory();
+}
+
+static char mdiff_doc[] = "Efficient binary diff.";
+
+static PyMethodDef methods[] = {
+ {"bdiff", bdiff, METH_VARARGS, "calculate a binary diff\n"},
+ {NULL, NULL}
+};
+
+PyMODINIT_FUNC initbdiff(void)
+{
+ Py_InitModule3("bdiff", methods, mdiff_doc);
+}
--- a/mercurial/commands.py
+++ b/mercurial/commands.py
@@ -6,7 +6,7 @@
# of the GNU General Public License, incorporated herein by reference.
import os, re, sys, signal
-import fancyopts, ui, hg
+import fancyopts, ui, hg, util
from demandload import *
demandload(globals(), "mdiff time hgweb traceback random signal errno version")
@@ -16,20 +16,20 @@ def filterfiles(filters, files):
l = [ x for x in files if x in filters ]
for t in filters:
- if t and t[-1] != os.sep: t += os.sep
+ if t and t[-1] != "/": t += "/"
l += [ x for x in files if x.startswith(t) ]
return l
def relfilter(repo, files):
if os.getcwd() != repo.root:
p = os.getcwd()[len(repo.root) + 1: ]
- return filterfiles([p], files)
+ return filterfiles([util.pconvert(p)], files)
return files
def relpath(repo, args):
if os.getcwd() != repo.root:
p = os.getcwd()[len(repo.root) + 1: ]
- return [ os.path.normpath(os.path.join(p, x)) for x in args ]
+ return [ util.pconvert(os.path.normpath(os.path.join(p, x))) for x in args ]
return args
def dodiff(ui, repo, path, files = None, node1 = None, node2 = None):
@@ -47,7 +47,7 @@ def dodiff(ui, repo, path, files = None,
(c, a, d, u) = repo.diffdir(path, node1)
if not node1:
node1 = repo.dirstate.parents()[0]
- def read(f): return file(os.path.join(repo.root, f)).read()
+ def read(f): return repo.wfile(f).read()
if ui.quiet:
r = None
@@ -285,7 +285,7 @@ def debugchangegroup(ui, repo, roots):
sys.stdout.write(chunk)
def debugindex(ui, file):
- r = hg.revlog(open, file, "")
+ r = hg.revlog(hg.opener(""), file, "")
print " rev offset length base linkrev"+\
" p1 p2 nodeid"
for i in range(r.count()):
@@ -295,7 +295,7 @@ def debugindex(ui, file):
hg.hex(e[4][:5]), hg.hex(e[5][:5]), hg.hex(e[6][:5]))
def debugindexdot(ui, file):
- r = hg.revlog(open, file, "")
+ r = hg.revlog(hg.opener(""), file, "")
print "digraph G {"
for i in range(r.count()):
e = r.index[i]
@@ -485,17 +485,27 @@ def patch(ui, repo, patch1, *patches, **
addremove(ui, repo, *files)
repo.commit(files, text)
-def pull(ui, repo, source="default"):
+def pull(ui, repo, source="default", **opts):
"""pull changes from the specified source"""
paths = {}
for name, path in ui.configitems("paths"):
paths[name] = path
- if source in paths: source = paths[source]
+ if source in paths:
+ source = paths[source]
+
+ ui.status('pulling from %s\n' % (source))
other = hg.repository(ui, source)
cg = repo.getchangegroup(other)
- repo.addchangegroup(cg)
+ r = repo.addchangegroup(cg)
+ if cg and not r:
+ if opts['update']:
+ return update(ui, repo)
+ else:
+ ui.status("(run 'hg update' to get a working copy)\n")
+
+ return r
def push(ui, repo, dest="default-push"):
"""push changes to the specified destination"""
@@ -533,7 +543,7 @@ def push(ui, repo, dest="default-push"):
os.kill(child, signal.SIGTERM)
return r
-def rawcommit(ui, repo, flist, **rc):
+def rawcommit(ui, repo, *flist, **rc):
"raw commit interface"
text = rc['text']
@@ -544,7 +554,7 @@ def rawcommit(ui, repo, flist, **rc):
print "missing commit text"
return 1
- files = relpath(repo, flist)
+ files = relpath(repo, list(flist))
if rc['files']:
files += open(rc['files']).read().splitlines()
@@ -579,6 +589,35 @@ def status(ui, repo):
for f in d: print "R", f
for f in u: print "?", f
+def tag(ui, repo, name, rev = None, **opts):
+ """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
+
+ (c, a, d, u) = repo.diffdir(repo.root)
+ for x in (c, a, d, u):
+ if ".hgtags" in x:
+ ui.warn("abort: working copy of .hgtags is changed!\n")
+ ui.status("(please commit .hgtags manually)\n")
+ return -1
+
+ if rev:
+ r = hg.hex(repo.lookup(rev))
+ else:
+ r = hg.hex(repo.changelog.tip())
+
+ add = 0
+ if not os.path.exists(repo.wjoin(".hgtags")): add = 1
+ repo.wfile(".hgtags", "a").write("%s %s\n" % (r, name))
+ if add: repo.add([".hgtags"])
+
+ if not opts['text']:
+ opts['text'] = "Added tag %s for changeset %s" % (name, r)
+
+ repo.commit([".hgtags"], opts['text'], opts['user'], opts['date'])
+
def tags(ui, repo):
"""list repository tags"""
@@ -662,7 +701,9 @@ table = {
('b', 'base', "", 'base path'),
('q', 'quiet', "", 'silence diff')],
"hg import [options] patches"),
- "pull|merge": (pull, [], 'hg pull [source]'),
+ "pull|merge": (pull,
+ [('u', 'update', None, 'update working directory')],
+ 'hg pull [options] [source]'),
"push": (push, [], 'hg push <destination>'),
"rawcommit": (rawcommit,
[('p', 'parent', [], 'parent'),
@@ -680,6 +721,10 @@ table = {
('t', 'templates', "", 'template map')],
"hg serve [options]"),
"status": (status, [], 'hg status'),
+ "tag": (tag, [('t', 'text', "", 'commit text'),
+ ('d', 'date', "", 'date'),
+ ('u', 'user', "", 'user')],
+ 'hg tag [options] <name> [rev]'),
"tags": (tags, [], 'hg tags'),
"tip": (tip, [], 'hg tip'),
"undo": (undo, [], 'hg undo'),
--- a/mercurial/hg.py
+++ b/mercurial/hg.py
@@ -6,6 +6,7 @@
# of the GNU General Public License, incorporated herein by reference.
import sys, struct, os
+import util
from revlog import *
from demandload import *
demandload(globals(), "re lock urllib urllib2 transaction time socket")
@@ -336,8 +337,8 @@ def opener(base):
os.makedirs(d)
else:
if s.st_nlink > 1:
- file(f + ".tmp", "w").write(file(f).read())
- os.rename(f+".tmp", f)
+ file(f + ".tmp", "wb").write(file(f, "rb").read())
+ util.rename(f+".tmp", f)
return file(f, mode)
@@ -353,11 +354,15 @@ class localrepository:
if not path:
p = os.getcwd()
while not os.path.isdir(os.path.join(p, ".hg")):
+ oldp = p
p = os.path.dirname(p)
- if p == "/": raise "No repo found"
+ if p == oldp: raise "No repo found"
path = p
self.path = os.path.join(path, ".hg")
+ if not create and not os.path.isdir(self.path):
+ raise "repository %s not found" % self.path
+
self.root = path
self.ui = ui
@@ -383,10 +388,10 @@ class localrepository:
if self.ignorelist is None:
self.ignorelist = []
try:
- l = self.wfile(".hgignore")
+ l = file(self.wjoin(".hgignore"))
for pat in l:
if pat != "\n":
- self.ignorelist.append(re.compile(pat[:-1]))
+ self.ignorelist.append(re.compile(util.pconvert(pat[:-1])))
except IOError: pass
for pat in self.ignorelist:
if pat.search(f): return True
@@ -477,7 +482,7 @@ class localrepository:
self.ui.status("attempting to rollback last transaction\n")
transaction.rollback(self.opener, self.join("undo"))
self.dirstate = None
- os.rename(self.join("undo.dirstate"), self.join("dirstate"))
+ util.rename(self.join("undo.dirstate"), self.join("dirstate"))
self.dirstate = dirstate(self.opener, self.ui, self.root)
else:
self.ui.warn("no undo information available\n")
@@ -566,7 +571,7 @@ class localrepository:
try:
fp = self.wjoin(f)
mf1[f] = is_exec(fp)
- t = file(fp).read()
+ t = self.wfile(f).read()
except IOError:
self.warn("trouble committing %s!\n" % f)
raise
@@ -585,7 +590,9 @@ class localrepository:
# update manifest
m1.update(new)
- for f in remove: del m1[f]
+ for f in remove:
+ if f in m1:
+ del m1[f]
mn = self.manifest.add(m1, mf1, tr, linkrev, c1[0], c2[0])
# add changeset
@@ -634,7 +641,7 @@ class localrepository:
if ".hg" in subdirs: subdirs.remove(".hg")
for f in files:
- fn = os.path.join(d, f)
+ fn = util.pconvert(os.path.join(d, f))
try: s = os.stat(os.path.join(self.root, fn))
except: continue
if fn in dc:
@@ -706,6 +713,9 @@ class localrepository:
p = self.wjoin(f)
if os.path.isfile(p):
self.ui.warn("%s still exists!\n" % f)
+ elif self.dirstate.state(f) == 'a':
+ self.ui.warn("%s never committed!\n" % f)
+ self.dirstate.forget(f)
elif f not in self.dirstate:
self.ui.warn("%s not tracked!\n" % f)
else:
@@ -1001,10 +1011,14 @@ class localrepository:
m2 = self.manifest.read(m2n)
mf2 = self.manifest.readflags(m2n)
ma = self.manifest.read(man)
- mfa = self.manifest.readflags(m2n)
+ mfa = self.manifest.readflags(man)
(c, a, d, u) = self.diffdir(self.root)
+ # is this a jump, or a merge? i.e. is there a linear path
+ # from p1 to p2?
+ linear_path = (pa == p1 or pa == p2)
+
# resolve the manifest to determine which files
# we care about merging
self.ui.note("resolving manifests\n")
@@ -1025,17 +1039,33 @@ class localrepository:
for f in d:
if f in mw: del mw[f]
+ # If we're jumping between revisions (as opposed to merging),
+ # and if neither the working directory nor the target rev has
+ # the file, then we need to remove it from the dirstate, to
+ # prevent the dirstate from listing the file when it is no
+ # longer in the manifest.
+ if linear_path and f not in m2:
+ self.dirstate.forget((f,))
+
for f, n in mw.iteritems():
if f in m2:
s = 0
+ # is the wfile new since m1, and match m2?
+ if f not in m1:
+ t1 = self.wfile(f).read()
+ t2 = self.file(f).revision(m2[f])
+ if cmp(t1, t2) == 0:
+ mark[f] = 1
+ n = m2[f]
+ del t1, t2
+
# are files different?
if n != m2[f]:
a = ma.get(f, nullid)
# are both different from the ancestor?
if n != a and m2[f] != a:
self.ui.debug(" %s versions differ, resolve\n" % f)
- merge[f] = (m1.get(f, nullid), m2[f])
# merge executable bits
# "if we changed or they changed, change in merge"
a, b, c = mfa.get(f, 0), mfw[f], mf2[f]
@@ -1066,9 +1096,11 @@ class localrepository:
del m2[f]
elif f in ma:
if not force and n != ma[f]:
- r = self.ui.prompt(
- (" local changed %s which remote deleted\n" % f) +
- "(k)eep or (d)elete?", "[kd]", "k")
+ r = ""
+ if linear_path or allow:
+ r = self.ui.prompt(
+ (" local changed %s which remote deleted\n" % f) +
+ "(k)eep or (d)elete?", "[kd]", "k")
if r == "d":
remove.append(f)
else:
@@ -1087,9 +1119,11 @@ class localrepository:
for f, n in m2.iteritems():
if f[0] == "/": continue
if not force and f in ma and n != ma[f]:
- r = self.ui.prompt(
- ("remote changed %s which local deleted\n" % f) +
- "(k)eep or (d)elete?", "[kd]", "k")
+ r = ""
+ if linear_path or allow:
+ r = self.ui.prompt(
+ ("remote changed %s which local deleted\n" % f) +
+ "(k)eep or (d)elete?", "[kd]", "k")
if r == "d": remove.append(f)
else:
self.ui.debug("remote created %s\n" % f)
@@ -1102,7 +1136,7 @@ class localrepository:
get[f] = merge[f][1]
merge = {}
- if pa == p1 or pa == p2:
+ if linear_path:
# we don't need to do any magic, just jump to the new rev
mode = 'n'
p1, p2 = p2, nullid
@@ -1166,7 +1200,7 @@ class localrepository:
def temp(prefix, node):
pre = "%s~%s." % (os.path.basename(fn), prefix)
(fd, name) = tempfile.mkstemp("", pre)
- f = os.fdopen(fd, "w")
+ f = os.fdopen(fd, "wb")
f.write(fl.revision(node))
f.close()
return name
--- a/mercurial/lock.py
+++ b/mercurial/lock.py
@@ -6,6 +6,7 @@
# of the GNU General Public License, incorporated herein by reference.
import os, time
+import util
class LockHeld(Exception):
pass
@@ -34,10 +35,10 @@ class lock:
def trylock(self):
pid = os.getpid()
try:
- os.symlink(str(pid), self.f)
+ util.makelock(str(pid), self.f)
self.held = 1
except:
- raise LockHeld(os.readlink(self.f))
+ raise LockHeld(util.readlock(self.f))
def release(self):
if self.held:
--- a/mercurial/mpatch.c
+++ b/mercurial/mpatch.c
@@ -23,8 +23,22 @@
#include <Python.h>
#include <stdlib.h>
#include <string.h>
-#include <netinet/in.h>
-#include <sys/types.h>
+#ifdef _WIN32
+
+typedef unsigned long uint32_t;
+
+static uint32_t ntohl(uint32_t x)
+{
+ return ((x & 0x000000ffUL) << 24) |
+ ((x & 0x0000ff00UL) << 8) |
+ ((x & 0x00ff0000UL) >> 8) |
+ ((x & 0xff000000UL) >> 24);
+}
+
+#else
+ #include <netinet/in.h>
+ #include <sys/types.h>
+#endif
static char mpatch_doc[] = "Efficient binary patching.";
--- a/mercurial/transaction.py
+++ b/mercurial/transaction.py
@@ -12,6 +12,7 @@
# of the GNU General Public License, incorporated herein by reference.
import os
+import util
class transaction:
def __init__(self, opener, journal, after = None):
@@ -46,7 +47,7 @@ class transaction:
self.file.close()
self.entries = []
if self.after:
- os.rename(self.journal, self.after)
+ util.rename(self.journal, self.after)
else:
os.unlink(self.journal)
new file mode 100644
--- /dev/null
+++ b/mercurial/util.py
@@ -0,0 +1,39 @@
+# util.py - utility functions and platform specfic implementations
+#
+# Copyright 2005 K. Thananchayan <thananck@yahoo.com>
+#
+# This software may be used and distributed according to the terms
+# of the GNU General Public License, incorporated herein by reference.
+
+import os
+
+def rename(src, dst):
+ try:
+ os.rename(src, dst)
+ except:
+ os.unlink(dst)
+ os.rename(src, dst)
+
+# Platfor specific varients
+if os.name == 'nt':
+ def pconvert(path):
+ return path.replace("\\", "/")
+
+ def makelock(info, pathname):
+ ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
+ os.write(ld, info)
+ os.close(ld)
+
+ def readlock(pathname):
+ return file(pathname).read()
+else:
+ def pconvert(path):
+ return path
+
+ def makelock(info, pathname):
+ os.symlink(info, pathname)
+
+ def readlock(pathname):
+ return os.readlink(pathname)
+
+
--- a/setup.py
+++ b/setup.py
@@ -23,19 +23,20 @@ class install_package_data(install_data)
try:
mercurial.version.remember_version(version)
setup(name='mercurial',
- version=mercurial.version.get_version(),
- author='Matt Mackall',
- author_email='mpm@selenic.com',
- url='http://selenic.com/mercurial',
- description='scalable distributed SCM',
- license='GNU GPL',
- packages=['mercurial'],
- ext_modules=[Extension('mercurial.mpatch', ['mercurial/mpatch.c'])],
- data_files=[('mercurial/templates',
- ['templates/map'] +
- glob.glob('templates/map-*') +
- glob.glob('templates/*.tmpl'))],
- cmdclass = { 'install_data' : install_package_data },
- scripts=['hg', 'hgmerge'])
+ version=mercurial.version.get_version(),
+ author='Matt Mackall',
+ author_email='mpm@selenic.com',
+ url='http://selenic.com/mercurial',
+ description='scalable distributed SCM',
+ license='GNU GPL',
+ packages=['mercurial'],
+ ext_modules=[Extension('mercurial.mpatch', ['mercurial/mpatch.c']),
+ Extension('mercurial.bdiff', ['mercurial/bdiff.c'])],
+ data_files=[('mercurial/templates',
+ ['templates/map'] +
+ glob.glob('templates/map-*') +
+ glob.glob('templates/*.tmpl'))],
+ cmdclass = { 'install_data' : install_package_data },
+ scripts=['hg', 'hgmerge'])
finally:
mercurial.version.forget_version()
--- a/tests/run-tests
+++ b/tests/run-tests
@@ -6,8 +6,22 @@ tests=0
failed=0
H=$PWD
+TESTPATH=$PWD/install/bin
+export PATH=$TESTPATH:$PATH
+export PYTHONPATH=$PWD/install/lib/python
+
+rm -rf install
+cd ..
+${PYTHON:-python} setup.py install --home=tests/install > tests/install.err
+if [ $? != 0 ] ; then
+ cat tests/install.err
+fi
+cd $H
+rm install.err
+
function run_one
{
+ rm -f $1.err
export TZ=GMT
D=`mktemp -d`
if [ "$D" == "" ] ; then
@@ -17,20 +31,20 @@ function run_one
cd $D
fail=0
- if ! $H/$f > .out 2>&1 ; then
- echo $f failed with error code $?
+ if ! $H/$1 > .out 2>&1 ; then
+ echo $1 failed with error code $?
fail=1
fi
- if [ -s .out -a ! -r $H/$f.out ] ; then
- echo $f generated unexpected output:
+ if [ -s .out -a ! -r $H/$1.out ] ; then
+ echo $1 generated unexpected output:
cat .out
- cp .out $H/$f.err
+ cp .out $H/$1.err
fail=1
- elif [ -r $H/$f.out ] && ! diff -u $H/$f.out .out > /dev/null ; then
- echo $f output changed:
- diff -u $H/$f.out .out && true
- cp .out $H/$f.err
+ elif [ -r $H/$1.out ] && ! diff -u $H/$1.out .out > /dev/null ; then
+ echo $1 output changed:
+ diff -u $H/$1.out .out && true
+ cp .out $H/$1.err
fail=1
fi
@@ -52,6 +66,8 @@ for f in $TESTS ; do
tests=$[$tests + 1]
done
+rm -rf install
+
echo
echo Ran $tests tests, $failed failed
new file mode 100755
--- /dev/null
+++ b/tests/test-bdiff
@@ -0,0 +1,42 @@
+#!/usr/bin/env python
+
+import sys
+from mercurial import bdiff, mpatch
+
+def test1(a, b):
+ d = bdiff.bdiff(a, b)
+ c = a
+ if d:
+ c = mpatch.patches(a, [d])
+ if c != b:
+ print "***", `a`, `b`
+ print "bad:"
+ print `c`[:200]
+ print `d`
+
+def test(a, b):
+ print "***", `a`, `b`
+ test1(a, b)
+ test1(b, a)
+
+test("a\nc\n\n\n\n", "a\nb\n\n\n")
+test("a\nb\nc\n", "a\nc\n")
+test("", "")
+test("a\nb\nc", "a\nb\nc")
+test("a\nb\nc\nd\n", "a\nd\n")
+test("a\nb\nc\nd\n", "a\nc\ne\n")
+test("a\nb\nc\n", "a\nc\n")
+test("a\n", "c\na\nb\n")
+test("a\n", "")
+test("a\n", "b\nc\n")
+test("a\n", "c\na\n")
+test("", "adjfkjdjksdhfksj")
+test("", "ab")
+test("", "abc")
+test("a", "a")
+test("ab", "ab")
+test("abc", "abc")
+test("a\n", "a\n")
+test("a\nb", "a\nb")
+
+print "done"
new file mode 100644
--- /dev/null
+++ b/tests/test-bdiff.out
@@ -0,0 +1,20 @@
+*** 'a\nc\n\n\n\n' 'a\nb\n\n\n'
+*** 'a\nb\nc\n' 'a\nc\n'
+*** '' ''
+*** 'a\nb\nc' 'a\nb\nc'
+*** 'a\nb\nc\nd\n' 'a\nd\n'
+*** 'a\nb\nc\nd\n' 'a\nc\ne\n'
+*** 'a\nb\nc\n' 'a\nc\n'
+*** 'a\n' 'c\na\nb\n'
+*** 'a\n' ''
+*** 'a\n' 'b\nc\n'
+*** 'a\n' 'c\na\n'
+*** '' 'adjfkjdjksdhfksj'
+*** '' 'ab'
+*** '' 'abc'
+*** 'a' 'a'
+*** 'ab' 'ab'
+*** 'abc' 'abc'
+*** 'a\n' 'a\n'
+*** 'a\nb' 'a\nb'
+done
new file mode 100755
--- /dev/null
+++ b/tests/test-flags
@@ -0,0 +1,33 @@
+#!/bin/sh +ex
+
+mkdir test1
+cd test1
+
+hg init
+touch a b
+hg add a b
+hg ci -t "added a b" -u test -d "0 0"
+
+cd ..
+mkdir test2
+cd test2
+
+hg init
+hg merge ../test1
+hg co
+chmod +x a
+hg ci -t "chmod +x a" -u test -d "0 0"
+
+cd ../test1
+echo 123 >>a
+hg ci -t "a updated" -u test -d "0 0"
+
+hg merge ../test2
+hg heads
+hg history
+
+hg -dv co -m
+
+ls -l ../test[12]/a > foo
+cut -b 0-10 < foo
+
new file mode 100644
--- /dev/null
+++ b/tests/test-flags.out
@@ -0,0 +1,51 @@
+pulling from ../test1
+requesting all changes
+adding changesets
+adding manifests
+adding file revisions
+modified 2 files, added 1 changesets and 2 new revisions
+(run 'hg update' to get a working copy)
+pulling from ../test2
+searching for changes
+adding changesets
+adding manifests
+adding file revisions
+modified 1 files, added 1 changesets and 1 new revisions
+(run 'hg update' to get a working copy)
+changeset: 2:3ef54330565526bebf37a0d9bf540c283fd133a1
+tag: tip
+parent: 0:22a449e20da501ca558394c083ca470e9c81b9f7
+user: test
+date: Thu Jan 1 00:00:00 1970
+summary: chmod +x a
+
+changeset: 1:c6ecefc45368ed556d965f1c1086c6561a8b2ac5
+user: test
+date: Thu Jan 1 00:00:00 1970
+summary: a updated
+
+changeset: 2:3ef54330565526bebf37a0d9bf540c283fd133a1
+tag: tip
+parent: 0:22a449e20da501ca558394c083ca470e9c81b9f7
+user: test
+date: Thu Jan 1 00:00:00 1970
+summary: chmod +x a
+
+changeset: 1:c6ecefc45368ed556d965f1c1086c6561a8b2ac5
+user: test
+date: Thu Jan 1 00:00:00 1970
+summary: a updated
+
+changeset: 0:22a449e20da501ca558394c083ca470e9c81b9f7
+user: test
+date: Thu Jan 1 00:00:00 1970
+summary: added a b
+
+resolving manifests
+ ancestor f328b97f7c11 local e7f06daf1cdb remote 629f0b785e0e
+ a versions differ, resolve
+merging a
+resolving a
+file a: other 37c42bd6cc03 ancestor b80de5d13875
+-rwxr-xr-x
+-rwxr-xr-x
--- a/tests/test-help.out
+++ b/tests/test-help.out
@@ -26,6 +26,7 @@ hg commands:
remove remove the specified files on the next commit
serve export the repository via HTTP
status show changed files in the working directory
+ tag add a tag for the current tip or a given revision
tags list repository tags
tip show the tip revision
undo undo the last transaction
@@ -74,6 +75,7 @@ hg commands:
remove remove the specified files on the next commit
serve export the repository via HTTP
status show changed files in the working directory
+ tag add a tag for the current tip or a given revision
tags list repository tags
tip show the tip revision
undo undo the last transaction
new file mode 100755
--- /dev/null
+++ b/tests/test-merge1
@@ -0,0 +1,85 @@
+#!/bin/sh -x
+
+cat <<'EOF' > merge
+#!/bin/sh
+echo merging for `basename $1`
+EOF
+chmod +x merge
+
+mkdir t
+cd t
+hg init
+echo This is file a1 > a
+hg add a
+hg commit -t "commit #0" -d "0 0" -u user
+echo This is file b1 > b
+hg add b
+hg commit -t "commit #1" -d "0 0" -u user
+
+hg update 0
+echo This is file c1 > c
+hg add c
+hg commit -t "commit #2" -d "0 0" -u user
+echo This is file b1 > b
+env HGMERGE=../merge hg update -m 1
+# no merges expected
+cd ..; /bin/rm -rf t
+
+mkdir t
+cd t
+hg init
+echo This is file a1 > a
+hg add a
+hg commit -t "commit #0" -d "0 0" -u user
+echo This is file b1 > b
+hg add b
+hg commit -t "commit #1" -d "0 0" -u user
+
+hg update 0
+echo This is file c1 > c
+hg add c
+hg commit -t "commit #2" -d "0 0" -u user
+echo This is file b2 > b
+env HGMERGE=../merge hg update -m 1
+# merge of b expected
+cd ..; /bin/rm -rf t
+
+mkdir t
+cd t
+hg init
+echo This is file a1 > a
+hg add a
+hg commit -t "commit #0" -d "0 0" -u user
+echo This is file b1 > b
+hg add b
+hg commit -t "commit #1" -d "0 0" -u user
+echo This is file b22 > b
+hg commit -t "commit #2" -d "0 0" -u user
+hg update 1
+echo This is file c1 > c
+hg add c
+hg commit -t "commit #3" -d "0 0" -u user
+echo This is file b22 > b
+env HGMERGE=../merge hg update -m 2
+# no merges expected
+cd ..; /bin/rm -rf t
+
+mkdir t
+cd t
+hg init
+echo This is file a1 > a
+hg add a
+hg commit -t "commit #0" -d "0 0" -u user
+echo This is file b1 > b
+hg add b
+hg commit -t "commit #1" -d "0 0" -u user
+echo This is file b22 > b
+hg commit -t "commit #2" -d "0 0" -u user
+hg update 1
+echo This is file c1 > c
+hg add c
+hg commit -t "commit #3" -d "0 0" -u user
+echo This is file b33 > b
+env HGMERGE=../merge hg update -m 2
+# merge of b expected
+cd ..; /bin/rm -rf t
new file mode 100644
--- /dev/null
+++ b/tests/test-merge1.out
@@ -0,0 +1,78 @@
++ cat
++ chmod +x merge
++ mkdir t
++ cd t
++ hg init
++ echo This is file a1
++ hg add a
++ hg commit -t 'commit #0' -d '0 0' -u user
++ echo This is file b1
++ hg add b
++ hg commit -t 'commit #1' -d '0 0' -u user
++ hg update 0
++ echo This is file c1
++ hg add c
++ hg commit -t 'commit #2' -d '0 0' -u user
++ echo This is file b1
++ env HGMERGE=../merge hg update -m 1
++ cd ..
++ /bin/rm -rf t
++ mkdir t
++ cd t
++ hg init
++ echo This is file a1
++ hg add a
++ hg commit -t 'commit #0' -d '0 0' -u user
++ echo This is file b1
++ hg add b
++ hg commit -t 'commit #1' -d '0 0' -u user
++ hg update 0
++ echo This is file c1
++ hg add c
++ hg commit -t 'commit #2' -d '0 0' -u user
++ echo This is file b2
++ env HGMERGE=../merge hg update -m 1
+merging for b
+merging b
++ cd ..
++ /bin/rm -rf t
++ mkdir t
++ cd t
++ hg init
++ echo This is file a1
++ hg add a
++ hg commit -t 'commit #0' -d '0 0' -u user
++ echo This is file b1
++ hg add b
++ hg commit -t 'commit #1' -d '0 0' -u user
++ echo This is file b22
++ hg commit -t 'commit #2' -d '0 0' -u user
++ hg update 1
++ echo This is file c1
++ hg add c
++ hg commit -t 'commit #3' -d '0 0' -u user
++ echo This is file b22
++ env HGMERGE=../merge hg update -m 2
++ cd ..
++ /bin/rm -rf t
++ mkdir t
++ cd t
++ hg init
++ echo This is file a1
++ hg add a
++ hg commit -t 'commit #0' -d '0 0' -u user
++ echo This is file b1
++ hg add b
++ hg commit -t 'commit #1' -d '0 0' -u user
++ echo This is file b22
++ hg commit -t 'commit #2' -d '0 0' -u user
++ hg update 1
++ echo This is file c1
++ hg add c
++ hg commit -t 'commit #3' -d '0 0' -u user
++ echo This is file b33
++ env HGMERGE=../merge hg update -m 2
+merging for b
+merging b
++ cd ..
++ /bin/rm -rf t
new file mode 100755
--- /dev/null
+++ b/tests/test-merge2
@@ -0,0 +1,48 @@
+#!/bin/sh -x
+
+mkdir t
+cd t
+hg init
+echo This is file a1 > a
+hg add a
+hg commit -t "commit #0" -d "0 0" -u user
+echo This is file b1 > b
+hg add b
+hg commit -t "commit #1" -d "0 0" -u user
+rm b
+hg update 0
+echo This is file b2 > b
+hg add b
+hg commit -t "commit #2" -d "0 0" -u user
+cd ..; /bin/rm -rf t
+
+mkdir t
+cd t
+hg init
+echo This is file a1 > a
+hg add a
+hg commit -t "commit #0" -d "0 0" -u user
+echo This is file b1 > b
+hg add b
+hg commit -t "commit #1" -d "0 0" -u user
+rm b
+hg update 0
+echo This is file b2 > b
+hg commit -A -t "commit #2" -d "0 0" -u user
+cd ..; /bin/rm -rf t
+
+mkdir t
+cd t
+hg init
+echo This is file a1 > a
+hg add a
+hg commit -t "commit #0" -d "0 0" -u user
+echo This is file b1 > b
+hg add b
+hg commit -t "commit #1" -d "0 0" -u user
+rm b
+hg remove b
+hg update 0
+echo This is file b2 > b
+hg commit -A -t "commit #2" -d "0 0" -u user
+cd ..; /bin/rm -rf t
new file mode 100644
--- /dev/null
+++ b/tests/test-merge2.out
@@ -0,0 +1,47 @@
++ mkdir t
++ cd t
++ hg init
++ echo This is file a1
++ hg add a
++ hg commit -t 'commit #0' -d '0 0' -u user
++ echo This is file b1
++ hg add b
++ hg commit -t 'commit #1' -d '0 0' -u user
++ rm b
++ hg update 0
++ echo This is file b2
++ hg add b
++ hg commit -t 'commit #2' -d '0 0' -u user
++ cd ..
++ /bin/rm -rf t
++ mkdir t
++ cd t
++ hg init
++ echo This is file a1
++ hg add a
++ hg commit -t 'commit #0' -d '0 0' -u user
++ echo This is file b1
++ hg add b
++ hg commit -t 'commit #1' -d '0 0' -u user
++ rm b
++ hg update 0
++ echo This is file b2
++ hg commit -A -t 'commit #2' -d '0 0' -u user
++ cd ..
++ /bin/rm -rf t
++ mkdir t
++ cd t
++ hg init
++ echo This is file a1
++ hg add a
++ hg commit -t 'commit #0' -d '0 0' -u user
++ echo This is file b1
++ hg add b
++ hg commit -t 'commit #1' -d '0 0' -u user
++ rm b
++ hg remove b
++ hg update 0
++ echo This is file b2
++ hg commit -A -t 'commit #2' -d '0 0' -u user
++ cd ..
++ /bin/rm -rf t
new file mode 100755
--- /dev/null
+++ b/tests/test-merge3
@@ -0,0 +1,10 @@
+#!/bin/sh -x
+
+hg init
+echo This is file a1 > a
+hg add a
+hg commit -t "commit #0" -d "0 0" -u user
+touch b
+hg add b
+rm b
+hg commit -A -t"comment #1" -d "0 0" -u user
new file mode 100644
--- /dev/null
+++ b/tests/test-merge3.out
@@ -0,0 +1,9 @@
++ hg init
++ echo This is file a1
++ hg add a
++ hg commit -t 'commit #0' -d '0 0' -u user
++ touch b
++ hg add b
++ rm b
++ hg commit -A '-tcomment #1' -d '0 0' -u user
+b never committed!
new file mode 100755
--- /dev/null
+++ b/tests/test-merge4
@@ -0,0 +1,17 @@
+#!/bin/sh -x
+
+hg init
+echo This is file a1 > a
+hg add a
+hg commit -t "commit #0" -d "0 0" -u user
+echo This is file b1 > b
+hg add b
+hg commit -t "commit #1" -d "0 0" -u user
+hg update 0
+echo This is file c1 > c
+hg add c
+hg commit -t "commit #2" -d "0 0" -u user
+hg update -m 1
+rm b
+echo This is file c22 > c
+hg commit -t "commit #3" -d "0 0" -u user
new file mode 100644
--- /dev/null
+++ b/tests/test-merge4.out
@@ -0,0 +1,15 @@
++ hg init
++ echo This is file a1
++ hg add a
++ hg commit -t 'commit #0' -d '0 0' -u user
++ echo This is file b1
++ hg add b
++ hg commit -t 'commit #1' -d '0 0' -u user
++ hg update 0
++ echo This is file c1
++ hg add c
++ hg commit -t 'commit #2' -d '0 0' -u user
++ hg update -m 1
++ rm b
++ echo This is file c22
++ hg commit -t 'commit #3' -d '0 0' -u user
new file mode 100755
--- /dev/null
+++ b/tests/test-merge5
@@ -0,0 +1,21 @@
+#!/bin/sh -x
+
+mkdir t
+cd t
+hg init
+echo This is file a1 > a
+echo This is file b1 > b
+hg add a b
+hg commit -t "commit #0" -d "0 0" -u user
+echo This is file b22 > b
+hg commit -t"comment #1" -d "0 0" -u user
+hg update 0
+rm b
+hg commit -A -t"comment #2" -d "0 0" -u user
+# in theory, we shouldn't need the "yes k" below, but it prevents
+# this test from hanging when "hg update" erroneously prompts the
+# user for "keep or delete"
+yes k | hg update 1
+# we exit with 0 to avoid the unavoidable SIGPIPE from above causing
+# us to fail this test
+exit 0
new file mode 100644
--- /dev/null
+++ b/tests/test-merge5.out
@@ -0,0 +1,18 @@
++ mkdir t
++ cd t
++ hg init
++ echo This is file a1
++ echo This is file b1
++ hg add a b
++ hg commit -t 'commit #0' -d '0 0' -u user
++ echo This is file b22
++ hg commit '-tcomment #1' -d '0 0' -u user
++ hg update 0
++ rm b
++ hg commit -A '-tcomment #2' -d '0 0' -u user
++ yes k
++ hg update 1
+this update spans a branch affecting the following files:
+aborting update spanning branches!
+(use update -m to perform a branch merge)
++ exit 0
--- a/tests/test-simple-update.out
+++ b/tests/test-simple-update.out
@@ -19,11 +19,13 @@ 1 files, 1 changesets, 1 total revisions
+ hg commit -t 2
+ cd ../test
+ hg pull ../branch
+pulling from ../branch
searching for changes
adding changesets
adding manifests
adding file revisions
modified 1 files, added 1 changesets and 1 new revisions
+(run 'hg update' to get a working copy)
+ hg verify
checking changesets
checking manifests
new file mode 100755
--- /dev/null
+++ b/tests/test-tag
@@ -0,0 +1,13 @@
+#!/bin/sh -x
+
+hg init
+echo a > a
+hg add a
+hg commit -t "test" -u test -d "0 0"
+hg history
+hg tag -u test -d "0 0" "bleah"
+hg history
+
+echo foo >> .hgtags
+hg tag -u test -d "0 0" "bleah2" || echo "failed"
+
new file mode 100644
--- /dev/null
+++ b/tests/test-tag.out
@@ -0,0 +1,31 @@
++ hg init
++ echo a
++ hg add a
++ hg commit -t test -u test -d '0 0'
++ hg history
+changeset: 0:acb14030fe0a21b60322c440ad2d20cf7685a376
+tag: tip
+user: test
+date: Thu Jan 1 00:00:00 1970
+summary: test
+
++ hg tag -u test -d '0 0' bleah
++ hg history
+changeset: 1:863197ef03781c4fc00276d83eb66c4cb9cd91df
+tag: tip
+user: test
+date: Thu Jan 1 00:00:00 1970
+summary: Added tag bleah for changeset acb14030fe0a21b60322c440ad2d20cf7685a376
+
+changeset: 0:acb14030fe0a21b60322c440ad2d20cf7685a376
+tag: bleah
+user: test
+date: Thu Jan 1 00:00:00 1970
+summary: test
+
++ echo foo
++ hg tag -u test -d '0 0' bleah2
+abort: working copy of .hgtags is changed!
+(please commit .hgtags manually)
++ echo failed
+failed