{"thread":{"id":"31976","subject":"[PATCH v5 00/14] New remote-hg helper","startedAt":"2012-10-30T04:35:22Z","lastAt":"2012-11-13T03:45:02Z","messageCount":26,"participants":["Felipe Contreras","Chris Webb","Johannes Schindelin"],"isPatch":true,"patchVersion":5,"patchTotal":14},"messages":[{"id":"202162","messageId":"1351571736-4682-1-git-send-email-felipe.contreras@gmail.com","threadId":"31976","inReplyTo":null,"subject":"[PATCH v5 00/14] New remote-hg helper","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2012-10-30T04:35:22Z","receivedAt":"2012-10-30T04:35:22Z","isPatch":true,"sender":{"key":"felipe.contreras@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8358?v=4"},"body":"Hi,\n\nI've ported the tests from hg-git and made sure that the output from remote-hg\nmatches the output of hg-git. With these extensive tests I would consider this\none ready for wide use. Not only do the tests pass, I've compared the generated\nrepos of a few projects, and the SHA-1's are exactly the same :)\n\nThis remote-hg has advantages other tools don't have:\n\n * Uses transport-helper (git clone hg::path)\n * The code is small\n * The code is simple\n * No external dependencies (other than mercurial)\n * It's easy to install (put into your path)\n * Has extensive tests\n * Active development\n * Has compatibility with hg-git\n * The required patches are available\n * No changes necesary to git core\n\nOne important alternative is the one written by Sverre Rabbelier that is now\nmaintained and distributed in msysgit. It's hard to evaluate this option as\nthere isn't a branch specific to this remote helper so it would be possible to\nevaluate the necessary patches.\n\nTo use it add it to your $PATH (e.g. ~/bin).\n\n % git clone hd:///full/path/or/url/to/hg/repo\n\nTo run the tests:\n\n % make -C contrib/remote-hg tests\n\nThe only caveat is that you need 'python' in your $PATH.\n\nChanges since v4:\n\n * Add new simple biridectional tests (no need for hg-git)\n * Move tests to contrib directory\n * Add dependency checks to the tests\n * Trivial fixes\n\nChanges since v3:\n\n * New extensive tests\n * Add compatibility mode with hg-git\n * Added support for boomkars\n * Add mercurial information to the git msg (branch, renames, extra, etc.)\n\nChanges since v2:\n\n * Added support for pushing\n * Tests copied from original remote-hg\n * Custom default -> master renames removed\n * Code reorganized\n\nChanges since v1:\n\n * Improved documentation\n * Use more common 'python' binary\n * Warn, don't barf when a branch has multiple heads\n * Fixed marks to fetch after cloned\n * Support for cloning/pulling remote repositories\n * Use a more appropriate internal directory (e.g. .git/hg/origin)\n * Fixes for python3\n\nFelipe Contreras (14):\n  Add new remote-hg transport helper\n  remote-hg: add support for bookmarks\n  remote-hg: add support for pushing\n  remote-hg: add support for remote pushing\n  remote-hg: add support to push URLs\n  remote-hg: make sure the encoding is correct\n  remote-hg: match hg merge behavior\n  remote-hg: add support for hg-git compat mode\n  remote-hg: add compat for hg-git author fixes\n  remote-hg: fake bookmark when there's none\n  remote-hg: add support for fake remote\n  remote-hg: add biridectional tests\n  remote-hg: add tests to compare with hg-git\n  remote-hg: add extra author test\n\n contrib/remote-hg/Makefile       |  13 +\n contrib/remote-hg/git-remote-hg  | 773 +++++++++++++++++++++++++++++++++++++++\n contrib/remote-hg/test-hg-git.sh | 464 +++++++++++++++++++++++\n contrib/remote-hg/test.sh        | 241 ++++++++++++\n 4 files changed, 1491 insertions(+)\n create mode 100644 contrib/remote-hg/Makefile\n create mode 100755 contrib/remote-hg/git-remote-hg\n create mode 100755 contrib/remote-hg/test-hg-git.sh\n create mode 100755 contrib/remote-hg/test.sh\n\n-- \n1.8.0\n"},{"id":"202164","messageId":"1351571736-4682-2-git-send-email-felipe.contreras@gmail.com","threadId":"31976","inReplyTo":"1351571736-4682-1-git-send-email-felipe.contreras@gmail.com","subject":"[PATCH v5 01/14] Add new remote-hg transport helper","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2012-10-30T04:35:23Z","receivedAt":"2012-10-30T04:35:23Z","isPatch":true,"sender":{"key":"felipe.contreras@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8358?v=4"},"body":"Signed-off-by: Felipe Contreras <felipe.contreras@gmail.com>\n---\n contrib/remote-hg/git-remote-hg | 359 ++++++++++++++++++++++++++++++++++++++++\n 1 file changed, 359 insertions(+)\n create mode 100755 contrib/remote-hg/git-remote-hg\n\ndiff --git a/contrib/remote-hg/git-remote-hg b/contrib/remote-hg/git-remote-hg\nnew file mode 100755\nindex 0000000..67d39fa\n--- /dev/null\n+++ b/contrib/remote-hg/git-remote-hg\n@@ -0,0 +1,359 @@\n+#!/usr/bin/env python\n+#\n+# Copyright (c) 2012 Felipe Contreras\n+#\n+\n+# Inspired by Rocco Rutte's hg-fast-export\n+\n+# Just copy to your ~/bin, or anywhere in your $PATH.\n+# Then you can clone with:\n+# git clone hg::/path/to/mercurial/repo/\n+\n+from mercurial import hg, ui\n+\n+import re\n+import sys\n+import os\n+import json\n+\n+NAME_RE = re.compile('^([^<>]+)')\n+AUTHOR_RE = re.compile('^([^<>]+?)? ?<([^<>]+)>$')\n+\n+def die(msg, *args):\n+    sys.stderr.write('ERROR: %s\\n' % (msg % args))\n+    sys.exit(1)\n+\n+def warn(msg, *args):\n+    sys.stderr.write('WARNING: %s\\n' % (msg % args))\n+\n+def gitmode(flags):\n+    return 'l' in flags and '120000' or 'x' in flags and '100755' or '100644'\n+\n+def gittz(tz):\n+    return '%+03d%02d' % (-tz / 3600, -tz % 3600 / 60)\n+\n+class Marks:\n+\n+    def __init__(self, path):\n+        self.path = path\n+        self.tips = {}\n+        self.marks = {}\n+        self.last_mark = 0\n+\n+        self.load()\n+\n+    def load(self):\n+        if not os.path.exists(self.path):\n+            return\n+\n+        tmp = json.load(open(self.path))\n+\n+        self.tips = tmp['tips']\n+        self.marks = tmp['marks']\n+        self.last_mark = tmp['last-mark']\n+\n+    def dict(self):\n+        return { 'tips': self.tips, 'marks': self.marks, 'last-mark' : self.last_mark }\n+\n+    def store(self):\n+        json.dump(self.dict(), open(self.path, 'w'))\n+\n+    def __str__(self):\n+        return str(self.dict())\n+\n+    def from_rev(self, rev):\n+        return self.marks[str(rev)]\n+\n+    def next_mark(self, rev):\n+        self.last_mark += 1\n+        self.marks[str(rev)] = self.last_mark\n+        return self.last_mark\n+\n+    def is_marked(self, rev):\n+        return self.marks.has_key(str(rev))\n+\n+    def get_tip(self, branch):\n+        return self.tips.get(branch, 0)\n+\n+    def set_tip(self, branch, tip):\n+        self.tips[branch] = tip\n+\n+class Parser:\n+\n+    def __init__(self, repo):\n+        self.repo = repo\n+        self.line = self.get_line()\n+\n+    def get_line(self):\n+        return sys.stdin.readline().strip()\n+\n+    def __getitem__(self, i):\n+        return self.line.split()[i]\n+\n+    def check(self, word):\n+        return self.line.startswith(word)\n+\n+    def each_block(self, separator):\n+        while self.line != separator:\n+            yield self.line\n+            self.line = self.get_line()\n+\n+    def __iter__(self):\n+        return self.each_block('')\n+\n+    def next(self):\n+        self.line = self.get_line()\n+        if self.line == 'done':\n+            self.line = None\n+\n+def export_file(fc):\n+    d = fc.data()\n+    print \"M %s inline %s\" % (gitmode(fc.flags()), fc.path())\n+    print \"data %d\" % len(d)\n+    print d\n+\n+def get_filechanges(repo, ctx, parents):\n+    l = [repo.status(p, ctx)[:3] for p in parents]\n+    changed, added, removed = [set(sum(e, [])) for e in zip(*l)]\n+    return added | changed, removed\n+\n+def fixup_user(user):\n+    user = user.replace('\"', '')\n+    name = mail = None\n+    m = AUTHOR_RE.match(user)\n+    if m:\n+        name = m.group(1)\n+        mail = m.group(2).strip()\n+    else:\n+        m = NAME_RE.match(user)\n+        if m:\n+            name = m.group(1).strip()\n+\n+    if not name:\n+        name = 'Unknown'\n+    if not mail:\n+        mail = 'unknown'\n+\n+    return '%s <%s>' % (name, mail)\n+\n+def get_repo(url, alias):\n+    global dirname\n+\n+    myui = ui.ui()\n+    myui.setconfig('ui', 'interactive', 'off')\n+\n+    if hg.islocal(url):\n+        repo = hg.repository(myui, url)\n+    else:\n+        local_path = os.path.join(dirname, 'clone')\n+        if not os.path.exists(local_path):\n+            peer, dstpeer = hg.clone(myui, {}, url, local_path, update=False, pull=True)\n+            repo = dstpeer.local()\n+        else:\n+            repo = hg.repository(myui, local_path)\n+            peer = hg.peer(myui, {}, url)\n+            repo.pull(peer, heads=None, force=True)\n+\n+    return repo\n+\n+def rev_to_mark(rev):\n+    global marks\n+    return marks.from_rev(rev)\n+\n+def export_ref(repo, name, kind, head):\n+    global prefix, marks\n+\n+    ename = '%s/%s' % (kind, name)\n+    tip = marks.get_tip(ename)\n+\n+    # mercurial takes too much time checking this\n+    if tip and tip == head.rev():\n+        # nothing to do\n+        return\n+    revs = repo.revs('%u:%u' % (tip, head))\n+    count = 0\n+\n+    revs = [rev for rev in revs if not marks.is_marked(rev)]\n+\n+    for rev in revs:\n+\n+        c = repo[rev]\n+        (manifest, user, (time, tz), files, desc, extra) = repo.changelog.read(c.node())\n+        rev_branch = extra['branch']\n+\n+        author = \"%s %d %s\" % (fixup_user(user), time, gittz(tz))\n+        if 'committer' in extra:\n+            user, time, tz = extra['committer'].rsplit(' ', 2)\n+            committer = \"%s %s %s\" % (user, time, gittz(int(tz)))\n+        else:\n+            committer = author\n+\n+        parents = [p for p in repo.changelog.parentrevs(rev) if p >= 0]\n+\n+        if len(parents) == 0:\n+            modified = c.manifest().keys()\n+            removed = []\n+        else:\n+            modified, removed = get_filechanges(repo, c, parents)\n+\n+        if len(parents) == 0 and rev:\n+            print 'reset %s/%s' % (prefix, ename)\n+\n+        print \"commit %s/%s\" % (prefix, ename)\n+        print \"mark :%d\" % (marks.next_mark(rev))\n+        print \"author %s\" % (author)\n+        print \"committer %s\" % (committer)\n+        print \"data %d\" % (len(desc))\n+        print desc\n+\n+        if len(parents) > 0:\n+            print \"from :%s\" % (rev_to_mark(parents[0]))\n+            if len(parents) > 1:\n+                print \"merge :%s\" % (rev_to_mark(parents[1]))\n+\n+        for f in removed:\n+            print \"D %s\" % (f)\n+        for f in modified:\n+            export_file(c.filectx(f))\n+        print\n+\n+        count += 1\n+        if (count % 100 == 0):\n+            print \"progress revision %d '%s' (%d/%d)\" % (rev, name, count, len(revs))\n+            print \"#############################################################\"\n+\n+    # make sure the ref is updated\n+    print \"reset %s/%s\" % (prefix, ename)\n+    print \"from :%u\" % rev_to_mark(rev)\n+    print\n+\n+    marks.set_tip(ename, rev)\n+\n+def export_tag(repo, tag):\n+    export_ref(repo, tag, 'tags', repo[tag])\n+\n+def export_branch(repo, branch):\n+    tip = get_branch_tip(repo, branch)\n+    head = repo[tip]\n+    export_ref(repo, branch, 'branches', head)\n+\n+def export_head(repo):\n+    global g_head\n+    export_ref(repo, g_head[0], g_head[1], g_head[2])\n+\n+def do_capabilities(parser):\n+    global prefix, dirname\n+\n+    print \"import\"\n+    print \"refspec refs/heads/branches/*:%s/branches/*\" % prefix\n+    print \"refspec refs/tags/*:%s/tags/*\" % prefix\n+    print\n+\n+def get_branch_tip(repo, branch):\n+    global branches\n+\n+    heads = branches.get(branch, None)\n+    if not heads:\n+        return None\n+\n+    # verify there's only one head\n+    if (len(heads) > 1):\n+        warn(\"Branch '%s' has more than one head, consider merging\" % branch)\n+        return repo.branchtip(branch)\n+\n+    return heads[0]\n+\n+def list_branch_head(repo, cur):\n+    global g_head\n+\n+    tip = get_branch_tip(repo, cur)\n+    head = 'branches/' + cur\n+    print \"@refs/heads/%s HEAD\" % head\n+    g_head = (head, 'branches', repo[tip])\n+\n+def do_list(parser):\n+    global branches\n+\n+    repo = parser.repo\n+    for branch in repo.branchmap():\n+        heads = repo.branchheads(branch)\n+        if len(heads):\n+            branches[branch] = heads\n+\n+    cur = repo.dirstate.branch()\n+\n+    list_branch_head(repo, cur)\n+    for branch in branches:\n+        print \"? refs/heads/branches/%s\" % branch\n+\n+    for tag, node in repo.tagslist():\n+        if tag == 'tip':\n+            continue\n+        print \"? refs/tags/%s\" % tag\n+\n+    print\n+\n+def do_import(parser):\n+    repo = parser.repo\n+\n+    path = os.path.join(dirname, 'marks-git')\n+\n+    print \"feature done\"\n+    if os.path.exists(path):\n+        print \"feature import-marks=%s\" % path\n+    print \"feature export-marks=%s\" % path\n+    sys.stdout.flush()\n+\n+    # lets get all the import lines\n+    while parser.check('import'):\n+        ref = parser[1]\n+\n+        if (ref == 'HEAD'):\n+            export_head(repo)\n+        elif ref.startswith('refs/heads/branches/'):\n+            branch = ref[len('refs/heads/branches/'):]\n+            export_branch(repo, branch)\n+        elif ref.startswith('refs/tags/'):\n+            tag = ref[len('refs/tags/'):]\n+            export_tag(repo, tag)\n+\n+        parser.next()\n+\n+    print 'done'\n+\n+def main(args):\n+    global prefix, dirname, marks, branches\n+\n+    alias = args[1]\n+    url = args[2]\n+\n+    gitdir = os.environ['GIT_DIR']\n+    dirname = os.path.join(gitdir, 'hg', alias)\n+    branches = {}\n+\n+    repo = get_repo(url, alias)\n+    prefix = 'refs/hg/%s' % alias\n+\n+    if not os.path.exists(dirname):\n+        os.makedirs(dirname)\n+\n+    marks_path = os.path.join(dirname, 'marks-hg')\n+    marks = Marks(marks_path)\n+\n+    parser = Parser(repo)\n+    for line in parser:\n+        if parser.check('capabilities'):\n+            do_capabilities(parser)\n+        elif parser.check('list'):\n+            do_list(parser)\n+        elif parser.check('import'):\n+            do_import(parser)\n+        elif parser.check('export'):\n+            do_export(parser)\n+        else:\n+            die('unhandled command: %s' % line)\n+        sys.stdout.flush()\n+\n+    marks.store()\n+\n+sys.exit(main(sys.argv))\n-- \n1.8.0\n"},{"id":"202163","messageId":"1351571736-4682-3-git-send-email-felipe.contreras@gmail.com","threadId":"31976","inReplyTo":"1351571736-4682-1-git-send-email-felipe.contreras@gmail.com","subject":"[PATCH v5 02/14] remote-hg: add support for bookmarks","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2012-10-30T04:35:24Z","receivedAt":"2012-10-30T04:35:24Z","isPatch":true,"sender":{"key":"felipe.contreras@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8358?v=4"},"body":"Signed-off-by: Felipe Contreras <felipe.contreras@gmail.com>\n---\n contrib/remote-hg/git-remote-hg | 20 +++++++++++++++++---\n 1 file changed, 17 insertions(+), 3 deletions(-)\n\ndiff --git a/contrib/remote-hg/git-remote-hg b/contrib/remote-hg/git-remote-hg\nindex 67d39fa..9e29daa 100755\n--- a/contrib/remote-hg/git-remote-hg\n+++ b/contrib/remote-hg/git-remote-hg\n@@ -9,7 +9,7 @@\n # Then you can clone with:\n # git clone hg::/path/to/mercurial/repo/\n \n-from mercurial import hg, ui\n+from mercurial import hg, ui, bookmarks\n \n import re\n import sys\n@@ -232,6 +232,10 @@ def export_ref(repo, name, kind, head):\n def export_tag(repo, tag):\n     export_ref(repo, tag, 'tags', repo[tag])\n \n+def export_bookmark(repo, bmark):\n+    head = bmarks[bmark]\n+    export_ref(repo, bmark, 'bookmarks', head)\n+\n def export_branch(repo, branch):\n     tip = get_branch_tip(repo, branch)\n     head = repo[tip]\n@@ -246,6 +250,7 @@ def do_capabilities(parser):\n \n     print \"import\"\n     print \"refspec refs/heads/branches/*:%s/branches/*\" % prefix\n+    print \"refspec refs/heads/*:%s/bookmarks/*\" % prefix\n     print \"refspec refs/tags/*:%s/tags/*\" % prefix\n     print\n \n@@ -272,7 +277,7 @@ def list_branch_head(repo, cur):\n     g_head = (head, 'branches', repo[tip])\n \n def do_list(parser):\n-    global branches\n+    global branches, bmarks\n \n     repo = parser.repo\n     for branch in repo.branchmap():\n@@ -280,11 +285,16 @@ def do_list(parser):\n         if len(heads):\n             branches[branch] = heads\n \n+    for bmark, node in bookmarks.listbookmarks(repo).iteritems():\n+        bmarks[bmark] = repo[node]\n+\n     cur = repo.dirstate.branch()\n \n     list_branch_head(repo, cur)\n     for branch in branches:\n         print \"? refs/heads/branches/%s\" % branch\n+    for bmark in bmarks:\n+        print \"? refs/heads/%s\" % bmark\n \n     for tag, node in repo.tagslist():\n         if tag == 'tip':\n@@ -313,6 +323,9 @@ def do_import(parser):\n         elif ref.startswith('refs/heads/branches/'):\n             branch = ref[len('refs/heads/branches/'):]\n             export_branch(repo, branch)\n+        elif ref.startswith('refs/heads/'):\n+            bmark = ref[len('refs/heads/'):]\n+            export_bookmark(repo, bmark)\n         elif ref.startswith('refs/tags/'):\n             tag = ref[len('refs/tags/'):]\n             export_tag(repo, tag)\n@@ -322,7 +335,7 @@ def do_import(parser):\n     print 'done'\n \n def main(args):\n-    global prefix, dirname, marks, branches\n+    global prefix, dirname, marks, branches, bmarks\n \n     alias = args[1]\n     url = args[2]\n@@ -330,6 +343,7 @@ def main(args):\n     gitdir = os.environ['GIT_DIR']\n     dirname = os.path.join(gitdir, 'hg', alias)\n     branches = {}\n+    bmarks = {}\n \n     repo = get_repo(url, alias)\n     prefix = 'refs/hg/%s' % alias\n-- \n1.8.0\n"},{"id":"202165","messageId":"1351571736-4682-4-git-send-email-felipe.contreras@gmail.com","threadId":"31976","inReplyTo":"1351571736-4682-1-git-send-email-felipe.contreras@gmail.com","subject":"[PATCH v5 03/14] remote-hg: add support for pushing","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2012-10-30T04:35:25Z","receivedAt":"2012-10-30T04:35:25Z","isPatch":true,"sender":{"key":"felipe.contreras@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8358?v=4"},"body":"Signed-off-by: Felipe Contreras <felipe.contreras@gmail.com>\n---\n contrib/remote-hg/git-remote-hg | 215 +++++++++++++++++++++++++++++++++++++++-\n 1 file changed, 213 insertions(+), 2 deletions(-)\n\ndiff --git a/contrib/remote-hg/git-remote-hg b/contrib/remote-hg/git-remote-hg\nindex 9e29daa..337ba40 100755\n--- a/contrib/remote-hg/git-remote-hg\n+++ b/contrib/remote-hg/git-remote-hg\n@@ -9,7 +9,7 @@\n # Then you can clone with:\n # git clone hg::/path/to/mercurial/repo/\n \n-from mercurial import hg, ui, bookmarks\n+from mercurial import hg, ui, bookmarks, context\n \n import re\n import sys\n@@ -18,6 +18,7 @@ import json\n \n NAME_RE = re.compile('^([^<>]+)')\n AUTHOR_RE = re.compile('^([^<>]+?)? ?<([^<>]+)>$')\n+RAW_AUTHOR_RE = re.compile('^(\\w+) (?:(.+)? )?<(.+)> (\\d+) ([+-]\\d+)')\n \n def die(msg, *args):\n     sys.stderr.write('ERROR: %s\\n' % (msg % args))\n@@ -32,12 +33,17 @@ def gitmode(flags):\n def gittz(tz):\n     return '%+03d%02d' % (-tz / 3600, -tz % 3600 / 60)\n \n+def hgmode(mode):\n+    m = { '0100755': 'x', '0120000': 'l' }\n+    return m.get(mode, '')\n+\n class Marks:\n \n     def __init__(self, path):\n         self.path = path\n         self.tips = {}\n         self.marks = {}\n+        self.rev_marks = {}\n         self.last_mark = 0\n \n         self.load()\n@@ -52,6 +58,9 @@ class Marks:\n         self.marks = tmp['marks']\n         self.last_mark = tmp['last-mark']\n \n+        for rev, mark in self.marks.iteritems():\n+            self.rev_marks[mark] = int(rev)\n+\n     def dict(self):\n         return { 'tips': self.tips, 'marks': self.marks, 'last-mark' : self.last_mark }\n \n@@ -64,11 +73,19 @@ class Marks:\n     def from_rev(self, rev):\n         return self.marks[str(rev)]\n \n+    def to_rev(self, mark):\n+        return self.rev_marks[mark]\n+\n     def next_mark(self, rev):\n         self.last_mark += 1\n         self.marks[str(rev)] = self.last_mark\n         return self.last_mark\n \n+    def new_mark(self, rev, mark):\n+        self.marks[str(rev)] = mark\n+        self.rev_marks[mark] = rev\n+        self.last_mark = mark\n+\n     def is_marked(self, rev):\n         return self.marks.has_key(str(rev))\n \n@@ -106,6 +123,35 @@ class Parser:\n         if self.line == 'done':\n             self.line = None\n \n+    def get_mark(self):\n+        i = self.line.index(':') + 1\n+        return int(self.line[i:])\n+\n+    def get_data(self):\n+        if not self.check('data'):\n+            return None\n+        i = self.line.index(' ') + 1\n+        size = int(self.line[i:])\n+        return sys.stdin.read(size)\n+\n+    def get_author(self):\n+        m = RAW_AUTHOR_RE.match(self.line)\n+        if not m:\n+            return None\n+        _, name, email, date, tz = m.groups()\n+\n+        if email != 'unknown':\n+            if name:\n+                user = '%s <%s>' % (name, email)\n+            else:\n+                user = '<%s>' % (email)\n+        else:\n+            user = name\n+\n+        tz = int(tz)\n+        tz = ((tz / 100) * 3600) + ((tz % 100) * 60)\n+        return (user, int(date), -tz)\n+\n def export_file(fc):\n     d = fc.data()\n     print \"M %s inline %s\" % (gitmode(fc.flags()), fc.path())\n@@ -160,6 +206,10 @@ def rev_to_mark(rev):\n     global marks\n     return marks.from_rev(rev)\n \n+def mark_to_rev(mark):\n+    global marks\n+    return marks.to_rev(mark)\n+\n def export_ref(repo, name, kind, head):\n     global prefix, marks\n \n@@ -249,9 +299,17 @@ def do_capabilities(parser):\n     global prefix, dirname\n \n     print \"import\"\n+    print \"export\"\n     print \"refspec refs/heads/branches/*:%s/branches/*\" % prefix\n     print \"refspec refs/heads/*:%s/bookmarks/*\" % prefix\n     print \"refspec refs/tags/*:%s/tags/*\" % prefix\n+\n+    path = os.path.join(dirname, 'marks-git')\n+\n+    if os.path.exists(path):\n+        print \"*import-marks %s\" % path\n+    print \"*export-marks %s\" % path\n+\n     print\n \n def get_branch_tip(repo, branch):\n@@ -334,8 +392,159 @@ def do_import(parser):\n \n     print 'done'\n \n+def parse_blob(parser):\n+    global blob_marks\n+\n+    parser.next()\n+    mark = parser.get_mark()\n+    parser.next()\n+    data = parser.get_data()\n+    blob_marks[mark] = data\n+    parser.next()\n+    return\n+\n+def parse_commit(parser):\n+    global marks, blob_marks, bmarks, parsed_refs\n+\n+    from_mark = merge_mark = None\n+\n+    a = parser.line.split(' ')\n+    ref = a[1]\n+    parser.next()\n+\n+    commit_mark = parser.get_mark()\n+    parser.next()\n+    author = parser.get_author()\n+    parser.next()\n+    committer = parser.get_author()\n+    parser.next()\n+    data = parser.get_data()\n+    parser.next()\n+    if parser.check('from'):\n+        from_mark = parser.get_mark()\n+        parser.next()\n+    if parser.check('merge'):\n+        merge_mark = parser.get_mark()\n+        parser.next()\n+        if parser.check('merge'):\n+            die('octopus merges are not supported yet')\n+\n+    files = {}\n+\n+    for line in parser:\n+        if parser.check('M'):\n+            t, m, mark_ref, path = line.split(' ')\n+            mark = int(mark_ref[1:])\n+            f = { 'mode' : hgmode(m), 'data' : blob_marks[mark] }\n+        elif parser.check('D'):\n+            t, path = line.split(' ')\n+            f = { 'deleted' : True }\n+        else:\n+            die('Unknown file command: %s' % line)\n+        files[path] = f\n+\n+    def getfilectx(repo, memctx, f):\n+        of = files[f]\n+        if 'deleted' in of:\n+            raise IOError\n+        is_exec = of['mode'] == 'x'\n+        is_link = of['mode'] == 'l'\n+        return context.memfilectx(f, of['data'], is_link, is_exec, None)\n+\n+    repo = parser.repo\n+\n+    user, date, tz = author\n+    extra = {}\n+\n+    if committer != author:\n+        extra['committer'] = \"%s %u %u\" % committer\n+\n+    if from_mark:\n+        p1 = repo.changelog.node(mark_to_rev(from_mark))\n+    else:\n+        p1 = '\\0' * 20\n+\n+    if merge_mark:\n+        p2 = repo.changelog.node(mark_to_rev(merge_mark))\n+    else:\n+        p2 = '\\0' * 20\n+\n+    ctx = context.memctx(repo, (p1, p2), data,\n+            files.keys(), getfilectx,\n+            user, (date, tz), extra)\n+\n+    node = repo.commitctx(ctx)\n+\n+    rev = repo[node].rev()\n+\n+    parsed_refs[ref] = node\n+\n+    marks.new_mark(rev, commit_mark)\n+\n+def parse_reset(parser):\n+    a = parser.line.split(' ')\n+    ref = a[1]\n+    parser.next()\n+    # ugh\n+    if parser.check('commit'):\n+        parse_commit(parser)\n+        return\n+    if not parser.check('from'):\n+        return\n+    from_mark = parser.get_mark()\n+    parser.next()\n+\n+    node = parser.repo.changelog.node(mark_to_rev(from_mark))\n+    parsed_refs[ref] = node\n+\n+def parse_tag(parser):\n+    a = parser.line.split(' ')\n+    name = a[1]\n+    parser.next()\n+    from_mark = parser.get_mark()\n+    parser.next()\n+    tagger = parser.get_author()\n+    parser.next()\n+    data = parser.get_data()\n+    parser.next()\n+\n+    # nothing to do\n+\n+def do_export(parser):\n+    global parsed_refs\n+\n+    parser.next()\n+\n+    for line in parser.each_block('done'):\n+        if parser.check('blob'):\n+            parse_blob(parser)\n+        elif parser.check('commit'):\n+            parse_commit(parser)\n+        elif parser.check('reset'):\n+            parse_reset(parser)\n+        elif parser.check('tag'):\n+            parse_tag(parser)\n+        elif parser.check('feature'):\n+            pass\n+        else:\n+            die('unhandled export command: %s' % line)\n+\n+    for ref, node in parsed_refs.iteritems():\n+        if ref.startswith('refs/heads/branches'):\n+            pass\n+        elif ref.startswith('refs/heads/'):\n+            bmark = ref[len('refs/heads/'):]\n+            bookmarks.pushbookmark(parser.repo, bmark, '', node)\n+        elif ref.startswith('refs/tags/'):\n+            tag = ref[len('refs/tags/'):]\n+            parser.repo.tag([tag], node, None, True, None, {})\n+        print \"ok %s\" % ref\n+\n+    print\n+\n def main(args):\n-    global prefix, dirname, marks, branches, bmarks\n+    global prefix, dirname, branches, bmarks\n+    global marks, blob_marks, parsed_refs\n \n     alias = args[1]\n     url = args[2]\n@@ -344,6 +553,8 @@ def main(args):\n     dirname = os.path.join(gitdir, 'hg', alias)\n     branches = {}\n     bmarks = {}\n+    blob_marks = {}\n+    parsed_refs = {}\n \n     repo = get_repo(url, alias)\n     prefix = 'refs/hg/%s' % alias\n-- \n1.8.0\n"},{"id":"202166","messageId":"1351571736-4682-5-git-send-email-felipe.contreras@gmail.com","threadId":"31976","inReplyTo":"1351571736-4682-1-git-send-email-felipe.contreras@gmail.com","subject":"[PATCH v5 04/14] remote-hg: add support for remote pushing","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2012-10-30T04:35:26Z","receivedAt":"2012-10-30T04:35:26Z","isPatch":true,"sender":{"key":"felipe.contreras@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8358?v=4"},"body":"Signed-off-by: Felipe Contreras <felipe.contreras@gmail.com>\n---\n contrib/remote-hg/git-remote-hg | 9 +++++++--\n 1 file changed, 7 insertions(+), 2 deletions(-)\n\ndiff --git a/contrib/remote-hg/git-remote-hg b/contrib/remote-hg/git-remote-hg\nindex 337ba40..959ab80 100755\n--- a/contrib/remote-hg/git-remote-hg\n+++ b/contrib/remote-hg/git-remote-hg\n@@ -183,7 +183,7 @@ def fixup_user(user):\n     return '%s <%s>' % (name, mail)\n \n def get_repo(url, alias):\n-    global dirname\n+    global dirname, peer\n \n     myui = ui.ui()\n     myui.setconfig('ui', 'interactive', 'off')\n@@ -511,7 +511,7 @@ def parse_tag(parser):\n     # nothing to do\n \n def do_export(parser):\n-    global parsed_refs\n+    global parsed_refs, peer\n \n     parser.next()\n \n@@ -542,12 +542,17 @@ def do_export(parser):\n \n     print\n \n+    if peer:\n+        parser.repo.push(peer, force=False)\n+\n def main(args):\n     global prefix, dirname, branches, bmarks\n     global marks, blob_marks, parsed_refs\n+    global peer\n \n     alias = args[1]\n     url = args[2]\n+    peer = None\n \n     gitdir = os.environ['GIT_DIR']\n     dirname = os.path.join(gitdir, 'hg', alias)\n-- \n1.8.0\n"},{"id":"202167","messageId":"1351571736-4682-6-git-send-email-felipe.contreras@gmail.com","threadId":"31976","inReplyTo":"1351571736-4682-1-git-send-email-felipe.contreras@gmail.com","subject":"[PATCH v5 05/14] remote-hg: add support to push URLs","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2012-10-30T04:35:27Z","receivedAt":"2012-10-30T04:35:27Z","isPatch":true,"sender":{"key":"felipe.contreras@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8358?v=4"},"body":"Signed-off-by: Felipe Contreras <felipe.contreras@gmail.com>\n---\n contrib/remote-hg/git-remote-hg | 14 ++++++++++++--\n 1 file changed, 12 insertions(+), 2 deletions(-)\n\ndiff --git a/contrib/remote-hg/git-remote-hg b/contrib/remote-hg/git-remote-hg\nindex 959ab80..4d49923 100755\n--- a/contrib/remote-hg/git-remote-hg\n+++ b/contrib/remote-hg/git-remote-hg\n@@ -9,12 +9,13 @@\n # Then you can clone with:\n # git clone hg::/path/to/mercurial/repo/\n \n-from mercurial import hg, ui, bookmarks, context\n+from mercurial import hg, ui, bookmarks, context, util\n \n import re\n import sys\n import os\n import json\n+import shutil\n \n NAME_RE = re.compile('^([^<>]+)')\n AUTHOR_RE = re.compile('^([^<>]+?)? ?<([^<>]+)>$')\n@@ -554,6 +555,12 @@ def main(args):\n     url = args[2]\n     peer = None\n \n+    if alias[4:] == url:\n+        is_tmp = True\n+        alias = util.sha1(alias).hexdigest()\n+    else:\n+        is_tmp = False\n+\n     gitdir = os.environ['GIT_DIR']\n     dirname = os.path.join(gitdir, 'hg', alias)\n     branches = {}\n@@ -584,6 +591,9 @@ def main(args):\n             die('unhandled command: %s' % line)\n         sys.stdout.flush()\n \n-    marks.store()\n+    if not is_tmp:\n+        marks.store()\n+    else:\n+        shutil.rmtree(dirname)\n \n sys.exit(main(sys.argv))\n-- \n1.8.0\n"},{"id":"202168","messageId":"1351571736-4682-7-git-send-email-felipe.contreras@gmail.com","threadId":"31976","inReplyTo":"1351571736-4682-1-git-send-email-felipe.contreras@gmail.com","subject":"[PATCH v5 06/14] remote-hg: make sure the encoding is correct","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2012-10-30T04:35:28Z","receivedAt":"2012-10-30T04:35:28Z","isPatch":true,"sender":{"key":"felipe.contreras@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8358?v=4"},"body":"Independently of the environment.\n\nSigned-off-by: Felipe Contreras <felipe.contreras@gmail.com>\n---\n contrib/remote-hg/git-remote-hg | 12 +++++++++++-\n 1 file changed, 11 insertions(+), 1 deletion(-)\n\ndiff --git a/contrib/remote-hg/git-remote-hg b/contrib/remote-hg/git-remote-hg\nindex 4d49923..29824be 100755\n--- a/contrib/remote-hg/git-remote-hg\n+++ b/contrib/remote-hg/git-remote-hg\n@@ -9,7 +9,7 @@\n # Then you can clone with:\n # git clone hg::/path/to/mercurial/repo/\n \n-from mercurial import hg, ui, bookmarks, context, util\n+from mercurial import hg, ui, bookmarks, context, util, encoding\n \n import re\n import sys\n@@ -373,6 +373,9 @@ def do_import(parser):\n     print \"feature export-marks=%s\" % path\n     sys.stdout.flush()\n \n+    tmp = encoding.encoding\n+    encoding.encoding = 'utf-8'\n+\n     # lets get all the import lines\n     while parser.check('import'):\n         ref = parser[1]\n@@ -391,6 +394,8 @@ def do_import(parser):\n \n         parser.next()\n \n+    encoding.encoding = tmp\n+\n     print 'done'\n \n def parse_blob(parser):\n@@ -474,8 +479,13 @@ def parse_commit(parser):\n             files.keys(), getfilectx,\n             user, (date, tz), extra)\n \n+    tmp = encoding.encoding\n+    encoding.encoding = 'utf-8'\n+\n     node = repo.commitctx(ctx)\n \n+    encoding.encoding = tmp\n+\n     rev = repo[node].rev()\n \n     parsed_refs[ref] = node\n-- \n1.8.0\n"},{"id":"202169","messageId":"1351571736-4682-8-git-send-email-felipe.contreras@gmail.com","threadId":"31976","inReplyTo":"1351571736-4682-1-git-send-email-felipe.contreras@gmail.com","subject":"[PATCH v5 07/14] remote-hg: match hg merge behavior","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2012-10-30T04:35:29Z","receivedAt":"2012-10-30T04:35:29Z","isPatch":true,"sender":{"key":"felipe.contreras@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8358?v=4"},"body":"Signed-off-by: Felipe Contreras <felipe.contreras@gmail.com>\n---\n contrib/remote-hg/git-remote-hg | 15 +++++++++++++++\n 1 file changed, 15 insertions(+)\n\ndiff --git a/contrib/remote-hg/git-remote-hg b/contrib/remote-hg/git-remote-hg\nindex 29824be..c28f4b4 100755\n--- a/contrib/remote-hg/git-remote-hg\n+++ b/contrib/remote-hg/git-remote-hg\n@@ -409,6 +409,12 @@ def parse_blob(parser):\n     parser.next()\n     return\n \n+def get_merge_files(repo, p1, p2, files):\n+    for e in repo[p1].files():\n+        if e not in files:\n+            f = { 'ctx' : repo[p1][e] }\n+            files[e] = f\n+\n def parse_commit(parser):\n     global marks, blob_marks, bmarks, parsed_refs\n \n@@ -453,6 +459,8 @@ def parse_commit(parser):\n         of = files[f]\n         if 'deleted' in of:\n             raise IOError\n+        if 'ctx' in of:\n+            return of['ctx']\n         is_exec = of['mode'] == 'x'\n         is_link = of['mode'] == 'l'\n         return context.memfilectx(f, of['data'], is_link, is_exec, None)\n@@ -475,6 +483,13 @@ def parse_commit(parser):\n     else:\n         p2 = '\\0' * 20\n \n+    #\n+    # If files changed from any of the parents, hg wants to know, but in git if\n+    # nothing changed from the first parent, nothing changed.\n+    #\n+    if merge_mark:\n+        get_merge_files(repo, p1, p2, files)\n+\n     ctx = context.memctx(repo, (p1, p2), data,\n             files.keys(), getfilectx,\n             user, (date, tz), extra)\n-- \n1.8.0\n"},{"id":"202170","messageId":"1351571736-4682-9-git-send-email-felipe.contreras@gmail.com","threadId":"31976","inReplyTo":"1351571736-4682-1-git-send-email-felipe.contreras@gmail.com","subject":"[PATCH v5 08/14] remote-hg: add support for hg-git compat mode","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2012-10-30T04:35:30Z","receivedAt":"2012-10-30T04:35:30Z","isPatch":true,"sender":{"key":"felipe.contreras@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8358?v=4"},"body":"Signed-off-by: Felipe Contreras <felipe.contreras@gmail.com>\n---\n contrib/remote-hg/git-remote-hg | 102 +++++++++++++++++++++++++++++++++++++---\n 1 file changed, 95 insertions(+), 7 deletions(-)\n\ndiff --git a/contrib/remote-hg/git-remote-hg b/contrib/remote-hg/git-remote-hg\nindex c28f4b4..e49f9ed 100755\n--- a/contrib/remote-hg/git-remote-hg\n+++ b/contrib/remote-hg/git-remote-hg\n@@ -16,6 +16,22 @@ import sys\n import os\n import json\n import shutil\n+import subprocess\n+\n+#\n+# If you want to switch to hg-git compatibility mode:\n+# git config --global remote-hg.hg-git-compat true\n+#\n+# git:\n+# Sensible defaults for git.\n+# hg bookmarks are exported as git branches, hg branches are prefixed\n+# with 'branches/'.\n+#\n+# hg:\n+# Emulate hg-git.\n+# Only hg bookmarks are exported as git branches.\n+# Commits are modified to preserve hg information and allow biridectionality.\n+#\n \n NAME_RE = re.compile('^([^<>]+)')\n AUTHOR_RE = re.compile('^([^<>]+?)? ?<([^<>]+)>$')\n@@ -212,7 +228,7 @@ def mark_to_rev(mark):\n     return marks.to_rev(mark)\n \n def export_ref(repo, name, kind, head):\n-    global prefix, marks\n+    global prefix, marks, mode\n \n     ename = '%s/%s' % (kind, name)\n     tip = marks.get_tip(ename)\n@@ -247,6 +263,33 @@ def export_ref(repo, name, kind, head):\n         else:\n             modified, removed = get_filechanges(repo, c, parents)\n \n+        if mode == 'hg':\n+            extra_msg = ''\n+\n+            if rev_branch != 'default':\n+                extra_msg += 'branch : %s\\n' % rev_branch\n+\n+            renames = []\n+            for f in c.files():\n+                if f not in c.manifest():\n+                    continue\n+                rename = c.filectx(f).renamed()\n+                if rename:\n+                    renames.append((rename[0], f))\n+\n+            for e in renames:\n+                extra_msg += \"rename : %s => %s\\n\" % e\n+\n+            for key, value in extra.iteritems():\n+                if key in ('author', 'committer', 'encoding', 'message', 'branch', 'hg-git'):\n+                    continue\n+                else:\n+                    extra_msg += \"extra : %s : %s\\n\" % (key, urllib.quote(value))\n+\n+            desc += '\\n'\n+            if extra_msg:\n+                desc += '\\n--HG--\\n' + extra_msg\n+\n         if len(parents) == 0 and rev:\n             print 'reset %s/%s' % (prefix, ename)\n \n@@ -335,8 +378,18 @@ def list_branch_head(repo, cur):\n     print \"@refs/heads/%s HEAD\" % head\n     g_head = (head, 'branches', repo[tip])\n \n+def list_bookmark_head(repo, cur):\n+    global g_head\n+\n+    head = bookmarks.readcurrent(repo)\n+    if not head:\n+        return\n+    node = repo[head]\n+    print \"@refs/heads/%s HEAD\" % head\n+    g_head = (head, 'bookmarks', node)\n+\n def do_list(parser):\n-    global branches, bmarks\n+    global branches, bmarks, mode\n \n     repo = parser.repo\n     for branch in repo.branchmap():\n@@ -349,9 +402,13 @@ def do_list(parser):\n \n     cur = repo.dirstate.branch()\n \n-    list_branch_head(repo, cur)\n-    for branch in branches:\n-        print \"? refs/heads/branches/%s\" % branch\n+    if mode != 'hg':\n+        list_branch_head(repo, cur)\n+        for branch in branches:\n+            print \"? refs/heads/branches/%s\" % branch\n+    else:\n+        list_bookmark_head(repo, cur)\n+\n     for bmark in bmarks:\n         print \"? refs/heads/%s\" % bmark\n \n@@ -417,6 +474,7 @@ def get_merge_files(repo, p1, p2, files):\n \n def parse_commit(parser):\n     global marks, blob_marks, bmarks, parsed_refs\n+    global mode\n \n     from_mark = merge_mark = None\n \n@@ -463,7 +521,9 @@ def parse_commit(parser):\n             return of['ctx']\n         is_exec = of['mode'] == 'x'\n         is_link = of['mode'] == 'l'\n-        return context.memfilectx(f, of['data'], is_link, is_exec, None)\n+        rename = of.get('rename', None)\n+        return context.memfilectx(f, of['data'],\n+                is_link, is_exec, rename)\n \n     repo = parser.repo\n \n@@ -490,6 +550,21 @@ def parse_commit(parser):\n     if merge_mark:\n         get_merge_files(repo, p1, p2, files)\n \n+    if mode == 'hg':\n+        i = data.find('\\n--HG--\\n')\n+        if i >= 0:\n+            tmp = data[i + len('\\n--HG--\\n'):].strip()\n+            for k, v in [e.split(' : ') for e in tmp.split('\\n')]:\n+                if k == 'rename':\n+                    old, new = v.split(' => ', 1)\n+                    files[new]['rename'] = old\n+                elif k == 'branch':\n+                    extra[k] = v\n+                elif k == 'extra':\n+                    ek, ev = v.split(' : ', 1)\n+                    extra[ek] = urllib.unquote(ev)\n+            data = data[:i]\n+\n     ctx = context.memctx(repo, (p1, p2), data,\n             files.keys(), getfilectx,\n             user, (date, tz), extra)\n@@ -574,12 +649,25 @@ def do_export(parser):\n def main(args):\n     global prefix, dirname, branches, bmarks\n     global marks, blob_marks, parsed_refs\n-    global peer\n+    global peer, mode\n \n     alias = args[1]\n     url = args[2]\n     peer = None\n \n+    cmd = ['git', 'config', '--get', 'remote-hg.hg-git-compat']\n+    hg_git_compat = False\n+    try:\n+        if subprocess.check_output(cmd) == 'true\\n':\n+            hg_git_compat = True\n+    except subprocess.CalledProcessError:\n+        pass\n+\n+    if hg_git_compat:\n+        mode = 'hg'\n+    else:\n+        mode = 'git'\n+\n     if alias[4:] == url:\n         is_tmp = True\n         alias = util.sha1(alias).hexdigest()\n-- \n1.8.0\n"},{"id":"202171","messageId":"1351571736-4682-10-git-send-email-felipe.contreras@gmail.com","threadId":"31976","inReplyTo":"1351571736-4682-1-git-send-email-felipe.contreras@gmail.com","subject":"[PATCH v5 09/14] remote-hg: add compat for hg-git author fixes","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2012-10-30T04:35:31Z","receivedAt":"2012-10-30T04:35:31Z","isPatch":true,"sender":{"key":"felipe.contreras@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8358?v=4"},"body":"Signed-off-by: Felipe Contreras <felipe.contreras@gmail.com>\n---\n contrib/remote-hg/git-remote-hg | 59 ++++++++++++++++++++++++++++++++++++-----\n 1 file changed, 53 insertions(+), 6 deletions(-)\n\ndiff --git a/contrib/remote-hg/git-remote-hg b/contrib/remote-hg/git-remote-hg\nindex e49f9ed..c2efadf 100755\n--- a/contrib/remote-hg/git-remote-hg\n+++ b/contrib/remote-hg/git-remote-hg\n@@ -17,6 +17,7 @@ import os\n import json\n import shutil\n import subprocess\n+import urllib\n \n #\n # If you want to switch to hg-git compatibility mode:\n@@ -35,6 +36,7 @@ import subprocess\n \n NAME_RE = re.compile('^([^<>]+)')\n AUTHOR_RE = re.compile('^([^<>]+?)? ?<([^<>]+)>$')\n+AUTHOR_HG_RE = re.compile('^(.*?) ?<(.+?)(?:>(.+)?)?$')\n RAW_AUTHOR_RE = re.compile('^(\\w+) (?:(.+)? )?<(.+)> (\\d+) ([+-]\\d+)')\n \n def die(msg, *args):\n@@ -152,12 +154,20 @@ class Parser:\n         return sys.stdin.read(size)\n \n     def get_author(self):\n+        global bad_mail\n+\n+        ex = None\n         m = RAW_AUTHOR_RE.match(self.line)\n         if not m:\n             return None\n         _, name, email, date, tz = m.groups()\n+        if name and 'ext:' in name:\n+            m = re.match('^(.+?) ext:\\((.+)\\)$', name)\n+            if m:\n+                name = m.group(1)\n+                ex = urllib.unquote(m.group(2))\n \n-        if email != 'unknown':\n+        if email != bad_mail:\n             if name:\n                 user = '%s <%s>' % (name, email)\n             else:\n@@ -165,6 +175,9 @@ class Parser:\n         else:\n             user = name\n \n+        if ex:\n+            user += ex\n+\n         tz = int(tz)\n         tz = ((tz / 100) * 3600) + ((tz % 100) * 60)\n         return (user, int(date), -tz)\n@@ -180,9 +193,9 @@ def get_filechanges(repo, ctx, parents):\n     changed, added, removed = [set(sum(e, [])) for e in zip(*l)]\n     return added | changed, removed\n \n-def fixup_user(user):\n-    user = user.replace('\"', '')\n+def fixup_user_git(user):\n     name = mail = None\n+    user = user.replace('\"', '')\n     m = AUTHOR_RE.match(user)\n     if m:\n         name = m.group(1)\n@@ -191,11 +204,41 @@ def fixup_user(user):\n         m = NAME_RE.match(user)\n         if m:\n             name = m.group(1).strip()\n+    return (name, mail)\n+\n+def fixup_user_hg(user):\n+    def sanitize(name):\n+        # stole this from hg-git\n+        return re.sub('[<>\\n]', '?', name.lstrip('< ').rstrip('> '))\n+\n+    m = AUTHOR_HG_RE.match(user)\n+    if m:\n+        name = sanitize(m.group(1))\n+        mail = sanitize(m.group(2))\n+        ex = m.group(3)\n+        if ex:\n+            name += ' ext:(' + urllib.quote(ex) + ')'\n+    else:\n+        name = sanitize(user)\n+        if '@' in user:\n+            mail = name\n+        else:\n+            mail = None\n+\n+    return (name, mail)\n+\n+def fixup_user(user):\n+    global mode, bad_mail\n+\n+    if mode == 'git':\n+        name, mail = fixup_user_git(user)\n+    else:\n+        name, mail = fixup_user_hg(user)\n \n     if not name:\n-        name = 'Unknown'\n+        name = bad_name\n     if not mail:\n-        mail = 'unknown'\n+        mail = bad_mail\n \n     return '%s <%s>' % (name, mail)\n \n@@ -649,7 +692,7 @@ def do_export(parser):\n def main(args):\n     global prefix, dirname, branches, bmarks\n     global marks, blob_marks, parsed_refs\n-    global peer, mode\n+    global peer, mode, bad_mail, bad_name\n \n     alias = args[1]\n     url = args[2]\n@@ -665,8 +708,12 @@ def main(args):\n \n     if hg_git_compat:\n         mode = 'hg'\n+        bad_mail = 'none@none'\n+        bad_name = ''\n     else:\n         mode = 'git'\n+        bad_mail = 'unknown'\n+        bad_name = 'Unknown'\n \n     if alias[4:] == url:\n         is_tmp = True\n-- \n1.8.0\n"},{"id":"202173","messageId":"1351571736-4682-11-git-send-email-felipe.contreras@gmail.com","threadId":"31976","inReplyTo":"1351571736-4682-1-git-send-email-felipe.contreras@gmail.com","subject":"[PATCH v5 10/14] remote-hg: fake bookmark when there's none","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2012-10-30T04:35:32Z","receivedAt":"2012-10-30T04:35:32Z","isPatch":true,"sender":{"key":"felipe.contreras@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8358?v=4"},"body":"Or at least no current bookmark.\n\nSigned-off-by: Felipe Contreras <felipe.contreras@gmail.com>\n---\n contrib/remote-hg/git-remote-hg | 16 ++++++++++++----\n 1 file changed, 12 insertions(+), 4 deletions(-)\n\ndiff --git a/contrib/remote-hg/git-remote-hg b/contrib/remote-hg/git-remote-hg\nindex c2efadf..c41ec95 100755\n--- a/contrib/remote-hg/git-remote-hg\n+++ b/contrib/remote-hg/git-remote-hg\n@@ -422,12 +422,20 @@ def list_branch_head(repo, cur):\n     g_head = (head, 'branches', repo[tip])\n \n def list_bookmark_head(repo, cur):\n-    global g_head\n+    global g_head, bmarks\n \n     head = bookmarks.readcurrent(repo)\n-    if not head:\n-        return\n-    node = repo[head]\n+    if head:\n+        node = repo[head]\n+    else:\n+        # fake bookmark from current branch\n+        head = cur\n+        tip = get_branch_tip(repo, head)\n+        if not tip:\n+            return\n+        node = repo[tip]\n+        bmarks[head] = node\n+\n     print \"@refs/heads/%s HEAD\" % head\n     g_head = (head, 'bookmarks', node)\n \n-- \n1.8.0\n"},{"id":"202172","messageId":"1351571736-4682-12-git-send-email-felipe.contreras@gmail.com","threadId":"31976","inReplyTo":"1351571736-4682-1-git-send-email-felipe.contreras@gmail.com","subject":"[PATCH v5 11/14] remote-hg: add support for fake remote","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2012-10-30T04:35:33Z","receivedAt":"2012-10-30T04:35:33Z","isPatch":true,"sender":{"key":"felipe.contreras@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8358?v=4"},"body":"Helpful while testing.\n\nSigned-off-by: Felipe Contreras <felipe.contreras@gmail.com>\n---\n contrib/remote-hg/git-remote-hg | 8 +++++++-\n 1 file changed, 7 insertions(+), 1 deletion(-)\n\ndiff --git a/contrib/remote-hg/git-remote-hg b/contrib/remote-hg/git-remote-hg\nindex c41ec95..61c1072 100755\n--- a/contrib/remote-hg/git-remote-hg\n+++ b/contrib/remote-hg/git-remote-hg\n@@ -248,7 +248,13 @@ def get_repo(url, alias):\n     myui = ui.ui()\n     myui.setconfig('ui', 'interactive', 'off')\n \n-    if hg.islocal(url):\n+    if url.startswith(\"remote://\"):\n+        remote = True\n+        url = \"file://%s\" % url[9:]\n+    else:\n+        remote = False\n+\n+    if hg.islocal(url) and not remote:\n         repo = hg.repository(myui, url)\n     else:\n         local_path = os.path.join(dirname, 'clone')\n-- \n1.8.0\n"},{"id":"202174","messageId":"1351571736-4682-13-git-send-email-felipe.contreras@gmail.com","threadId":"31976","inReplyTo":"1351571736-4682-1-git-send-email-felipe.contreras@gmail.com","subject":"[PATCH v5 12/14] remote-hg: add biridectional tests","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2012-10-30T04:35:34Z","receivedAt":"2012-10-30T04:35:34Z","isPatch":true,"sender":{"key":"felipe.contreras@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8358?v=4"},"body":"Base commands from hg-git tests:\nhttps://bitbucket.org/durin42/hg-git/src\n\nSigned-off-by: Felipe Contreras <felipe.contreras@gmail.com>\n---\n contrib/remote-hg/Makefile |  13 +++\n contrib/remote-hg/test.sh  | 241 +++++++++++++++++++++++++++++++++++++++++++++\n 2 files changed, 254 insertions(+)\n create mode 100644 contrib/remote-hg/Makefile\n create mode 100755 contrib/remote-hg/test.sh\n\ndiff --git a/contrib/remote-hg/Makefile b/contrib/remote-hg/Makefile\nnew file mode 100644\nindex 0000000..9a76575\n--- /dev/null\n+++ b/contrib/remote-hg/Makefile\n@@ -0,0 +1,13 @@\n+TESTS := $(wildcard test*.sh)\n+\n+export T := $(addprefix $(CURDIR)/,$(TESTS))\n+export MAKE := $(MAKE) -e\n+export PATH := $(CURDIR):$(PATH)\n+\n+test:\n+\t$(MAKE) -C ../../t $@\n+\n+$(TESTS):\n+\t$(MAKE) -C ../../t $(CURDIR)/$@\n+\n+.PHONY: $(TESTS)\ndiff --git a/contrib/remote-hg/test.sh b/contrib/remote-hg/test.sh\nnew file mode 100755\nindex 0000000..4ea2b24\n--- /dev/null\n+++ b/contrib/remote-hg/test.sh\n@@ -0,0 +1,241 @@\n+#!/bin/sh\n+#\n+# Copyright (c) 2012 Felipe Contreras\n+#\n+# Base commands from hg-git tests:\n+# https://bitbucket.org/durin42/hg-git/src\n+#\n+\n+test_description='Test remote-hg'\n+\n+. ./test-lib.sh\n+\n+if ! test_have_prereq PYTHON; then\n+\tskip_all='skipping remote-hg tests; python not available'\n+\ttest_done\n+fi\n+\n+if ! \"$PYTHON_PATH\" -c 'import mercurial'; then\n+\tskip_all='skipping remote-hg tests; mercurial not available'\n+\ttest_done\n+fi\n+\n+# clone to a git repo\n+git_clone () {\n+\thg -R $1 bookmark -f -r tip master &&\n+\tgit clone -q \"hg::$PWD/$1\" $2\n+}\n+\n+# clone to an hg repo\n+hg_clone () {\n+\t(\n+\thg init $2 &&\n+\tcd $1 &&\n+\tgit push -q \"hg::$PWD/../$2\" 'refs/tags/*:refs/tags/*' 'refs/heads/*:refs/heads/*'\n+\t) &&\n+\n+\t(cd $2 && hg -q update)\n+}\n+\n+# push an hg repo\n+hg_push () {\n+\t(\n+\tcd $2\n+\told=$(git symbolic-ref --short HEAD)\n+\tgit checkout -q -b tmp &&\n+\tgit fetch -q \"hg::$PWD/../$1\" 'refs/tags/*:refs/tags/*' 'refs/heads/*:refs/heads/*' &&\n+\tgit checkout -q $old &&\n+\tgit branch -q -D tmp 2> /dev/null || true\n+\t)\n+}\n+\n+hg_log () {\n+\thg -R $1 log --graph --debug | grep -v 'tag: *default/'\n+}\n+\n+test_expect_success 'setup' '\n+\t(\n+\techo \"[ui]\"\n+\techo \"username = A U Thor <author@example.com>\"\n+\techo \"[defaults]\"\n+\techo \"backout = -d \\\"0 0\\\"\"\n+\techo \"commit = -d \\\"0 0\\\"\"\n+\techo \"debugrawcommit = -d \\\"0 0\\\"\"\n+\techo \"tag = -d \\\"0 0\\\"\"\n+\t) >> \"$HOME\"/.hgrc &&\n+\tgit config --global remote-hg.hg-git-compat true\n+\n+\texport HGEDITOR=/usr/bin/true\n+\n+\texport GIT_AUTHOR_DATE=\"2007-01-01 00:00:00 +0230\"\n+\texport GIT_COMMITTER_DATE=\"$GIT_AUTHOR_DATE\"\n+'\n+\n+test_expect_success 'encoding' '\n+\tmkdir -p tmp && cd tmp &&\n+\ttest_when_finished \"cd .. && rm -rf tmp\" &&\n+\n+\t(\n+\tgit init -q gitrepo &&\n+\tcd gitrepo &&\n+\n+\techo alpha > alpha &&\n+\tgit add alpha &&\n+\tgit commit -m \"add älphà\" &&\n+\n+\texport GIT_AUTHOR_NAME=\"tést èncödîng\" &&\n+\techo beta > beta &&\n+\tgit add beta &&\n+\tgit commit -m \"add beta\" &&\n+\n+\techo gamma > gamma &&\n+\tgit add gamma &&\n+\tgit commit -m \"add gämmâ\" &&\n+\n+\t: TODO git config i18n.commitencoding latin-1 &&\n+\techo delta > delta &&\n+\tgit add delta &&\n+\tgit commit -m \"add déltà\"\n+\t) &&\n+\n+\thg_clone gitrepo hgrepo &&\n+\tgit_clone hgrepo gitrepo2 &&\n+\thg_clone gitrepo2 hgrepo2 &&\n+\n+\tHGENCODING=utf-8 hg_log hgrepo > expected &&\n+\tHGENCODING=utf-8 hg_log hgrepo2 > actual &&\n+\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'file removal' '\n+\tmkdir -p tmp && cd tmp &&\n+\ttest_when_finished \"cd .. && rm -rf tmp\" &&\n+\n+\t(\n+\tgit init -q gitrepo &&\n+\tcd gitrepo &&\n+\techo alpha > alpha &&\n+\tgit add alpha &&\n+\tgit commit -m \"add alpha\" &&\n+\techo beta > beta &&\n+\tgit add beta &&\n+\tgit commit -m \"add beta\"\n+\tmkdir foo &&\n+\techo blah > foo/bar &&\n+\tgit add foo &&\n+\tgit commit -m \"add foo\" &&\n+\tgit rm alpha &&\n+\tgit commit -m \"remove alpha\" &&\n+\tgit rm foo/bar &&\n+\tgit commit -m \"remove foo/bar\"\n+\t) &&\n+\n+\thg_clone gitrepo hgrepo &&\n+\tgit_clone hgrepo gitrepo2 &&\n+\thg_clone gitrepo2 hgrepo2 &&\n+\n+\thg_log hgrepo > expected &&\n+\thg_log hgrepo2 > actual &&\n+\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'git tags' '\n+\tmkdir -p tmp && cd tmp &&\n+\ttest_when_finished \"cd .. && rm -rf tmp\" &&\n+\n+\t(\n+\tgit init -q gitrepo &&\n+\tcd gitrepo &&\n+\tgit config receive.denyCurrentBranch ignore &&\n+\techo alpha > alpha &&\n+\tgit add alpha &&\n+\tgit commit -m \"add alpha\" &&\n+\tgit tag alpha &&\n+\n+\techo beta > beta &&\n+\tgit add beta &&\n+\tgit commit -m \"add beta\" &&\n+\tgit tag -a -m \"added tag beta\" beta\n+\t) &&\n+\n+\thg_clone gitrepo hgrepo &&\n+\tgit_clone hgrepo gitrepo2 &&\n+\thg_clone gitrepo2 hgrepo2 &&\n+\n+\thg_log hgrepo > expected &&\n+\thg_log hgrepo2 > actual &&\n+\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'hg branch' '\n+\tmkdir -p tmp && cd tmp &&\n+\ttest_when_finished \"cd .. && rm -rf tmp\" &&\n+\n+\t(\n+\tgit init -q gitrepo &&\n+\tcd gitrepo &&\n+\n+\techo alpha > alpha &&\n+\tgit add alpha &&\n+\tgit commit -q -m \"add alpha\" &&\n+\tgit checkout -q -b not-master\n+\t) &&\n+\n+\t(\n+\thg_clone gitrepo hgrepo &&\n+\n+\tcd hgrepo &&\n+\thg -q co master &&\n+\thg mv alpha beta &&\n+\thg -q commit -m \"rename alpha to beta\" &&\n+\thg branch gamma | grep -v \"permanent and global\" &&\n+\thg -q commit -m \"started branch gamma\"\n+\t) &&\n+\n+\thg_push hgrepo gitrepo &&\n+\thg_clone gitrepo hgrepo2 &&\n+\n+\t: TODO, avoid \"master\" bookmark &&\n+\t(cd hgrepo2 && hg checkout gamma) &&\n+\n+\thg_log hgrepo > expected &&\n+\thg_log hgrepo2 > actual &&\n+\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'hg tags' '\n+\tmkdir -p tmp && cd tmp &&\n+\ttest_when_finished \"cd .. && rm -rf tmp\" &&\n+\n+\t(\n+\tgit init -q gitrepo &&\n+\tcd gitrepo &&\n+\n+\techo alpha > alpha &&\n+\tgit add alpha &&\n+\tgit commit -m \"add alpha\" &&\n+\tgit checkout -q -b not-master\n+\t) &&\n+\n+\t(\n+\thg_clone gitrepo hgrepo &&\n+\n+\tcd hgrepo &&\n+\thg co master &&\n+\thg tag alpha\n+\t) &&\n+\n+\thg_push hgrepo gitrepo &&\n+\thg_clone gitrepo hgrepo2 &&\n+\n+\thg_log hgrepo > expected &&\n+\thg_log hgrepo2 > actual &&\n+\n+\ttest_cmp expected actual\n+'\n+\n+test_done\n-- \n1.8.0\n"},{"id":"202176","messageId":"1351571736-4682-14-git-send-email-felipe.contreras@gmail.com","threadId":"31976","inReplyTo":"1351571736-4682-1-git-send-email-felipe.contreras@gmail.com","subject":"[PATCH v5 13/14] remote-hg: add tests to compare with hg-git","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2012-10-30T04:35:35Z","receivedAt":"2012-10-30T04:35:35Z","isPatch":true,"sender":{"key":"felipe.contreras@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8358?v=4"},"body":"The base commands come from the tests of the hg-git project.\n\nSigned-off-by: Felipe Contreras <felipe.contreras@gmail.com>\n---\n contrib/remote-hg/test-hg-git.sh | 460 +++++++++++++++++++++++++++++++++++++++\n 1 file changed, 460 insertions(+)\n create mode 100755 contrib/remote-hg/test-hg-git.sh\n\ndiff --git a/contrib/remote-hg/test-hg-git.sh b/contrib/remote-hg/test-hg-git.sh\nnew file mode 100755\nindex 0000000..2b7acb0\n--- /dev/null\n+++ b/contrib/remote-hg/test-hg-git.sh\n@@ -0,0 +1,460 @@\n+#!/bin/sh\n+#\n+# Copyright (c) 2012 Felipe Contreras\n+#\n+# Base commands from hg-git tests:\n+# https://bitbucket.org/durin42/hg-git/src\n+#\n+\n+test_description='Test remote-hg output compared to hg-git'\n+\n+. ./test-lib.sh\n+\n+if ! test_have_prereq PYTHON; then\n+\tskip_all='skipping remote-hg tests; python not available'\n+\ttest_done\n+fi\n+\n+if ! \"$PYTHON_PATH\" -c 'import mercurial'; then\n+\tskip_all='skipping remote-hg tests; mercurial not available'\n+\ttest_done\n+fi\n+\n+if ! \"$PYTHON_PATH\" -c 'import hggit'; then\n+\tskip_all='skipping remote-hg tests; hg-git not available'\n+\ttest_done\n+fi\n+\n+# clone to a git repo with git\n+git_clone_git () {\n+\thg -R $1 bookmark -f -r tip master &&\n+\tgit clone -q \"hg::$PWD/$1\" $2\n+}\n+\n+# clone to an hg repo with git\n+hg_clone_git () {\n+\t(\n+\thg init $2 &&\n+\tcd $1 &&\n+\tgit push -q \"hg::$PWD/../$2\" 'refs/tags/*:refs/tags/*' 'refs/heads/*:refs/heads/*'\n+\t) &&\n+\n+\t(cd $2 && hg -q update)\n+}\n+\n+# clone to a git repo with hg\n+git_clone_hg () {\n+\t(\n+\tgit init -q $2 &&\n+\tcd $1 &&\n+\thg bookmark -f -r tip master &&\n+\thg -q push -r master ../$2 || true\n+\t)\n+}\n+\n+# clone to an hg repo with hg\n+hg_clone_hg () {\n+\thg -q clone $1 $2\n+}\n+\n+# push an hg repo with git\n+hg_push_git () {\n+\t(\n+\tcd $2\n+\told=$(git symbolic-ref --short HEAD)\n+\tgit checkout -q -b tmp &&\n+\tgit fetch -q \"hg::$PWD/../$1\" 'refs/tags/*:refs/tags/*' 'refs/heads/*:refs/heads/*' &&\n+\tgit checkout -q $old &&\n+\tgit branch -q -D tmp 2> /dev/null || true\n+\t)\n+}\n+\n+# push an hg git repo with hg\n+hg_push_hg () {\n+\t(\n+\tcd $1 &&\n+\thg -q push ../$2 || true\n+\t)\n+}\n+\n+hg_log () {\n+\thg -R $1 log --graph --debug | grep -v 'tag: *default/'\n+}\n+\n+git_log () {\n+\tgit --git-dir=$1/.git fast-export --branches\n+}\n+\n+test_expect_success 'setup' '\n+\t(\n+\techo \"[ui]\"\n+\techo \"username = A U Thor <author@example.com>\"\n+\techo \"[defaults]\"\n+\techo \"backout = -d \\\"0 0\\\"\"\n+\techo \"commit = -d \\\"0 0\\\"\"\n+\techo \"debugrawcommit = -d \\\"0 0\\\"\"\n+\techo \"tag = -d \\\"0 0\\\"\"\n+\techo \"[extensions]\"\n+\techo \"hgext.bookmarks =\"\n+\techo \"hggit =\"\n+\t) >> \"$HOME\"/.hgrc &&\n+\tgit config --global receive.denycurrentbranch warn\n+\tgit config --global remote-hg.hg-git-compat true\n+\n+\texport HGEDITOR=/usr/bin/true\n+\n+\texport GIT_AUTHOR_DATE=\"2007-01-01 00:00:00 +0230\"\n+\texport GIT_COMMITTER_DATE=\"$GIT_AUTHOR_DATE\"\n+'\n+\n+test_expect_success 'merge conflict 1' '\n+\tmkdir -p tmp && cd tmp &&\n+\ttest_when_finished \"cd .. && rm -rf tmp\" &&\n+\n+\t(\n+\thg init hgrepo1 &&\n+\tcd hgrepo1 &&\n+\techo A > afile &&\n+\thg add afile &&\n+\thg ci -m \"origin\" &&\n+\n+\techo B > afile &&\n+\thg ci -m \"A->B\" &&\n+\n+\thg up -r0 &&\n+\techo C > afile &&\n+\thg ci -m \"A->C\" &&\n+\n+\thg merge -r1 || true &&\n+\techo C > afile &&\n+\thg resolve -m afile &&\n+\thg ci -m \"merge to C\"\n+\t) &&\n+\n+\tfor x in hg git; do\n+\t\tgit_clone_$x hgrepo1 gitrepo-$x &&\n+\t\thg_clone_$x gitrepo-$x hgrepo2-$x &&\n+\t\thg_log hgrepo2-$x > hg-log-$x &&\n+\t\tgit_log gitrepo-$x > git-log-$x\n+\tdone &&\n+\n+\ttest_cmp hg-log-hg hg-log-git &&\n+\ttest_cmp git-log-hg git-log-git\n+'\n+\n+test_expect_success 'merge conflict 2' '\n+\tmkdir -p tmp && cd tmp &&\n+\ttest_when_finished \"cd .. && rm -rf tmp\" &&\n+\n+\t(\n+\thg init hgrepo1 &&\n+\tcd hgrepo1 &&\n+\techo A > afile &&\n+\thg add afile &&\n+\thg ci -m \"origin\" &&\n+\n+\techo B > afile &&\n+\thg ci -m \"A->B\" &&\n+\n+\thg up -r0 &&\n+\techo C > afile &&\n+\thg ci -m \"A->C\" &&\n+\n+\thg merge -r1 || true &&\n+\techo B > afile &&\n+\thg resolve -m afile &&\n+\thg ci -m \"merge to B\"\n+\t) &&\n+\n+\tfor x in hg git; do\n+\t\tgit_clone_$x hgrepo1 gitrepo-$x &&\n+\t\thg_clone_$x gitrepo-$x hgrepo2-$x &&\n+\t\thg_log hgrepo2-$x > hg-log-$x &&\n+\t\tgit_log gitrepo-$x > git-log-$x\n+\tdone &&\n+\n+\ttest_cmp hg-log-hg hg-log-git &&\n+\ttest_cmp git-log-hg git-log-git\n+'\n+\n+test_expect_success 'converged merge' '\n+\tmkdir -p tmp && cd tmp &&\n+\ttest_when_finished \"cd .. && rm -rf tmp\" &&\n+\n+\t(\n+\thg init hgrepo1 &&\n+\tcd hgrepo1 &&\n+\techo A > afile &&\n+\thg add afile &&\n+\thg ci -m \"origin\" &&\n+\n+\techo B > afile &&\n+\thg ci -m \"A->B\" &&\n+\n+\techo C > afile &&\n+\thg ci -m \"B->C\" &&\n+\n+\thg up -r0 &&\n+\techo C > afile &&\n+\thg ci -m \"A->C\" &&\n+\n+\thg merge -r2 || true &&\n+\thg ci -m \"merge\"\n+\t) &&\n+\n+\tfor x in hg git; do\n+\t\tgit_clone_$x hgrepo1 gitrepo-$x &&\n+\t\thg_clone_$x gitrepo-$x hgrepo2-$x &&\n+\t\thg_log hgrepo2-$x > hg-log-$x &&\n+\t\tgit_log gitrepo-$x > git-log-$x\n+\tdone &&\n+\n+\ttest_cmp hg-log-hg hg-log-git &&\n+\ttest_cmp git-log-hg git-log-git\n+'\n+\n+test_expect_success 'encoding' '\n+\tmkdir -p tmp && cd tmp &&\n+\ttest_when_finished \"cd .. && rm -rf tmp\" &&\n+\n+\t(\n+\tgit init -q gitrepo &&\n+\tcd gitrepo &&\n+\n+\techo alpha > alpha &&\n+\tgit add alpha &&\n+\tgit commit -m \"add älphà\" &&\n+\n+\texport GIT_AUTHOR_NAME=\"tést èncödîng\" &&\n+\techo beta > beta &&\n+\tgit add beta &&\n+\tgit commit -m \"add beta\" &&\n+\n+\techo gamma > gamma &&\n+\tgit add gamma &&\n+\tgit commit -m \"add gämmâ\" &&\n+\n+\t: TODO git config i18n.commitencoding latin-1 &&\n+\techo delta > delta &&\n+\tgit add delta &&\n+\tgit commit -m \"add déltà\"\n+\t) &&\n+\n+\tfor x in hg git; do\n+\t\thg_clone_$x gitrepo hgrepo-$x &&\n+\t\tgit_clone_$x hgrepo-$x gitrepo2-$x &&\n+\n+\t\tHGENCODING=utf-8 hg_log hgrepo-$x > hg-log-$x &&\n+\t\tgit_log gitrepo2-$x > git-log-$x\n+\tdone &&\n+\n+\ttest_cmp hg-log-hg hg-log-git &&\n+\ttest_cmp git-log-hg git-log-git\n+'\n+\n+test_expect_success 'file removal' '\n+\tmkdir -p tmp && cd tmp &&\n+\ttest_when_finished \"cd .. && rm -rf tmp\" &&\n+\n+\t(\n+\tgit init -q gitrepo &&\n+\tcd gitrepo &&\n+\techo alpha > alpha &&\n+\tgit add alpha &&\n+\tgit commit -m \"add alpha\" &&\n+\techo beta > beta &&\n+\tgit add beta &&\n+\tgit commit -m \"add beta\"\n+\tmkdir foo &&\n+\techo blah > foo/bar &&\n+\tgit add foo &&\n+\tgit commit -m \"add foo\" &&\n+\tgit rm alpha &&\n+\tgit commit -m \"remove alpha\" &&\n+\tgit rm foo/bar &&\n+\tgit commit -m \"remove foo/bar\"\n+\t) &&\n+\n+\tfor x in hg git; do\n+\t\t(\n+\t\thg_clone_$x gitrepo hgrepo-$x &&\n+\t\tcd hgrepo-$x &&\n+\t\thg_log . &&\n+\t\thg manifest -r 3 &&\n+\t\thg manifest\n+\t\t) > output-$x &&\n+\n+\t\tgit_clone_$x hgrepo-$x gitrepo2-$x &&\n+\t\tgit_log gitrepo2-$x > log-$x\n+\tdone &&\n+\n+\ttest_cmp output-hg output-git &&\n+\ttest_cmp log-hg log-git\n+'\n+\n+test_expect_success 'git tags' '\n+\tmkdir -p tmp && cd tmp &&\n+\ttest_when_finished \"cd .. && rm -rf tmp\" &&\n+\n+\t(\n+\tgit init -q gitrepo &&\n+\tcd gitrepo &&\n+\tgit config receive.denyCurrentBranch ignore &&\n+\techo alpha > alpha &&\n+\tgit add alpha &&\n+\tgit commit -m \"add alpha\" &&\n+\tgit tag alpha &&\n+\n+\techo beta > beta &&\n+\tgit add beta &&\n+\tgit commit -m \"add beta\" &&\n+\tgit tag -a -m \"added tag beta\" beta\n+\t) &&\n+\n+\tfor x in hg git; do\n+\t\thg_clone_$x gitrepo hgrepo-$x &&\n+\t\thg_log hgrepo-$x > log-$x\n+\tdone &&\n+\n+\ttest_cmp log-hg log-git\n+'\n+\n+test_expect_success 'hg author' '\n+\tmkdir -p tmp && cd tmp &&\n+\ttest_when_finished \"cd .. && rm -rf tmp\" &&\n+\n+\tfor x in hg git; do\n+\t\t(\n+\t\tgit init -q gitrepo-$x &&\n+\t\tcd gitrepo-$x &&\n+\n+\t\techo alpha > alpha &&\n+\t\tgit add alpha &&\n+\t\tgit commit -m \"add alpha\" &&\n+\t\tgit checkout -q -b not-master\n+\t\t) &&\n+\n+\t\t(\n+\t\thg_clone_$x gitrepo-$x hgrepo-$x &&\n+\t\tcd hgrepo-$x &&\n+\n+\t\thg co master &&\n+\t\techo beta > beta &&\n+\t\thg add beta &&\n+\t\thg commit -u \"test\" -m \"add beta\" &&\n+\n+\t\techo gamma >> beta &&\n+\t\thg commit -u \"test <test@example.com> (comment)\" -m \"modify beta\" &&\n+\n+\t\techo gamma > gamma &&\n+\t\thg add gamma &&\n+\t\thg commit -u \"<test@example.com>\" -m \"add gamma\" &&\n+\n+\t\techo delta > delta &&\n+\t\thg add delta &&\n+\t\thg commit -u \"name<test@example.com>\" -m \"add delta\" &&\n+\n+\t\techo epsilon > epsilon &&\n+\t\thg add epsilon &&\n+\t\thg commit -u \"name <test@example.com\" -m \"add epsilon\" &&\n+\n+\t\techo zeta > zeta &&\n+\t\thg add zeta &&\n+\t\thg commit -u \" test \" -m \"add zeta\" &&\n+\n+\t\techo eta > eta &&\n+\t\thg add eta &&\n+\t\thg commit -u \"test < test@example.com >\" -m \"add eta\" &&\n+\n+\t\techo theta > theta &&\n+\t\thg add theta &&\n+\t\thg commit -u \"test >test@example.com>\" -m \"add theta\"\n+\t\t) &&\n+\n+\t\thg_push_$x hgrepo-$x gitrepo-$x &&\n+\t\thg_clone_$x gitrepo-$x hgrepo2-$x &&\n+\n+\t\thg_log hgrepo2-$x > hg-log-$x &&\n+\t\tgit_log gitrepo-$x > git-log-$x\n+\tdone &&\n+\n+\ttest_cmp git-log-hg git-log-git &&\n+\n+\ttest_cmp hg-log-hg hg-log-git &&\n+\ttest_cmp git-log-hg git-log-git\n+'\n+\n+test_expect_success 'hg branch' '\n+\tmkdir -p tmp && cd tmp &&\n+\ttest_when_finished \"cd .. && rm -rf tmp\" &&\n+\n+\tfor x in hg git; do\n+\t\t(\n+\t\tgit init -q gitrepo-$x &&\n+\t\tcd gitrepo-$x &&\n+\n+\t\techo alpha > alpha &&\n+\t\tgit add alpha &&\n+\t\tgit commit -q -m \"add alpha\" &&\n+\t\tgit checkout -q -b not-master\n+\t\t) &&\n+\n+\t\t(\n+\t\thg_clone_$x gitrepo-$x hgrepo-$x &&\n+\n+\t\tcd hgrepo-$x &&\n+\t\thg -q co master &&\n+\t\thg mv alpha beta &&\n+\t\thg -q commit -m \"rename alpha to beta\" &&\n+\t\thg branch gamma | grep -v \"permanent and global\" &&\n+\t\thg -q commit -m \"started branch gamma\"\n+\t\t) &&\n+\n+\t\thg_push_$x hgrepo-$x gitrepo-$x &&\n+\t\thg_clone_$x gitrepo-$x hgrepo2-$x &&\n+\n+\t\thg_log hgrepo2-$x > hg-log-$x &&\n+\t\tgit_log gitrepo-$x > git-log-$x\n+\tdone &&\n+\n+\ttest_cmp hg-log-hg hg-log-git &&\n+\ttest_cmp git-log-hg git-log-git\n+'\n+\n+test_expect_success 'hg tags' '\n+\tmkdir -p tmp && cd tmp &&\n+\ttest_when_finished \"cd .. && rm -rf tmp\" &&\n+\n+\tfor x in hg git; do\n+\t\t(\n+\t\tgit init -q gitrepo-$x &&\n+\t\tcd gitrepo-$x &&\n+\n+\t\techo alpha > alpha &&\n+\t\tgit add alpha &&\n+\t\tgit commit -m \"add alpha\" &&\n+\t\tgit checkout -q -b not-master\n+\t\t) &&\n+\n+\t\t(\n+\t\thg_clone_$x gitrepo-$x hgrepo-$x &&\n+\n+\t\tcd hgrepo-$x &&\n+\t\thg co master &&\n+\t\thg tag alpha\n+\t\t) &&\n+\n+\t\thg_push_$x hgrepo-$x gitrepo-$x &&\n+\t\thg_clone_$x gitrepo-$x hgrepo2-$x &&\n+\n+\t\t(\n+\t\tgit --git-dir=gitrepo-$x/.git tag -l &&\n+\t\thg_log hgrepo2-$x &&\n+\t\tcat hgrepo2-$x/.hgtags\n+\t\t) > output-$x\n+\tdone &&\n+\n+\ttest_cmp output-hg output-git\n+'\n+\n+test_done\n-- \n1.8.0\n"},{"id":"202175","messageId":"1351571736-4682-15-git-send-email-felipe.contreras@gmail.com","threadId":"31976","inReplyTo":"1351571736-4682-1-git-send-email-felipe.contreras@gmail.com","subject":"[PATCH v5 14/14] remote-hg: add extra author test","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2012-10-30T04:35:36Z","receivedAt":"2012-10-30T04:35:36Z","isPatch":true,"sender":{"key":"felipe.contreras@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8358?v=4"},"body":"For hg.hg.\n\nSigned-off-by: Felipe Contreras <felipe.contreras@gmail.com>\n---\n contrib/remote-hg/test-hg-git.sh | 6 +++++-\n 1 file changed, 5 insertions(+), 1 deletion(-)\n\ndiff --git a/contrib/remote-hg/test-hg-git.sh b/contrib/remote-hg/test-hg-git.sh\nindex 2b7acb0..a9f5cb2 100755\n--- a/contrib/remote-hg/test-hg-git.sh\n+++ b/contrib/remote-hg/test-hg-git.sh\n@@ -368,7 +368,11 @@ test_expect_success 'hg author' '\n \n \t\techo theta > theta &&\n \t\thg add theta &&\n-\t\thg commit -u \"test >test@example.com>\" -m \"add theta\"\n+\t\thg commit -u \"test >test@example.com>\" -m \"add theta\" &&\n+\n+\t\techo iota > iota &&\n+\t\thg add iota &&\n+\t\thg commit -u \"test <test <at> example <dot> com>\" -m \"add iota\"\n \t\t) &&\n \n \t\thg_push_$x hgrepo-$x gitrepo-$x &&\n-- \n1.8.0\n"},{"id":"202179","messageId":"CAMP44s27Sw1ohCRkvqBwWgfs5A4THiy1-c1CDjXjVbLUp2_Q0A@mail.gmail.com","threadId":"31976","inReplyTo":"CAPc5daUuCsiQd4MoQzQm_aQ6c88b_E8vYfA5btXMW4yCBX8E=g@mail.gmail.com","subject":"Re: [PATCH v5 12/14] remote-hg: add biridectional tests","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2012-10-30T04:49:19Z","receivedAt":"2012-10-30T04:49:19Z","isPatch":true,"sender":{"key":"felipe.contreras@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8358?v=4"},"body":"On Tue, Oct 30, 2012 at 5:47 AM, Junio C Hamano <gitster@pobox.com> wrote:\n> What's the copyright status of the part this borrows from? Is there an\n> in-file copyright notice needed to *name* the original author?\n>\n> The set-up part may become easier to read if done with here document.\n>\n> Pardon terseness, typo and HTML from a tablet.\n\nI'm the original author. Some chunks are borrowed from the hg-git\nproject, but they had no copyright, I'll contact them and ask.\n\nCheers.\n\n-- \nFelipe Contreras\n"},{"id":"202190","messageId":"20121030102526.GN4891@arachsys.com","threadId":"31976","inReplyTo":"1351571736-4682-1-git-send-email-felipe.contreras@gmail.com","subject":"Re: [PATCH v5 00/14] New remote-hg helper","fromName":"Chris Webb","fromEmail":"chris@arachsys.com","sentAt":"2012-10-30T10:25:27Z","receivedAt":"2012-10-30T10:25:27Z","isPatch":true,"sender":{"key":"chris@arachsys.com","avatar":"https://avatars.githubusercontent.com/u/299056?v=4"},"body":"Hi. I routinely work with projects in both hg and git, so I'm really\ninterested in this. Thanks for working on it! I grabbed the latest version\nfrom\n\n  https://github.com/felipec/git/blob/fc-remote-hg/contrib/remote-hg/git-remote-hg\n\nand have been trying it out. For the most part, it seems to work very nicely\nfor the hg repos I have access to and can test against. I've spotted a couple\nof issues along the way that I thought would be worth reporting.\n\nThe first is really a symptom of a general difference between hg and git: an hg\nrepository can have multiple heads, whereas a git repo has exactly one head. To\ndemonstrate:\n\n  $ hg init hgtest && cd hgtest\n  $ echo zero >foo && hg add foo && hg commit -m zero\n  $ echo one >foo && hg commit -m one\n  $ hg checkout -r 0\n  1 files updated, 0 files merged, 0 files removed, 0 files unresolved\n  $ echo two >foo && hg commit -m two\n  created new head\n  $ hg log --graph\n  @  changeset:   2:ca09651009cb\n  |  tag:         tip\n  |  parent:      0:9f552c53d116\n  |  user:        Chris Webb <chris@arachsys.com>\n  |  date:        Tue Oct 30 09:33:38 2012 +0000\n  |  summary:     two\n  |\n  | o  changeset:   1:58fad8998339\n  |/   user:        Chris Webb <chris@arachsys.com>\n  |    date:        Tue Oct 30 09:33:25 2012 +0000\n  |    summary:     one\n  |\n  o  changeset:   0:9f552c53d116\n     user:        Chris Webb <chris@arachsys.com>\n     date:        Tue Oct 30 09:33:08 2012 +0000\n     summary:     zero\n\n  $ cd ..\n\nNow if I try to convert this:\n\n  $ git clone hg::$PWD/hgtest gittest\n  Cloning into 'gittest'...\n  WARNING: Branch 'default' has more than one head, consider merging\n  Traceback (most recent call last):\n    File \"/home/chris/bin/git-remote-hg\", line 773, in <module>\n      sys.exit(main(sys.argv))\n    File \"/home/chris/bin/git-remote-hg\", line 759, in main\n      do_list(parser)\n    File \"/home/chris/bin/git-remote-hg\", line 463, in do_list\n      list_branch_head(repo, cur)\n    File \"/home/chris/bin/git-remote-hg\", line 425, in list_branch_head\n      tip = get_branch_tip(repo, cur)\n    File \"/home/chris/bin/git-remote-hg\", line 418, in get_branch_tip\n      return repo.branchtip(branch)\n  AttributeError: 'mqrepo' object has no attribute 'branchtip'\n\nStrip the second head and it's fine:\n\n  $ hg -R hgtest strip 2\n  1 files updated, 0 files merged, 0 files removed, 0 files unresolved\n  saved backup bundle to /tmp/hgtest/hgtest/.hg/strip-backup/ca09651009cb-backup.hg\n  $ git clone hg::$PWD/hgtest gittest\n  Cloning into 'gittest'...\n  $\n\nNot sure what the most friendly thing to do here is. Perhaps refuse to\nclone/pull from a repo with multiple heads unless you name the specific head\nyou want?\n\n\nThe second thing I spotted is the behaviour of bookmarks on push:\n\n  $ hg init hgtest && cd hgtest\n  $ echo zero >foo && hg add foo && hg commit -m zero\n  $ hg bookmark development\n  $ cd ..\n  $ git clone hg::$PWD/hgtest gittest && cd gittest\n  Cloning into 'gittest'...\n  $ git checkout development\n  Branch development set up to track remote branch development from origin.\n  Switched to a new branch 'development'\n  $ echo one >foo && git add foo && git commit -m one\n  [development 9f67dc4] one\n   1 file changed, 1 insertion(+), 1 deletion(-)\n  $ git status\n  # On branch development\n  # Your branch is ahead of 'origin/development' by 1 commit.\n  #\n  nothing to commit\n  $ git push\n  warning: helper reported unexpected status of refs/hg/origin/bookmarks/development\n  To hg::/tmp/hgtest/hgtest\n   * [new branch]      branches/default -> branches/default\n   * [new branch]      development -> development\n  $ hg log -R ../hgtest\n  changeset:   1:1c0714d93864\n  tag:         tip\n  user:        Chris Webb <chris@arachsys.com>\n  date:        Tue Oct 30 09:51:51 2012 +0000\n  summary:     one\n\n  changeset:   0:f56c463398ea\n  bookmark:    development\n  user:        Chris Webb <chris@arachsys.com>\n  date:        Tue Oct 30 09:50:53 2012 +0000\n  summary:     zero\n\ni.e. the development bookmark hasn't been updated by the push. This might be\nconnected to the warning message\n\n  warning: helper reported unexpected status of refs/hg/origin/bookmarks/development\n\nI'm testing with hg 2.2.2 and current git master, so I expect this could be a\npython api change in the more recent versions of hg if you don't see the same\nbehaviour.\n\nBest wishes,\n\nChris.\n"},{"id":"202191","messageId":"20121030102827.GO4891@arachsys.com","threadId":"31976","inReplyTo":"20121030102526.GN4891@arachsys.com","subject":"Re: [PATCH v5 00/14] New remote-hg helper","fromName":"Chris Webb","fromEmail":"chris@arachsys.com","sentAt":"2012-10-30T10:28:27Z","receivedAt":"2012-10-30T10:28:27Z","isPatch":true,"sender":{"key":"chris@arachsys.com","avatar":"https://avatars.githubusercontent.com/u/299056?v=4"},"body":"Chris Webb <chris@arachsys.com> writes:\n\n> The first is really a symptom of a general difference between hg and git: an hg\n> repository can have multiple heads, whereas a git repo has exactly one head.\n\nBy this I mean an hg repository without bookmarks or branches can still have\nmultiple heads, whereas a git branch points at exactly one place. Sorry,\nvery vague description there!\n\nCheers,\n\nChris.\n"},{"id":"202204","messageId":"CAMP44s1g8rFGP7UOcvp9BEZ1oiSh3+-gYheciqO8Fmghipot8A@mail.gmail.com","threadId":"31976","inReplyTo":"20121030102526.GN4891@arachsys.com","subject":"Re: [PATCH v5 00/14] New remote-hg helper","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2012-10-30T15:51:50Z","receivedAt":"2012-10-30T15:51:50Z","isPatch":true,"sender":{"key":"felipe.contreras@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8358?v=4"},"body":"On Tue, Oct 30, 2012 at 11:25 AM, Chris Webb <chris@arachsys.com> wrote:\n> Hi. I routinely work with projects in both hg and git, so I'm really\n> interested in this. Thanks for working on it! I grabbed the latest version\n> from\n>\n>   https://github.com/felipec/git/blob/fc-remote-hg/contrib/remote-hg/git-remote-hg\n>\n> and have been trying it out. For the most part, it seems to work very nicely\n> for the hg repos I have access to and can test against. I've spotted a couple\n> of issues along the way that I thought would be worth reporting.\n\nGreat!\n\n> The first is really a symptom of a general difference between hg and git: an hg\n> repository can have multiple heads, whereas a git repo has exactly one head. To\n> demonstrate:\n\n> Now if I try to convert this:\n>\n>   $ git clone hg::$PWD/hgtest gittest\n>   Cloning into 'gittest'...\n>   WARNING: Branch 'default' has more than one head, consider merging\n>   Traceback (most recent call last):\n>     File \"/home/chris/bin/git-remote-hg\", line 773, in <module>\n>       sys.exit(main(sys.argv))\n>     File \"/home/chris/bin/git-remote-hg\", line 759, in main\n>       do_list(parser)\n>     File \"/home/chris/bin/git-remote-hg\", line 463, in do_list\n>       list_branch_head(repo, cur)\n>     File \"/home/chris/bin/git-remote-hg\", line 425, in list_branch_head\n>       tip = get_branch_tip(repo, cur)\n>     File \"/home/chris/bin/git-remote-hg\", line 418, in get_branch_tip\n>       return repo.branchtip(branch)\n>   AttributeError: 'mqrepo' object has no attribute 'branchtip'\n\nYes, it seems this is an API issue; repo.branchtip doesn't exist in\npython 2.2. I've added a check for that, and it should work fine now.\nWe'll be picking a random head (the first one), but the user has been\nwarned anyway.\n\n> The second thing I spotted is the behaviour of bookmarks on push:\n\n> i.e. the development bookmark hasn't been updated by the push. This might be\n> connected to the warning message\n\nThis is not an API issue, this is a bug; bookmarks are not updated\n(only the first creation works). I've fixed this as well, and added a\ntest with the example you put above:\n\nhttp://github.com/felipec/git/commit/d006d54fc444484707dffa24d9fad053e574918d\n\nBoth issues should be fixed now :)\n\nCheers.\n\n-- \nFelipe Contreras\n"},{"id":"202210","messageId":"alpine.DEB.1.00.1210301826220.7256@s15462909.onlinehome-server.info","threadId":"31976","inReplyTo":"20121030102526.GN4891@arachsys.com","subject":"Re: [PATCH v5 00/14] New remote-hg helper","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2012-10-30T17:27:57Z","receivedAt":"2012-10-30T17:27:57Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Chris,\n\nOn Tue, 30 Oct 2012, Chris Webb wrote:\n\n> I routinely work with projects in both hg and git, so I'm really\n> interested in this. Thanks for working on it! I grabbed the latest\n> version from\n> \n>   https://github.com/felipec/git/blob/fc-remote-hg/contrib/remote-hg/git-remote-hg\n> \n> and have been trying it out.\n\nYou might also want to try out the 'devel' branch of\nhttps://github.com/msysgit/git. It is in production use here.\n\nCiao,\nJohannes\n"},{"id":"202211","messageId":"20121030180021.GX26850@arachsys.com","threadId":"31976","inReplyTo":"CAMP44s1g8rFGP7UOcvp9BEZ1oiSh3+-gYheciqO8Fmghipot8A@mail.gmail.com","subject":"Re: [PATCH v5 00/14] New remote-hg helper","fromName":"Chris Webb","fromEmail":"chris@arachsys.com","sentAt":"2012-10-30T18:00:22Z","receivedAt":"2012-10-30T18:00:22Z","isPatch":true,"sender":{"key":"chris@arachsys.com","avatar":"https://avatars.githubusercontent.com/u/299056?v=4"},"body":"Felipe Contreras <felipe.contreras@gmail.com> writes:\n\n> Yes, it seems this is an API issue; repo.branchtip doesn't exist in\n> python 2.2.\n\nHi. Presumably this is a problem with old mercurial not a problem with old\npython as mentioned in the commit?\n\n> Both issues should be fixed now :)\n\nThey are indeed, and it now works nicely on all the repos I've tested it\nwith, including http://selenic.com/hg: very impressive!\n\nI wonder whether it's worth ignoring heads with bookmarks pointing to them\nwhen it comes to considering heads of branches, or at least allowing the\nhg branch tracking to be easily disabled?\n\nA common idiom when working with hg bookmarks is to completely ignore the\n(not very useful) hg branches (i.e. all commits are on the default hg\nbranch) and have a bookmark for each line of development used exactly as a\ngit branch would be.\n\nOn such a repository, at the moment you will always get a warning about\nmultiple heads on branches/default, even though you actually don't care\nabout branches/default (and would prefer it not to exist) and just want the\nbranches coming from the bookmarks.\n\nI've also seen repositories with no hg branches, but with a single\nunbookmarked tip and bookmarks on some other heads to mark non-mainline\ndevelopment. It would be nice for branches/default to track the unbookmarked\ntip in this case, without warning about the other, bookmarked heads.\n\nFinally, on a simple repo with no branches and where there's no clash with a\nbookmark called master, I'd love to be able to a get a more idiomatic\norigin/master rather than origin/branches/default.\n\nJust some idle thoughts...\n\nBest wishes,\n\nChris.\n"},{"id":"202217","messageId":"20121030181604.GY26850@arachsys.com","threadId":"31976","inReplyTo":"20121030180021.GX26850@arachsys.com","subject":"Re: [PATCH v5 00/14] New remote-hg helper","fromName":"Chris Webb","fromEmail":"chris@arachsys.com","sentAt":"2012-10-30T18:16:05Z","receivedAt":"2012-10-30T18:16:05Z","isPatch":true,"sender":{"key":"chris@arachsys.com","avatar":"https://avatars.githubusercontent.com/u/299056?v=4"},"body":"Chris Webb <chris@arachsys.com> writes:\n\n> A common idiom when working with hg bookmarks is to completely ignore the\n> (not very useful) hg branches (i.e. all commits are on the default hg\n> branch) and have a bookmark for each line of development used exactly as a\n> git branch would be.\n> \n> On such a repository, at the moment you will always get a warning about\n> multiple heads on branches/default, even though you actually don't care\n> about branches/default (and would prefer it not to exist) and just want the\n> branches coming from the bookmarks.\n\nSomething which you can do with hg clone is\n\n  hg clone http://my.repo/foo#master\n\nto clone just the history behind the master bookmark from foo. This works\nnicely with git-remote-hg too:\n\n  git clone hg::http://my.repo/foo#master\n\ngives you just origin/master and origin/branches/default, not\norigin/otherbookmark. This is a case where it would be particularly nice to\nbe able to kill origin/branches/default and just keep the identical\norigin/master.\n\nCheers,\n\nChris.\n"},{"id":"202219","messageId":"CAMP44s3LY0m14LkuRHhbMTqsgD=t1Xw+KLsac9e=tgpnhGc+NA@mail.gmail.com","threadId":"31976","inReplyTo":"20121030180021.GX26850@arachsys.com","subject":"Re: [PATCH v5 00/14] New remote-hg helper","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2012-10-30T18:29:30Z","receivedAt":"2012-10-30T18:29:30Z","isPatch":true,"sender":{"key":"felipe.contreras@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8358?v=4"},"body":"On Tue, Oct 30, 2012 at 7:00 PM, Chris Webb <chris@arachsys.com> wrote:\n> Felipe Contreras <felipe.contreras@gmail.com> writes:\n>\n>> Yes, it seems this is an API issue; repo.branchtip doesn't exist in\n>> python 2.2.\n>\n> Hi. Presumably this is a problem with old mercurial not a problem with old\n> python as mentioned in the commit?\n>\n>> Both issues should be fixed now :)\n>\n> They are indeed, and it now works nicely on all the repos I've tested it\n> with, including http://selenic.com/hg: very impressive!\n>\n> I wonder whether it's worth ignoring heads with bookmarks pointing to them\n> when it comes to considering heads of branches, or at least allowing the\n> hg branch tracking to be easily disabled?\n>\n> A common idiom when working with hg bookmarks is to completely ignore the\n> (not very useful) hg branches (i.e. all commits are on the default hg\n> branch) and have a bookmark for each line of development used exactly as a\n> git branch would be.\n>\n> On such a repository, at the moment you will always get a warning about\n> multiple heads on branches/default, even though you actually don't care\n> about branches/default (and would prefer it not to exist) and just want the\n> branches coming from the bookmarks.\n>\n> I've also seen repositories with no hg branches, but with a single\n> unbookmarked tip and bookmarks on some other heads to mark non-mainline\n> development. It would be nice for branches/default to track the unbookmarked\n> tip in this case, without warning about the other, bookmarked heads.\n>\n> Finally, on a simple repo with no branches and where there's no clash with a\n> bookmark called master, I'd love to be able to a get a more idiomatic\n> origin/master rather than origin/branches/default.\n\nSounds like you might want to try this:\n% git config --global remote-hg.hg-git-compat true\n\nThis would match the behavior of hg-git, which seems to be what you\nare looking for: branches will be ignored. But additionally there will\nbe other obtrusive changes:\n\n 1) Extra information will be stored in the commit message: branch,\nrenames, extra info.\n\nPersonally I don't like this, which is why it's only enabled with the\nhg-git compat mode. Eventually there will be support for this in the\nnormal mode through git notes, and eventually (hopefully), hg-git will\nuse notes as well.\n\n 2) Authors will be fixed trying to preserve as much information as possible\n\n\nThe problem with this mode is what happens when there are no\nbookmarks, and what I decided to do is create a fake bookmark to the\ncurrent branch (e.g. default), so you would get 'origin/default', but\nyou will be warned if there are multiple heads. We could try to create\na 'master' ref to the 'tip', but that might not be what the user\nwants: (s)he might switch to a certain branch, and clone the\nrepository in git; currently the result working directory would be the\nsame as in hg, but if we point to 'tip', then it would not.\n\nMaybe we should have a branch pointing to 'tip' either way.\n\nThe reason I decided on having 'branches/default' on the normal mode\nis that users unfamiliar with mercurial might expect to see all the\nbranches somewhere. But perhaps that should happen only for other\nbranches, while 'default' gets a special treatment and gets promoted\nto 'origin/master' but only if there's no 'master' bookmark, as you\nsay. Now the tricky part would be handling the case where there was no\n'master' bookmark, and suddenly one gets created, but it might be\noverkill to think about that right now.\n\nEither way, try the hg-git-compat mode, should be close :)\n\nCheers.\n\n-- \nFelipe Contreras\n"},{"id":"202357","messageId":"CAMP44s1-VOetN+e49UgJtpbpwYN2EBVYBzw5j_KoqXu6sbbaHA@mail.gmail.com","threadId":"31976","inReplyTo":"20121030180021.GX26850@arachsys.com","subject":"Re: [PATCH v5 00/14] New remote-hg helper","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2012-11-01T06:05:21Z","receivedAt":"2012-11-01T06:05:21Z","isPatch":true,"sender":{"key":"felipe.contreras@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8358?v=4"},"body":"On Tue, Oct 30, 2012 at 7:00 PM, Chris Webb <chris@arachsys.com> wrote:\n> Felipe Contreras <felipe.contreras@gmail.com> writes:\n>\n>> Yes, it seems this is an API issue; repo.branchtip doesn't exist in\n>> python 2.2.\n>\n> Hi. Presumably this is a problem with old mercurial not a problem with old\n> python as mentioned in the commit?\n\nYeah, mercurial.\n\n>> Both issues should be fixed now :)\n>\n> They are indeed, and it now works nicely on all the repos I've tested it\n> with, including http://selenic.com/hg: very impressive!\n>\n> I wonder whether it's worth ignoring heads with bookmarks pointing to them\n> when it comes to considering heads of branches, or at least allowing the\n> hg branch tracking to be easily disabled?\n>\n> A common idiom when working with hg bookmarks is to completely ignore the\n> (not very useful) hg branches (i.e. all commits are on the default hg\n> branch) and have a bookmark for each line of development used exactly as a\n> git branch would be.\n>\n> On such a repository, at the moment you will always get a warning about\n> multiple heads on branches/default, even though you actually don't care\n> about branches/default (and would prefer it not to exist) and just want the\n> branches coming from the bookmarks.\n>\n> I've also seen repositories with no hg branches, but with a single\n> unbookmarked tip and bookmarks on some other heads to mark non-mainline\n> development. It would be nice for branches/default to track the unbookmarked\n> tip in this case, without warning about the other, bookmarked heads.\n\nImplemented now. I'm not handling the 'tip' revision, but most likely\nit's also the '.' revision. In this case a fake 'master' bookmark will\nbe created to track that revision.\n\n> Finally, on a simple repo with no branches and where there's no clash with a\n> bookmark called master, I'd love to be able to a get a more idiomatic\n> origin/master rather than origin/branches/default.\n\nYeah, you can get that now. If there are no branches or bookmarks,\n'master' will point to '.'.\n\nAs for your preference of not tracking branches, there's a new option:\n\n% git config --global remote-hg.track-branches false\n\nSo you don't have to use hg-git-compat mode :)\n\nCheers.\n\n-- \nFelipe Contreras\n"},{"id":"202926","messageId":"20121111221712.GB2774@arachsys.com","threadId":"31976","inReplyTo":"CAMP44s1-VOetN+e49UgJtpbpwYN2EBVYBzw5j_KoqXu6sbbaHA@mail.gmail.com","subject":"Re: [PATCH v5 00/14] New remote-hg helper","fromName":"Chris Webb","fromEmail":"chris@arachsys.com","sentAt":"2012-11-11T22:17:13Z","receivedAt":"2012-11-11T22:17:13Z","isPatch":true,"sender":{"key":"chris@arachsys.com","avatar":"https://avatars.githubusercontent.com/u/299056?v=4"},"body":"Felipe Contreras <felipe.contreras@gmail.com> writes:\n\n> Implemented now. I'm not handling the 'tip' revision, but most likely\n> it's also the '.' revision. In this case a fake 'master' bookmark will\n> be created to track that revision.\n\nHi Felipe. Sorry for the slow response, I've been snowed under with work and\nhave only just got around to testing your latest version.\n\nThe new remote-hg.track-branches=false option is great and does exactly what\nI was hoping for. For the benefit of the list archives, one natural way to\nuse it is\n\n  git clone -c remote-hg.track-branches=false hg::foo\n\nwhen cloning the relevant repositories, if you don't want the setting\nglobally for every hg-remote clone.\n\nDuring testing, I've seen some strange behaviour which I think is caused by\nusing the . revision instead of tip:\n\n$ hg init h\n$ hg init h2\n$ ( cd h && touch foo && hg add foo && hg commit -m foo && hg push ../h2 )\npushing to ../h2\nsearching for changes\nadding changesets\nadding manifests\nadding file changes\nadded 1 changesets with 1 changes to 1 files\n$ git clone hg::h g\nCloning into 'g'...\n$ git clone hg::h2 g2\nCloning into 'g2'...\nwarning: remote HEAD refers to nonexistent ref, unable to checkout.\n$\n\nThe reason for this is that by default . == null (not tip) in the repo h2\nwhich we pushed into from h. The hg equivalent of a bare repo typically has a\nnull checkout like this. (Actually, the checkout of HEAD seems to break\nwhenever . is different from tip, not just when it's null as in this example.)\n\nApart from this, everything seems solid and works well. Really useful; thanks!\n\nBest wishes,\n\nChris.\n"},{"id":"203038","messageId":"CAMP44s3oJhrZ=pdT040uHtN1SmGNQzvvVGGG+W6VfOvch-5qmg@mail.gmail.com","threadId":"31976","inReplyTo":"20121111221712.GB2774@arachsys.com","subject":"Re: [PATCH v5 00/14] New remote-hg helper","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2012-11-13T03:45:02Z","receivedAt":"2012-11-13T03:45:02Z","isPatch":true,"sender":{"key":"felipe.contreras@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8358?v=4"},"body":"On Sun, Nov 11, 2012 at 11:17 PM, Chris Webb <chris@arachsys.com> wrote:\n> Felipe Contreras <felipe.contreras@gmail.com> writes:\n>\n>> Implemented now. I'm not handling the 'tip' revision, but most likely\n>> it's also the '.' revision. In this case a fake 'master' bookmark will\n>> be created to track that revision.\n>\n> Hi Felipe. Sorry for the slow response, I've been snowed under with work and\n> have only just got around to testing your latest version.\n>\n> The new remote-hg.track-branches=false option is great and does exactly what\n> I was hoping for. For the benefit of the list archives, one natural way to\n> use it is\n>\n>   git clone -c remote-hg.track-branches=false hg::foo\n>\n> when cloning the relevant repositories, if you don't want the setting\n> globally for every hg-remote clone.\n\nCool :)\n\n> During testing, I've seen some strange behaviour which I think is caused by\n> using the . revision instead of tip:\n>\n> $ hg init h\n> $ hg init h2\n> $ ( cd h && touch foo && hg add foo && hg commit -m foo && hg push ../h2 )\n> pushing to ../h2\n> searching for changes\n> adding changesets\n> adding manifests\n> adding file changes\n> added 1 changesets with 1 changes to 1 files\n> $ git clone hg::h g\n> Cloning into 'g'...\n> $ git clone hg::h2 g2\n> Cloning into 'g2'...\n> warning: remote HEAD refers to nonexistent ref, unable to checkout.\n> $\n>\n> The reason for this is that by default . == null (not tip) in the repo h2\n> which we pushed into from h. The hg equivalent of a bare repo typically has a\n> null checkout like this. (Actually, the checkout of HEAD seems to break\n> whenever . is different from tip, not just when it's null as in this example.)\n\nWell, I thought in those cases we didn't want HEAD to be updated.\nPeople can still use the repo and checkout whatever branch they want.\nBut '.' is not  really the equivalent of HEAD. Since there's no\nequivalent, I think it makes sense to try first '.', and then 'tip'.\nThis means that we would never have a HEAD pointing to nowhere.\n\nI've added a patch for that.\n\nCheers.\n-- \nFelipe Contreras\n"}]}