Commits

David Carr committed dbfc443

Import performance patches

Comments (0)

Files changed (13)

bookmark-delete.diff

 # Parent 878ae1d1bd73f5e079f6990b4a93e9a9f5405fb8
 WIP: bookmark delete
 
-diff -r 878ae1d1bd73 hggit/git_handler.py
---- a/hggit/git_handler.py	Fri Sep 14 19:10:18 2012 -0400
-+++ b/hggit/git_handler.py	Sat Sep 15 14:27:11 2012 -0400
+diff --git a/hggit/git_handler.py b/hggit/git_handler.py
+--- a/hggit/git_handler.py
++++ b/hggit/git_handler.py
 @@ -244,6 +244,21 @@
          except (HangupException, GitProtocolError), e:
              raise hgutil.Abort(_("git remote error: ") + str(e))
  
              # Check if the tags the server is advertising are annotated tags,
              # by attempting to retrieve it from the our git repo, and building a
-diff -r 878ae1d1bd73 hggit/gitrepo.py
---- a/hggit/gitrepo.py	Fri Sep 14 19:10:18 2012 -0400
-+++ b/hggit/gitrepo.py	Sat Sep 15 14:27:11 2012 -0400
+diff --git a/hggit/gitrepo.py b/hggit/gitrepo.py
+--- a/hggit/gitrepo.py
++++ b/hggit/gitrepo.py
 @@ -1,3 +1,4 @@
 +import os
  from mercurial import util
          return False
  
      # used by incoming in hg <= 1.6
-diff -r 878ae1d1bd73 hggit/hgrepo.py
---- a/hggit/hgrepo.py	Fri Sep 14 19:10:18 2012 -0400
-+++ b/hggit/hgrepo.py	Sat Sep 15 14:27:11 2012 -0400
+diff --git a/hggit/hgrepo.py b/hggit/hgrepo.py
+--- a/hggit/hgrepo.py
++++ b/hggit/hgrepo.py
 @@ -11,6 +11,7 @@
          def pull(self, remote, heads=None, force=False):
              if isinstance(remote, gitrepo):
                  base, heads = git.get_refs(remote.path)
                  out, h = super(hgrepo, self).findoutgoing(remote, base, heads, force)
                  return out
-diff -r 878ae1d1bd73 tests/test-push.t
---- a/tests/test-push.t	Fri Sep 14 19:10:18 2012 -0400
-+++ b/tests/test-push.t	Sat Sep 15 14:27:11 2012 -0400
-@@ -151,5 +151,30 @@
+diff --git a/tests/test-outgoing.t b/tests/test-outgoing.t
+old mode 100755
+new mode 100644
+--- a/tests/test-outgoing.t
++++ b/tests/test-outgoing.t
+@@ -119,6 +119,7 @@
+   $ hg pull
+   pulling from */gitrepo (glob)
+   importing git objects into hg
++  divergent bookmark master stored as master@default
+   (run 'hg update' to get a working copy)
+   $ hg outgoing | sed 's/bookmark:    /tag:         /' | grep -v 'searching for changes'
+   comparing with */gitrepo (glob)
+diff --git a/tests/test-push.t b/tests/test-push.t
+old mode 100755
+new mode 100644
+--- a/tests/test-push.t
++++ b/tests/test-push.t
+@@ -105,6 +105,7 @@
+   $ hg pull
+   pulling from $TESTTMP/gitrepo
+   importing git objects into hg
++  divergent bookmark master stored as master@default
+   (run 'hg update' to get a working copy)
+ TODO shouldn't need to do this since we're (in theory) pushing master explicitly,
+ which should not implicitly also push the not-master ref.
+@@ -119,6 +120,7 @@
+   
+   $ hg log -r default/master
+   changeset:   3:1436150b86c2
++  bookmark:    master@default
+   tag:         default/master
+   tag:         tip
+   parent:      0:3442585be8a6
+@@ -151,5 +153,30 @@
    creating and sending data
    no changes found
    [1]

implement_treetracker_for_incremental_tree_calculation

+# HG changeset patch
+# User Gregory Szorc <gregory.szorc@gmail.com>
+# Date 1348422117 25200
+# Node ID 85c4b8e2e129975f400c9810eb9bf6ce6fea4c8b
+# Parent  ef583ac939de39b80aaff2d1d3d9f47bf1a1c9f3
+Implement TreeTracker for incremental tree calculation
+
+This class makes exporting Mercurial changesets to Git much faster.
+
+diff --git a/hggit/git_handler.py b/hggit/git_handler.py
+--- a/hggit/git_handler.py
++++ b/hggit/git_handler.py
+@@ -1,13 +1,12 @@
+ import os, math, urllib, re
+ import stat, posixpath, StringIO
+ 
+ from dulwich.errors import HangupException, GitProtocolError, UpdateRefsError
+-from dulwich.index import commit_tree
+ from dulwich.objects import Blob, Commit, Tag, Tree, parse_timezone, S_IFGITLINK
+ from dulwich.pack import create_delta, apply_delta
+ from dulwich.repo import Repo
+ from dulwich import client
+ from dulwich import config as dul_config
+ 
+ try:
+     from mercurial import bookmarks
+@@ -24,16 +23,18 @@
+ from mercurial.node import hex, bin, nullid
+ from mercurial import context, util as hgutil
+ from mercurial import error
+ 
+ import _ssh
+ import util
+ from overlay import overlayrepo
+ 
++from .hg2git import TreeTracker
++
+ RE_GIT_AUTHOR = re.compile('^(.*?) ?\<(.*?)(?:\>(.*))?$')
+ 
+ RE_GIT_SANITIZE_AUTHOR = re.compile('[<>\n]')
+ 
+ RE_GIT_AUTHOR_EXTRA = re.compile('^(.*?)\ ext:\((.*)\) <(.*)\>$')
+ 
+ # Test for git:// and git+ssh:// URI.
+ # Support several URL forms, including separating the
+@@ -323,32 +324,35 @@
+     def export_git_objects(self):
+         self.init_if_missing()
+ 
+         nodes = [self.repo.lookup(n) for n in self.repo]
+         export = [node for node in nodes if not hex(node) in self._map_hg]
+         total = len(export)
+         if total:
+             self.ui.status(_("exporting hg objects to git\n"))
++
++        tracker = TreeTracker(self.repo)
++
+         for i, rev in enumerate(export):
+             util.progress(self.ui, 'exporting', i, total=total)
+             ctx = self.repo.changectx(rev)
+             state = ctx.extra().get('hg-git', None)
+             if state == 'octopus':
+                 self.ui.debug("revision %d is a part "
+                               "of octopus explosion\n" % ctx.rev())
+                 continue
+-            self.export_hg_commit(rev)
++            self.export_hg_commit(rev, tracker)
+         util.progress(self.ui, 'importing', None, total=total)
+ 
+ 
+     # convert this commit into git objects
+     # go through the manifest, convert all blobs/trees we don't have
+     # write the commit object (with metadata info)
+-    def export_hg_commit(self, rev):
++    def export_hg_commit(self, rev, tracker):
+         self.ui.note(_("converting revision %s\n") % hex(rev))
+ 
+         oldenc = self.swap_out_encoding()
+ 
+         ctx = self.repo.changectx(rev)
+         extra = ctx.extra()
+ 
+         commit = Commit()
+@@ -390,17 +394,21 @@
+ 
+                 commit.parents.append(git_sha)
+ 
+         commit.message = self.get_git_message(ctx)
+ 
+         if 'encoding' in extra:
+             commit.encoding = extra['encoding']
+ 
+-        tree_sha = commit_tree(self.git.object_store, self.iterblobs(ctx))
++        for obj in tracker.update_changeset(ctx):
++            self.git.object_store.add_object(obj)
++
++        tree_sha = tracker.root_tree_sha
++
+         if tree_sha not in self.git.object_store:
+             raise hgutil.Abort(_('Tree SHA-1 not present in Git repo: %s' %
+                 tree_sha))
+ 
+         commit.tree = tree_sha
+ 
+         self.git.object_store.add_object(commit)
+         self.map_set(commit.id, ctx.hex())
+@@ -536,53 +544,16 @@
+                 add_extras = True
+                 extra_message += "extra : " + key + " : " +  urllib.quote(value) + "\n"
+ 
+         if add_extras:
+             message += "\n--HG--\n" + extra_message
+ 
+         return message
+ 
+-    def iterblobs(self, ctx):
+-        if '.hgsubstate' in ctx:
+-            hgsub = util.OrderedDict()
+-            if '.hgsub' in ctx:
+-                hgsub = util.parse_hgsub(ctx['.hgsub'].data().splitlines())
+-            hgsubstate = util.parse_hgsubstate(ctx['.hgsubstate'].data().splitlines())
+-            for path, sha in hgsubstate.iteritems():
+-                try:
+-                    if path in hgsub and not hgsub[path].startswith('[git]'):
+-                        # some other kind of a repository (e.g. [hg])
+-                        # that keeps its state in .hgsubstate, shall ignore
+-                        continue
+-                    yield path, sha, S_IFGITLINK
+-                except ValueError:
+-                    pass
+-
+-        for f in ctx:
+-            if f == '.hgsubstate' or f == '.hgsub':
+-                continue
+-            fctx = ctx[f]
+-            blobid = self.map_git_get(hex(fctx.filenode()))
+-
+-            if not blobid:
+-                blob = Blob.from_string(fctx.data())
+-                self.git.object_store.add_object(blob)
+-                self.map_set(blob.id, hex(fctx.filenode()))
+-                blobid = blob.id
+-
+-            if 'l' in ctx.flags(f):
+-                mode = 0120000
+-            elif 'x' in ctx.flags(f):
+-                mode = 0100755
+-            else:
+-                mode = 0100644
+-
+-            yield f, blobid, mode
+-
+     def getnewgitcommits(self, refs=None):
+         self.init_if_missing()
+ 
+         # import heads and fetched tags as remote references
+         todo = []
+         done = set()
+         convert_list = {}
+ 
+diff --git a/hggit/hg2git.py b/hggit/hg2git.py
+new file mode 100644
+--- /dev/null
++++ b/hggit/hg2git.py
+@@ -0,0 +1,205 @@
++# This file contains code dealing specifically with converting Mercurial
++# repositories to Git repositories. Code in this file is meant to be a generic
++# library and should be usable outside the context of hg-git or an hg command.
++
++import os
++import stat
++
++from dulwich.objects import Blob
++from dulwich.objects import S_IFGITLINK
++from dulwich.objects import TreeEntry
++from dulwich.objects import Tree
++
++from mercurial import error as hgerror
++from mercurial.node import nullrev
++
++from . import util
++
++class TreeTracker(object):
++    """Tracks Git tree objects across Mercurial revisions.
++
++    The purpose of this class is to facilitate Git tree export that is more
++    optimal than brute force. The tree calculation part of this class is
++    essentially a reimplementation of dulwich.index.commit_tree. However, since
++    our implementation reuses Tree instances and only recalculates SHA-1 when
++    things change, we are much more efficient.
++
++    Callers instantiate this class against a mercurial.localrepo instance. They
++    then associate the tracker with a specific changeset by calling
++    update_changeset(). That function emits Git objects that need to be
++    exported to a Git repository. Callers then typically obtain the
++    root_tree_sha and use that as part of assembling a Git commit.
++    """
++
++    def __init__(self, hg_repo):
++        self._hg = hg_repo
++        self._rev = nullrev
++        self._dirs = {}
++        self._blob_cache = {}
++
++    @property
++    def root_tree_sha(self):
++        return self._dirs[''].id
++
++    def update_changeset(self, ctx):
++        """Set the tree to track a new Mercurial changeset.
++
++        This is a generator of dulwich Git objects. Each returned object can be
++        added to a Git store via add_object(). Some objects may already exist
++        in the Git repository. Emitted objects are either Blob or Tree
++        instances.
++
++        Emitted objects are those that have changed since the last call to
++        update_changeset.
++        """
++        # In theory we should be able to look at changectx.files(). This is
++        # *much* faster. However, it may not be accurate, especially with older
++        # repositories, which may not record things like deleted files
++        # explicitly in the manifest (which is where files() gets its data).
++        # The only reliable way to get the full set of changes is by looking at
++        # the full manifest. And, the easy way to compare two manifests is
++        # localrepo.status().
++
++        # The other members of status are only relevant when looking at the
++        # working directory.
++        modified, added, removed = self._hg.status(self._rev, ctx.rev())[0:3]
++
++        for path in sorted(removed, key=len, reverse=True):
++            d = os.path.dirname(path)
++            tree = self._dirs.get(d, Tree())
++
++            del tree[os.path.basename(path)]
++
++            if not len(tree):
++                self._remove_tree(d)
++                continue
++
++            self._dirs[d] = tree
++
++        for path in sorted(set(modified) | set(added), key=len, reverse=True):
++            if path == '.hgsubstate':
++                self._handle_subrepos(ctx)
++                continue
++
++            if path == '.hgsub':
++                continue
++
++            d = os.path.dirname(path)
++            tree = self._dirs.get(d, Tree())
++
++            fctx = ctx[path]
++
++            entry, blob = TreeTracker.tree_entry(fctx, self._blob_cache)
++            if blob is not None:
++                yield blob
++
++            tree.add(*entry)
++            self._dirs[d] = tree
++
++        for obj in self._populate_tree_entries():
++            yield obj
++
++        self._rev = ctx.rev()
++
++    def _remove_tree(self, path):
++        try:
++            del self._dirs[path]
++        except KeyError:
++            return
++
++        # Now we traverse up to the parent and delete any references.
++        if path == '':
++            return
++
++        basename = os.path.basename(path)
++        parent = os.path.dirname(path)
++        while True:
++            tree = self._dirs.get(parent, None)
++
++            # No parent entry. Nothing to remove or update.
++            if tree is None:
++                return
++
++            try:
++                del tree[basename]
++            except KeyError:
++                return
++
++            if len(tree):
++                return
++
++            # The parent tree is empty. Se, we can delete it.
++            del self._dirs[parent]
++
++            if parent == '':
++                return
++
++            basename = os.path.basename(parent)
++            parent = os.path.dirname(parent)
++
++    def _populate_tree_entries(self):
++        if '' not in self._dirs:
++            self._dirs[''] = Tree()
++
++        # Fill in missing directories.
++        for path in self._dirs.keys():
++            parent = os.path.dirname(path)
++
++            while parent != '':
++                parent_tree = self._dirs.get(parent, None)
++
++                if parent_tree is not None:
++                    break
++
++                self._dirs[parent] = Tree()
++                parent = os.path.dirname(parent)
++
++        # TODO only emit trees that have been modified.
++        for d in sorted(self._dirs.keys(), key=len, reverse=True):
++            tree = self._dirs[d]
++            yield tree
++
++            if d == '':
++                continue
++
++            parent_tree = self._dirs[os.path.dirname(d)]
++            parent_tree[os.path.basename(d)] = (stat.S_IFDIR, tree.id)
++
++    def _handle_subrepos(self, ctx):
++        substate = util.parse_hgsubstate(ctx['.hgsubstate'].data().splitlines())
++        sub = util.OrderedDict()
++
++        if '.hgsub' in ctx:
++            sub = util.parse_hgsub(ctx['.hgsub'].data().splitlines())
++
++        for path, sha in substate.iteritems():
++            # Ignore non-Git repositories keeping state in .hgsubstate.
++            if path in sub and not sub[path].startswith('[git]'):
++                continue
++
++            d = os.path.dirname(path)
++            tree = self._dirs.get(d, Tree())
++            tree.add(os.path.basename(path), S_IFGITLINK, sha)
++            self._dirs[d] = tree
++
++    @staticmethod
++    def tree_entry(fctx, blob_cache):
++        blob_id = blob_cache.get(fctx.filenode(), None)
++        blob = None
++
++        if blob_id is None:
++            blob = Blob.from_string(fctx.data())
++            blob_id = blob.id
++            blob_cache[fctx.filenode()] = blob_id
++
++        flags = fctx.flags()
++
++        if 'l' in flags:
++            mode = 0120000
++        elif 'x' in flags:
++            mode = 0100755
++        else:
++            mode = 0100644
++
++        return (TreeEntry(os.path.basename(fctx.path()), mode, blob_id), blob)
++
+
+-- 
+You received this message because you are subscribed to the Google Groups "hg-git" group.
+To post to this group, send email to hg-git@googlegroups.com.
+To unsubscribe from this group, send email to hg-git+unsubscribe@googlegroups.com.
+For more options, visit this group at http://groups.google.com/group/hg-git?hl=en.
+

make_get_valid_git_username_email_static

+# HG changeset patch
+# User Gregory Szorc <gregory.szorc@gmail.com>
+# Date 1348284630 25200
+# Node ID ef583ac939de39b80aaff2d1d3d9f47bf1a1c9f3
+# Parent  2db03c124dde9c84de1006526f497d867094a231
+Make get_valid_git_username_email static
+
+Also alias where it is used to make code a little easier to read.
+
+diff --git a/hggit/git_handler.py b/hggit/git_handler.py
+--- a/hggit/git_handler.py
++++ b/hggit/git_handler.py
+@@ -403,17 +403,18 @@
+         commit.tree = tree_sha
+ 
+         self.git.object_store.add_object(commit)
+         self.map_set(commit.id, ctx.hex())
+ 
+         self.swap_out_encoding(oldenc)
+         return commit.id
+ 
+-    def get_valid_git_username_email(self, name):
++    @staticmethod
++    def get_valid_git_username_email(name):
+         r"""Sanitize usernames and emails to fit git's restrictions.
+ 
+         The following is taken from the man page of git's fast-import
+         command:
+ 
+             [...] Likewise LF means one (and only one) linefeed [...]
+ 
+             committer
+@@ -435,17 +436,17 @@
+         angle brackets and spaces from the beginning, and right angle
+         brackets and spaces from the end, of this string, to convert
+         such things as " <john@doe.com> " to "john@doe.com" for
+         convenience.
+ 
+         TESTS:
+ 
+         >>> from mercurial.ui import ui
+-        >>> g = GitHandler('', ui()).get_valid_git_username_email
++        >>> g = GitHandler.get_valid_git_username_email
+         >>> g('John Doe')
+         'John Doe'
+         >>> g('john@doe.com')
+         'john@doe.com'
+         >>> g(' <john@doe.com> ')
+         'john@doe.com'
+         >>> g('    <random<\n<garbage\n>  > > ')
+         'random???garbage?'
+@@ -459,26 +460,28 @@
+         author = ctx.user()
+ 
+         # see if a translation exists
+         author = self.author_map.get(author, author)
+ 
+         # check for git author pattern compliance
+         a = RE_GIT_AUTHOR.match(author)
+ 
++        get_valid = GitHandler.get_valid_git_username_email
++
+         if a:
+-            name = self.get_valid_git_username_email(a.group(1))
+-            email = self.get_valid_git_username_email(a.group(2))
++            name = get_valid(a.group(1))
++            email = get_valid(a.group(2))
+             if a.group(3) != None and len(a.group(3)) != 0:
+                 name += ' ext:(' + urllib.quote(a.group(3)) + ')'
+-            author = self.get_valid_git_username_email(name) + ' <' + self.get_valid_git_username_email(email) + '>'
++            author = get_valid(name) + ' <' + get_valid(email) + '>'
+         elif '@' in author:
+-            author = self.get_valid_git_username_email(author) + ' <' + self.get_valid_git_username_email(author) + '>'
++            author = get_valid(author) + ' <' + get_valid(author) + '>'
+         else:
+-            author = self.get_valid_git_username_email(author) + ' <none@none>'
++            author = get_valid(author) + ' <none@none>'
+ 
+         if 'author' in ctx.extra():
+             author = "".join(apply_delta(author, ctx.extra()['author']))
+ 
+         return author
+ 
+     def get_git_parents(self, ctx):
+         def is_octopus_part(ctx):
+
+-- 
+You received this message because you are subscribed to the Google Groups "hg-git" group.
+To post to this group, send email to hg-git@googlegroups.com.
+To unsubscribe from this group, send email to hg-git+unsubscribe@googlegroups.com.
+For more options, visit this group at http://groups.google.com/group/hg-git?hl=en.
+

optimize_get_git_author

+# HG changeset patch
+# User Gregory Szorc <gregory.szorc@gmail.com>
+# Date 1348280926 25200
+# Node ID 5ca256907196d908a23dc72bf3230e975565d6e8
+# Parent  e152bdf5998098e135d15affd2a98c357d323b3f
+Optimize get_git_author
+
+Pre-compile regular expression. Prevent extra key lookup in author_map.
+
+diff --git a/hggit/git_handler.py b/hggit/git_handler.py
+--- a/hggit/git_handler.py
++++ b/hggit/git_handler.py
+@@ -24,16 +24,18 @@
+ from mercurial.node import hex, bin, nullid
+ from mercurial import context, util as hgutil
+ from mercurial import error
+ 
+ import _ssh
+ import util
+ from overlay import overlayrepo
+ 
++RE_GIT_AUTHOR = re.compile('^(.*?) ?\<(.*?)(?:\>(.*))?$')
++
+ class GitProgress(object):
+     """convert git server progress strings into mercurial progress"""
+     def __init__(self, ui):
+         self.ui = ui
+ 
+         self.lasttopic = None
+         self.msgbuf = ''
+ 
+@@ -428,22 +430,20 @@
+         """
+         return re.sub('[<>\n]', '?', name.lstrip('< ').rstrip('> '))
+ 
+     def get_git_author(self, ctx):
+         # hg authors might not have emails
+         author = ctx.user()
+ 
+         # see if a translation exists
+-        if author in self.author_map:
+-            author = self.author_map[author]
++        author = self.author_map.get(author, author)
+ 
+         # check for git author pattern compliance
+-        regex = re.compile('^(.*?) ?\<(.*?)(?:\>(.*))?$')
+-        a = regex.match(author)
++        a = RE_GIT_AUTHOR.match(author)
+ 
+         if a:
+             name = self.get_valid_git_username_email(a.group(1))
+             email = self.get_valid_git_username_email(a.group(2))
+             if a.group(3) != None and len(a.group(3)) != 0:
+                 name += ' ext:(' + urllib.quote(a.group(3)) + ')'
+             author = self.get_valid_git_username_email(name) + ' <' + self.get_valid_git_username_email(email) + '>'
+         elif '@' in author:
+
+-- 
+You received this message because you are subscribed to the Google Groups "hg-git" group.
+To post to this group, send email to hg-git@googlegroups.com.
+To unsubscribe from this group, send email to hg-git+unsubscribe@googlegroups.com.
+For more options, visit this group at http://groups.google.com/group/hg-git?hl=en.
+

precompile_author_file_regular_expression

+# HG changeset patch
+# User Gregory Szorc <gregory.szorc@gmail.com>
+# Date 1348281830 25200
+# Node ID 95b937230a1352d738c81bbe1e3b3a031e071956
+# Parent  df4f8b7f800ff2a4c8a0c8e30575f38d5ea45fcb
+Precompile author file regular expression
+
+diff --git a/hggit/git_handler.py b/hggit/git_handler.py
+--- a/hggit/git_handler.py
++++ b/hggit/git_handler.py
+@@ -40,16 +40,18 @@
+ # host and path with either a / or : (sepr)
+ RE_GIT_URI = re.compile(
+     r'^(?P<scheme>git([+]ssh)?://)(?P<host>.*?)(:(?P<port>\d+))?'
+     r'(?P<sepr>[:/])(?P<path>.*)$')
+ 
+ RE_NEWLINES = re.compile('[\r\n]')
+ RE_GIT_PROGRESS = re.compile('\((\d+)/(\d+)\)')
+ 
++RE_AUTHOR_FILE = re.compile('\s*=\s*')
++
+ class GitProgress(object):
+     """convert git server progress strings into mercurial progress"""
+     def __init__(self, ui):
+         self.ui = ui
+ 
+         self.lasttopic = None
+         self.msgbuf = ''
+ 
+@@ -120,17 +122,17 @@
+         if self.ui.config('git', 'authors'):
+             with open(self.repo.wjoin(
+                 self.ui.config('git', 'authors')
+             )) as f:
+                 for line in f:
+                     line = line.strip()
+                     if not line or line.startswith('#'):
+                         continue
+-                    from_, to = re.split(r'\s*=\s*', line, 2)
++                    from_, to = RE_AUTHOR_FILE.split(line, 2)
+                     self.author_map[from_] = to
+ 
+     ## FILE LOAD AND SAVE METHODS
+ 
+     def map_set(self, gitsha, hgsha):
+         self._map_git[gitsha] = hgsha
+         self._map_hg[hgsha] = gitsha
+ 
+
+-- 
+You received this message because you are subscribed to the Google Groups "hg-git" group.
+To post to this group, send email to hg-git@googlegroups.com.
+To unsubscribe from this group, send email to hg-git+unsubscribe@googlegroups.com.
+For more options, visit this group at http://groups.google.com/group/hg-git?hl=en.
+

precompile_git_author_extra_data_regular_expression

+# HG changeset patch
+# User Gregory Szorc <gregory.szorc@gmail.com>
+# Date 1348281593 25200
+# Node ID fbb9ade686ffdd66ed30a7e9b68d9246409f7389
+# Parent  1de6cd07221e0d5fb90b30e515891a325574a9fc
+Precompile Git author extra data regular expression
+
+diff --git a/hggit/git_handler.py b/hggit/git_handler.py
+--- a/hggit/git_handler.py
++++ b/hggit/git_handler.py
+@@ -28,16 +28,18 @@
+ import _ssh
+ import util
+ from overlay import overlayrepo
+ 
+ RE_GIT_AUTHOR = re.compile('^(.*?) ?\<(.*?)(?:\>(.*))?$')
+ 
+ RE_GIT_SANITIZE_AUTHOR = re.compile('[<>\n]')
+ 
++RE_GIT_AUTHOR_EXTRA = re.compile('^(.*?)\ ext:\((.*)\) <(.*)\>$')
++
+ # Test for git:// and git+ssh:// URI.
+ # Support several URL forms, including separating the
+ # host and path with either a / or : (sepr)
+ RE_GIT_URI = re.compile(
+     r'^(?P<scheme>git([+]ssh)?://)(?P<host>.*?)(:(?P<port>\d+))?'
+     r'(?P<sepr>[:/])(?P<path>.*)$')
+ 
+ class GitProgress(object):
+@@ -702,18 +704,17 @@
+         text = '\n'.join([l.rstrip() for l in text.splitlines()]).strip('\n')
+         if text + '\n' != origtext:
+             extra['message'] = create_delta(text +'\n', origtext)
+ 
+         author = commit.author
+ 
+         # convert extra data back to the end
+         if ' ext:' in commit.author:
+-            regex = re.compile('^(.*?)\ ext:\((.*)\) <(.*)\>$')
+-            m = regex.match(commit.author)
++            m = RE_GIT_AUTHOR_EXTRA.match(commit.author)
+             if m:
+                 name = m.group(1)
+                 ex = urllib.unquote(m.group(2))
+                 email = m.group(3)
+                 author = name + ' <' + email + '>' + ex
+ 
+         if ' <none@none>' in commit.author:
+             author = commit.author[:-12]
+
+-- 
+You received this message because you are subscribed to the Google Groups "hg-git" group.
+To post to this group, send email to hg-git@googlegroups.com.
+To unsubscribe from this group, send email to hg-git+unsubscribe@googlegroups.com.
+For more options, visit this group at http://groups.google.com/group/hg-git?hl=en.
+

precompile_git_progress_regular_expressions

+# HG changeset patch
+# User Gregory Szorc <gregory.szorc@gmail.com>
+# Date 1348281744 25200
+# Node ID df4f8b7f800ff2a4c8a0c8e30575f38d5ea45fcb
+# Parent  fbb9ade686ffdd66ed30a7e9b68d9246409f7389
+Precompile Git progress regular expressions
+
+diff --git a/hggit/git_handler.py b/hggit/git_handler.py
+--- a/hggit/git_handler.py
++++ b/hggit/git_handler.py
+@@ -37,39 +37,42 @@
+ 
+ # Test for git:// and git+ssh:// URI.
+ # Support several URL forms, including separating the
+ # host and path with either a / or : (sepr)
+ RE_GIT_URI = re.compile(
+     r'^(?P<scheme>git([+]ssh)?://)(?P<host>.*?)(:(?P<port>\d+))?'
+     r'(?P<sepr>[:/])(?P<path>.*)$')
+ 
++RE_NEWLINES = re.compile('[\r\n]')
++RE_GIT_PROGRESS = re.compile('\((\d+)/(\d+)\)')
++
+ class GitProgress(object):
+     """convert git server progress strings into mercurial progress"""
+     def __init__(self, ui):
+         self.ui = ui
+ 
+         self.lasttopic = None
+         self.msgbuf = ''
+ 
+     def progress(self, msg):
+         # 'Counting objects: 33640, done.\n'
+         # 'Compressing objects:   0% (1/9955)   \r
+-        msgs = re.split('[\r\n]', self.msgbuf + msg)
++        msgs = RE_NEWLINES.split(self.msgbuf + msg)
+         self.msgbuf = msgs.pop()
+ 
+         for msg in msgs:
+             td = msg.split(':', 1)
+             data = td.pop()
+             if not td:
+                 self.flush(data)
+                 continue
+             topic = td[0]
+ 
+-            m = re.search('\((\d+)/(\d+)\)', data)
++            m = RE_GIT_PROGRESS.search(data)
+             if m:
+                 if self.lasttopic and self.lasttopic != topic:
+                     self.flush()
+                 self.lasttopic = topic
+ 
+                 pos, total = map(int, m.group(1, 2))
+                 util.progress(self.ui, topic, pos, total=total)
+             else:
+
+-- 
+You received this message because you are subscribed to the Google Groups "hg-git" group.
+To post to this group, send email to hg-git@googlegroups.com.
+To unsubscribe from this group, send email to hg-git+unsubscribe@googlegroups.com.
+For more options, visit this group at http://groups.google.com/group/hg-git?hl=en.
+

precompile_git_uri_regular_expression

+# HG changeset patch
+# User Gregory Szorc <gregory.szorc@gmail.com>
+# Date 1348281136 25200
+# Node ID 58a14700666b1eb51d13bee03ce411da149104a7
+# Parent  5ca256907196d908a23dc72bf3230e975565d6e8
+Precompile Git URI regular expression
+
+diff --git a/hggit/git_handler.py b/hggit/git_handler.py
+--- a/hggit/git_handler.py
++++ b/hggit/git_handler.py
+@@ -26,16 +26,23 @@
+ from mercurial import error
+ 
+ import _ssh
+ import util
+ from overlay import overlayrepo
+ 
+ RE_GIT_AUTHOR = re.compile('^(.*?) ?\<(.*?)(?:\>(.*))?$')
+ 
++# Test for git:// and git+ssh:// URI.
++# Support several URL forms, including separating the
++# host and path with either a / or : (sepr)
++RE_GIT_URI = re.compile(
++    r'^(?P<scheme>git([+]ssh)?://)(?P<host>.*?)(:(?P<port>\d+))?'
++    r'(?P<sepr>[:/])(?P<path>.*)$')
++
+ class GitProgress(object):
+     """convert git server progress strings into mercurial progress"""
+     def __init__(self, ui):
+         self.ui = ui
+ 
+         self.lasttopic = None
+         self.msgbuf = ''
+ 
+@@ -1288,24 +1295,17 @@
+         except UnicodeDecodeError:
+             return string.decode('ascii', 'replace').encode('utf-8')
+ 
+     def get_transport_and_path(self, uri):
+         # pass hg's ui.ssh config to dulwich
+         if not issubclass(client.get_ssh_vendor, _ssh.SSHVendor):
+             client.get_ssh_vendor = _ssh.generate_ssh_vendor(self.ui)
+ 
+-        # Test for git:// and git+ssh:// URI.
+-        #  Support several URL forms, including separating the
+-        #  host and path with either a / or : (sepr)
+-        git_pattern = re.compile(
+-            r'^(?P<scheme>git([+]ssh)?://)(?P<host>.*?)(:(?P<port>\d+))?'
+-            r'(?P<sepr>[:/])(?P<path>.*)$'
+-        )
+-        git_match = git_pattern.match(uri)
++        git_match = RE_GIT_URI.match(uri)
+         if git_match:
+             res = git_match.groupdict()
+             transport = client.SSHGitClient if 'ssh' in res['scheme'] else client.TCPGitClient
+             host, port, sepr, path = res['host'], res['port'], res['sepr'], res['path']
+             if sepr == '/':
+                 path = '/' + path
+             # strip trailing slash for heroku-style URLs
+             # ssh+git://git@heroku.com:project.git/
+
+-- 
+You received this message because you are subscribed to the Google Groups "hg-git" group.
+To post to this group, send email to hg-git@googlegroups.com.
+To unsubscribe from this group, send email to hg-git+unsubscribe@googlegroups.com.
+For more options, visit this group at http://groups.google.com/group/hg-git?hl=en.
+

precompile_git_username_sanitizing_regular_expression

+# HG changeset patch
+# User Gregory Szorc <gregory.szorc@gmail.com>
+# Date 1348281417 25200
+# Node ID 1de6cd07221e0d5fb90b30e515891a325574a9fc
+# Parent  58a14700666b1eb51d13bee03ce411da149104a7
+Precompile Git username sanitizing regular expression
+
+diff --git a/hggit/git_handler.py b/hggit/git_handler.py
+--- a/hggit/git_handler.py
++++ b/hggit/git_handler.py
+@@ -26,16 +26,18 @@
+ from mercurial import error
+ 
+ import _ssh
+ import util
+ from overlay import overlayrepo
+ 
+ RE_GIT_AUTHOR = re.compile('^(.*?) ?\<(.*?)(?:\>(.*))?$')
+ 
++RE_GIT_SANITIZE_AUTHOR = re.compile('[<>\n]')
++
+ # Test for git:// and git+ssh:// URI.
+ # Support several URL forms, including separating the
+ # host and path with either a / or : (sepr)
+ RE_GIT_URI = re.compile(
+     r'^(?P<scheme>git([+]ssh)?://)(?P<host>.*?)(:(?P<port>\d+))?'
+     r'(?P<sepr>[:/])(?P<path>.*)$')
+ 
+ class GitProgress(object):
+@@ -430,17 +432,17 @@
+         'john@doe.com'
+         >>> g(' <john@doe.com> ')
+         'john@doe.com'
+         >>> g('    <random<\n<garbage\n>  > > ')
+         'random???garbage?'
+         >>> g('Typo in hgrc >but.hg-git@handles.it.gracefully>')
+         'Typo in hgrc ?but.hg-git@handles.it.gracefully'
+         """
+-        return re.sub('[<>\n]', '?', name.lstrip('< ').rstrip('> '))
++        return RE_GIT_SANITIZE_AUTHOR.sub('?', name.lstrip('< ').rstrip('> '))
+ 
+     def get_git_author(self, ctx):
+         # hg authors might not have emails
+         author = ctx.user()
+ 
+         # see if a translation exists
+         author = self.author_map.get(author, author)
+ 
+
+-- 
+You received this message because you are subscribed to the Google Groups "hg-git" group.
+To post to this group, send email to hg-git@googlegroups.com.
+To unsubscribe from this group, send email to hg-git+unsubscribe@googlegroups.com.
+For more options, visit this group at http://groups.google.com/group/hg-git?hl=en.
+
+optimize_get_git_author
+precompile_git_uri_regular_expression
+precompile_git_username_sanitizing_regular_expression
+precompile_git_author_extra_data_regular_expression
+precompile_git_progress_regular_expressions
+precompile_author_file_regular_expression
+verify_tree_and_parent_objects_are_in_git_repo
+make_get_valid_git_username_email_static
+implement_treetracker_for_incremental_tree_calculation
+test-git-branch-loss.diff
+test-bookmark-workflow.diff
 bookmark-delete.diff

test-bookmark-workflow.diff

+# HG changeset patch
+# Parent a68fb6d66262a79f4dfddc8ec10ba7b313ccef58
+diff -r a68fb6d66262 -r bb5129764b66 tests/test-git-bookmark-workflow.t
+--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
++++ b/tests/test-git-bookmark-workflow.t	Mon Sep 24 23:10:00 2012 -0400
+@@ -0,0 +1,274 @@
++This test demonstrates how Hg works with remote branches with Hg-Git.  Ideally,
++the operations shown here would behave identically when the remote repository
++was a Hg repository.  In practice, some differences are unavoidable, but we
++should try to minimize them.  This test should be maintained in parallel with
++test-hg-bookmark-workflow.t to keep a meaningful comparison.
++
++bail if the user does not have dulwich
++  $ python -c 'import dulwich, dulwich.repo' || exit 80
++
++  $ echo "[extensions]" >> $HGRCPATH
++  $ echo "hggit=$(echo $(dirname $TESTDIR))/hggit" >> $HGRCPATH
++
++  $ GIT_AUTHOR_NAME='test'; export GIT_AUTHOR_NAME
++  $ GIT_AUTHOR_EMAIL='test@example.org'; export GIT_AUTHOR_EMAIL
++  $ GIT_AUTHOR_DATE="2007-01-01 00:00:00 +0000"; export GIT_AUTHOR_DATE
++  $ GIT_COMMITTER_NAME="$GIT_AUTHOR_NAME"; export GIT_COMMITTER_NAME
++  $ GIT_COMMITTER_EMAIL="$GIT_AUTHOR_EMAIL"; export GIT_COMMITTER_EMAIL
++  $ GIT_COMMITTER_DATE="$GIT_AUTHOR_DATE"; export GIT_COMMITTER_DATE
++
++  $ count=10
++  $ gitcommit()
++  > {
++  >     GIT_AUTHOR_DATE="2007-01-01 00:00:$count +0000"
++  >     GIT_COMMITTER_DATE="$GIT_AUTHOR_DATE"
++  >     git commit "$@" >/dev/null 2>/dev/null || echo "git commit error"
++  >     count=`expr $count + 1`
++  > }
++  $ hgcommit()
++  > {
++  >     HGDATE="2007-01-01 00:00:$count +0000"
++  >     hg commit -d "$HGDATE" "$@" >/dev/null 2>/dev/null || echo "hg commit error"
++  >     count=`expr $count + 1`
++  > }
++  $ gitstate()
++  > {
++  >     git log --format="  %h \"%s\" refs:%d" $@
++  > }
++  $ hgstate()
++  > {
++  >     hg log --template "  {rev} \"{desc}\" bookmarks: [{bookmarks}]\n" $@
++  > }
++
++  $ git init -q remoterepo
++  $ cd remoterepo
++  $ git config --add receive.denyCurrentBranch false
++  $ echo alpha > alpha && git add alpha && gitcommit -m 'add alpha'
++  $ echo beta > beta && git add beta && gitcommit -m 'add beta'
++  $ git branch b1
++  $ echo gamma > gamma && git add gamma && gitcommit -m 'add gamma'
++  $ echo delta > delta && git add delta && gitcommit -m 'add delta'
++  $ gitstate
++    55b133e "add delta" refs: (HEAD, master)
++    d338971 "add gamma" refs:
++    9497a4e "add beta" refs: (b1)
++    7eeab2e "add alpha" refs:
++  $ cd ..
++
++Cloning transfers all bookmarks from remote to local
++  $ hg clone -q remoterepo localrepo
++  $ cd localrepo
++  $ hgstate
++    3 "add delta" bookmarks: [master]
++    2 "add gamma" bookmarks: []
++    1 "add beta" bookmarks: [b1]
++    0 "add alpha" bookmarks: []
++
++No changes
++  $ hg outgoing
++  comparing with $TESTTMP/remoterepo
++  searching for changes
++  no changes found
++  [1]
++  $ hg outgoing -B
++  remote doesn't support bookmarks
++  $ hg push
++  pushing to $TESTTMP/remoterepo
++  creating and sending data
++      default::refs/heads/b1 => GIT:9497a4ee
++
++Changed bookmarks, but not revs
++  $ hg bookmark -fr 2 b1
++  $ hg bookmark -r 0 b2
++  $ hgstate
++    3 "add delta" bookmarks: [master]
++    2 "add gamma" bookmarks: [b1]
++    1 "add beta" bookmarks: []
++    0 "add alpha" bookmarks: [b2]
++  $ hg outgoing
++  comparing with $TESTTMP/remoterepo
++  searching for changes
++  no changes found
++  [1]
++TODO why isn't b1 showing up here?
++Because that's what outgoing currently does
++FUJIWARA has an outstanding patch to change this
++  $ hg outgoing -B
++  remote doesn't support bookmarks
++
++Calling default push updates the shared bookmark, but doesn't share new
++bookmarks
++  $ hg push
++  pushing to $TESTTMP/remoterepo
++  creating and sending data
++      default::refs/heads/b2 => GIT:7eeab2ea
++      default::refs/heads/b1 => GIT:d338971a
++  $ GIT_DIR=../remoterepo/.git gitstate
++    55b133e "add delta" refs: (HEAD, master)
++    d338971 "add gamma" refs: (b1)
++    9497a4e "add beta" refs:
++    7eeab2e "add alpha" refs: (b2)
++
++Calling push -B for the new bookmark shares it, as well as updating other
++shared bookmarks
++  $ hg bookmark -fr 3 b1
++  $ hgstate
++    3 "add delta" bookmarks: [b1 master]
++    2 "add gamma" bookmarks: []
++    1 "add beta" bookmarks: []
++    0 "add alpha" bookmarks: [b2]
++  $ hg push -B b2
++  pushing to $TESTTMP/remoterepo
++  creating and sending data
++  no changes found
++  exporting bookmark b2
++  updating bookmark b2 failed!
++  [1]
++  $ GIT_DIR=../remoterepo/.git gitstate
++    55b133e "add delta" refs: (HEAD, master)
++    d338971 "add gamma" refs: (b1)
++    9497a4e "add beta" refs:
++    7eeab2e "add alpha" refs: (b2)
++
++Changed bookmarks and revs; diverged
++  $ cd ..
++  $ cd remoterepo
++  $ git checkout master
++  Already on 'master'
++  $ echo epsilon > epsilon && git add epsilon && gitcommit -m 'add epsilon'
++  $ gitstate
++    fcfd2c0 "add epsilon" refs: (HEAD, master)
++    55b133e "add delta" refs:
++    d338971 "add gamma" refs: (b1)
++    9497a4e "add beta" refs:
++    7eeab2e "add alpha" refs: (b2)
++  $ cd ../localrepo
++  $ hg update master
++  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
++  $ echo zeta > zeta && hg add zeta && hgcommit -m 'add zeta'
++  $ hgstate
++    4 "add zeta" bookmarks: [master]
++    3 "add delta" bookmarks: [b1]
++    2 "add gamma" bookmarks: []
++    1 "add beta" bookmarks: []
++    0 "add alpha" bookmarks: [b2]
++  $ hg outgoing
++  comparing with $TESTTMP/remoterepo
++  exporting hg objects to git
++  abort: refs/heads/master changed on the server, please pull and merge before pushing
++  [255]
++  $ hg outgoing -B
++  remote doesn't support bookmarks
++  $ hg push
++  pushing to $TESTTMP/remoterepo
++  creating and sending data
++  abort: refs/heads/master changed on the server, please pull and merge before pushing
++  [255]
++  $ hg pull
++  pulling from $TESTTMP/remoterepo
++  importing git objects into hg
++  (run 'hg update' to get a working copy)
++  $ hgstate
++    5 "add epsilon" bookmarks: []
++    4 "add zeta" bookmarks: [master]
++    3 "add delta" bookmarks: [b1]
++    2 "add gamma" bookmarks: []
++    1 "add beta" bookmarks: []
++    0 "add alpha" bookmarks: [b2]
++  $ hg merge master@default && hg commit -m 'Merge master with upstream'
++  hg: parse error at 6: syntax error
++  [255]
++  $ hgstate
++    5 "add epsilon" bookmarks: []
++    4 "add zeta" bookmarks: [master]
++    3 "add delta" bookmarks: [b1]
++    2 "add gamma" bookmarks: []
++    1 "add beta" bookmarks: []
++    0 "add alpha" bookmarks: [b2]
++  $ hg push
++  pushing to $TESTTMP/remoterepo
++  creating and sending data
++  abort: pushing refs/heads/master overwrites 0e69043b5c70
++  [255]
++  $ GIT_DIR=../remoterepo/.git gitstate
++    fcfd2c0 "add epsilon" refs: (HEAD, master)
++    55b133e "add delta" refs:
++    d338971 "add gamma" refs: (b1)
++    9497a4e "add beta" refs:
++    7eeab2e "add alpha" refs: (b2)
++
++Changed revs but not bookmarks
++#  $ hg update 4
++#  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
++#  $ echo eta > eta && hg add eta && hgcommit -m 'add eta'
++#  $ hgstate
++#    7 "add eta" bookmarks: []
++#    6 "Merge master with upstream" bookmarks: [master]
++#    5 "add epsilon" bookmarks: []
++#    4 "add zeta" bookmarks: []
++#    3 "add delta" bookmarks: [b1]
++#    2 "add gamma" bookmarks: []
++#    1 "add beta" bookmarks: []
++#    0 "add alpha" bookmarks: [b2]
++#  $ hg outgoing
++#  comparing with $TESTTMP/remoterepo
++#  searching for changes
++#  changeset:   7:34b29aa986b2
++#  tag:         tip
++#  parent:      4:2af4720080cd
++#  user:        test
++#  date:        Mon Jan 01 00:00:16 2007 +0000
++#  summary:     add eta
++#  
++#  $ hg outgoing -B
++#  comparing with $TESTTMP/remoterepo
++#  searching for changed bookmarks
++#  no changed bookmarks found
++#  [1]
++#  $ hg push
++#  pushing to $TESTTMP/remoterepo
++#  searching for changes
++#  abort: push creates new remote head 34b29aa986b2!
++#  (did you forget to merge? use push -f to force)
++#  [255]
++#  $ hg push -f
++#  pushing to $TESTTMP/remoterepo
++#  searching for changes
++#  adding changesets
++#  adding manifests
++#  adding file changes
++#  added 1 changesets with 1 changes to 1 files (+1 heads)
++#  $ GIT_DIR=../remoterepo/.git gitstate
++#    7 "add eta" bookmarks: []
++#    6 "Merge master with upstream" bookmarks: [master]
++#    5 "add zeta" bookmarks: []
++#    4 "add epsilon" bookmarks: []
++#    3 "add delta" bookmarks: [b1]
++#    2 "add gamma" bookmarks: []
++#    1 "add beta" bookmarks: []
++#    0 "add alpha" bookmarks: [b2]
++
++#  $ cd hgrepo1
++pull with no options specified, nothing to pull
++#  $ hg pull
++pull -B with bookmark that doesn't exist
++#  $ hg pull -B missingbookmark
++
++TODO: more
++clone transfers all bookmarks
++pull with no opts pulls all remote bookmarks
++pull states (add/no-change/update/diverge)
++bookmark change but no rev change
++TODO -B
++TODO -r non-bookmark
++TODO -r bookmark
++push with no opts pushes all revs, but only bookmarks present local and remote?
++push with -r; does it push bookmarks?
++push -B (add/no-change/update/diverge)
++TODO incoming
++TODO incoming -B
++TODO outgoing
++TODO outgoing -B
++
++  $ echo moo
++  moo
+diff -r a68fb6d66262 -r bb5129764b66 tests/test-hg-bookmark-workflow.t
+--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
++++ b/tests/test-hg-bookmark-workflow.t	Mon Sep 24 23:10:00 2012 -0400
+@@ -0,0 +1,284 @@
++This test demonstrates how Hg works with bookmarks WITHOUT Hg-Git.  Ideally,
++the operations shown here would behave identically when the remote repository
++was a Git repository via Hg-Git.  In practice, some differences are
++unavoidable, but we should try to minimize them.  This test should be
++maintained in parallel with test-git-bookmark-workflow.t to keep a meaningful
++comparison.
++
++This test should not bother testing the behavior of bookmark creation,
++deletion, activation, deactivation, etc.  These behaviors, while important to
++the end user, don't vary at all when Hg-Git is in use.  Only the synchonization
++of bookmarks should be considered "under test", and mutation of bookmarks
++locally is only to provide a test fixture.
++
++  $ count=10
++  $ hgcommit()
++  > {
++  >     HGDATE="2007-01-01 00:00:$count +0000"
++  >     hg commit -d "$HGDATE" "$@" >/dev/null 2>/dev/null || echo "hg commit error"
++  >     count=`expr $count + 1`
++  > }
++  $ hgstate()
++  > {
++  >     hg log --template "  {rev} \"{desc}\" bookmarks: [{bookmarks}]\n" $@
++  > }
++
++  $ hg init remoterepo
++  $ cd remoterepo
++  $ hg bookmark master
++  $ echo alpha > alpha && hg add alpha && hgcommit -m 'add alpha'
++  $ echo beta > beta && hg add beta && hgcommit -m 'add beta'
++  $ echo gamma > gamma && hg add gamma && hgcommit -m 'add gamma'
++  $ echo delta > delta && hg add delta && hgcommit -m 'add delta'
++  $ hg bookmark -r 1 b1
++  $ hgstate
++    3 "add delta" bookmarks: [master]
++    2 "add gamma" bookmarks: []
++    1 "add beta" bookmarks: [b1]
++    0 "add alpha" bookmarks: []
++  $ cd ..
++
++Cloning transfers all bookmarks from remote to local
++  $ hg clone -q remoterepo localrepo
++  $ cd localrepo
++  $ hgstate
++    3 "add delta" bookmarks: [master]
++    2 "add gamma" bookmarks: []
++    1 "add beta" bookmarks: [b1]
++    0 "add alpha" bookmarks: []
++
++No changes
++  $ hg outgoing
++  comparing with $TESTTMP/remoterepo
++  searching for changes
++  no changes found
++  [1]
++  $ hg outgoing -B
++  comparing with $TESTTMP/remoterepo
++  searching for changed bookmarks
++  no changed bookmarks found
++  [1]
++  $ hg push
++  pushing to $TESTTMP/remoterepo
++  searching for changes
++  no changes found
++  [1]
++
++Changed bookmarks, but not revs
++  $ hg bookmark -fr 2 b1
++  $ hg bookmark -r 0 b2
++  $ hgstate
++    3 "add delta" bookmarks: [master]
++    2 "add gamma" bookmarks: [b1]
++    1 "add beta" bookmarks: []
++    0 "add alpha" bookmarks: [b2]
++  $ hg outgoing
++  comparing with $TESTTMP/remoterepo
++  searching for changes
++  no changes found
++  [1]
++TODO why isn't b1 showing up here?
++Because that's what outgoing currently does
++FUJIWARA has an outstanding patch to change this
++  $ hg outgoing -B
++  comparing with $TESTTMP/remoterepo
++  searching for changed bookmarks
++     b2                        0221c246a567
++
++Calling default push updates the shared bookmark, but doesn't share new
++bookmarks
++  $ hg push
++  pushing to $TESTTMP/remoterepo
++  searching for changes
++  no changes found
++  updating bookmark b1
++  [1]
++  $ hgstate -R ../remoterepo
++    3 "add delta" bookmarks: [master]
++    2 "add gamma" bookmarks: [b1]
++    1 "add beta" bookmarks: []
++    0 "add alpha" bookmarks: []
++
++Calling push -B for the new bookmark shares it, as well as updating other
++shared bookmarks
++  $ hg bookmark -fr 3 b1
++  $ hgstate
++    3 "add delta" bookmarks: [b1 master]
++    2 "add gamma" bookmarks: []
++    1 "add beta" bookmarks: []
++    0 "add alpha" bookmarks: [b2]
++  $ hg push -B b2
++  pushing to $TESTTMP/remoterepo
++  searching for changes
++  no changes found
++  updating bookmark b1
++  exporting bookmark b2
++  [1]
++  $ hgstate -R ../remoterepo
++    3 "add delta" bookmarks: [b1 master]
++    2 "add gamma" bookmarks: []
++    1 "add beta" bookmarks: []
++    0 "add alpha" bookmarks: [b2]
++
++Changed bookmarks and revs; diverged
++  $ cd ..
++  $ cd remoterepo
++  $ hg update master
++  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
++  $ echo epsilon > epsilon && hg add epsilon && hgcommit -m 'add epsilon'
++  $ hgstate
++    4 "add epsilon" bookmarks: [master]
++    3 "add delta" bookmarks: [b1]
++    2 "add gamma" bookmarks: []
++    1 "add beta" bookmarks: []
++    0 "add alpha" bookmarks: [b2]
++  $ cd ../localrepo
++  $ hg update master
++  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
++  $ echo zeta > zeta && hg add zeta && hgcommit -m 'add zeta'
++  $ hgstate
++    4 "add zeta" bookmarks: [master]
++    3 "add delta" bookmarks: [b1]
++    2 "add gamma" bookmarks: []
++    1 "add beta" bookmarks: []
++    0 "add alpha" bookmarks: [b2]
++  $ hg outgoing
++  comparing with $TESTTMP/remoterepo
++  searching for changes
++  changeset:   4:2af4720080cd
++  bookmark:    master
++  tag:         tip
++  user:        test
++  date:        Mon Jan 01 00:00:15 2007 +0000
++  summary:     add zeta
++  
++  $ hg outgoing -B
++  comparing with $TESTTMP/remoterepo
++  searching for changed bookmarks
++  no changed bookmarks found
++  [1]
++  $ hg push
++  pushing to $TESTTMP/remoterepo
++  searching for changes
++  abort: push creates new remote head 2af4720080cd!
++  (you should pull and merge or use push -f to force)
++  [255]
++  $ hg pull
++  pulling from $TESTTMP/remoterepo
++  searching for changes
++  adding changesets
++  adding manifests
++  adding file changes
++  added 1 changesets with 1 changes to 1 files (+1 heads)
++  divergent bookmark master stored as master@default
++  (run 'hg heads' to see heads, 'hg merge' to merge)
++  $ hgstate
++    5 "add epsilon" bookmarks: [master@default]
++    4 "add zeta" bookmarks: [master]
++    3 "add delta" bookmarks: [b1]
++    2 "add gamma" bookmarks: []
++    1 "add beta" bookmarks: []
++    0 "add alpha" bookmarks: [b2]
++  $ hg merge master@default && hg commit -m 'Merge master with upstream'
++  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
++  (branch merge, don't forget to commit)
++  $ hgstate
++    6 "Merge master with upstream" bookmarks: [master]
++    5 "add epsilon" bookmarks: []
++    4 "add zeta" bookmarks: []
++    3 "add delta" bookmarks: [b1]
++    2 "add gamma" bookmarks: []
++    1 "add beta" bookmarks: []
++    0 "add alpha" bookmarks: [b2]
++  $ hg push
++  pushing to $TESTTMP/remoterepo
++  searching for changes
++  adding changesets
++  adding manifests
++  adding file changes
++  added 2 changesets with 1 changes to 1 files
++  updating bookmark master
++  $ hgstate -R ../remoterepo
++    6 "Merge master with upstream" bookmarks: [master]
++    5 "add zeta" bookmarks: []
++    4 "add epsilon" bookmarks: []
++    3 "add delta" bookmarks: [b1]
++    2 "add gamma" bookmarks: []
++    1 "add beta" bookmarks: []
++    0 "add alpha" bookmarks: [b2]
++
++Changed revs but not bookmarks
++  $ hg update 4
++  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
++  $ echo eta > eta && hg add eta && hgcommit -m 'add eta'
++  $ hgstate
++    7 "add eta" bookmarks: []
++    6 "Merge master with upstream" bookmarks: [master]
++    5 "add epsilon" bookmarks: []
++    4 "add zeta" bookmarks: []
++    3 "add delta" bookmarks: [b1]
++    2 "add gamma" bookmarks: []
++    1 "add beta" bookmarks: []
++    0 "add alpha" bookmarks: [b2]
++  $ hg outgoing
++  comparing with $TESTTMP/remoterepo
++  searching for changes
++  changeset:   7:34b29aa986b2
++  tag:         tip
++  parent:      4:2af4720080cd
++  user:        test
++  date:        Mon Jan 01 00:00:16 2007 +0000
++  summary:     add eta
++  
++  $ hg outgoing -B
++  comparing with $TESTTMP/remoterepo
++  searching for changed bookmarks
++  no changed bookmarks found
++  [1]
++  $ hg push
++  pushing to $TESTTMP/remoterepo
++  searching for changes
++  abort: push creates new remote head 34b29aa986b2!
++  (did you forget to merge? use push -f to force)
++  [255]
++  $ hg push -f
++  pushing to $TESTTMP/remoterepo
++  searching for changes
++  adding changesets
++  adding manifests
++  adding file changes
++  added 1 changesets with 1 changes to 1 files (+1 heads)
++  $ hgstate -R ../remoterepo
++    7 "add eta" bookmarks: []
++    6 "Merge master with upstream" bookmarks: [master]
++    5 "add zeta" bookmarks: []
++    4 "add epsilon" bookmarks: []
++    3 "add delta" bookmarks: [b1]
++    2 "add gamma" bookmarks: []
++    1 "add beta" bookmarks: []
++    0 "add alpha" bookmarks: [b2]
++
++#  $ cd hgrepo1
++pull with no options specified, nothing to pull
++#  $ hg pull
++pull -B with bookmark that doesn't exist
++#  $ hg pull -B missingbookmark
++
++TODO: more
++clone transfers all bookmarks
++pull with no opts pulls all remote bookmarks
++pull states (add/no-change/update/diverge)
++bookmark change but no rev change
++TODO -B
++TODO -r non-bookmark
++TODO -r bookmark
++push with no opts pushes all revs, but only bookmarks present local and remote?
++push with -r; does it push bookmarks?
++push -B (add/no-change/update/diverge)
++TODO incoming
++TODO incoming -B
++TODO outgoing
++TODO outgoing -B
++
++  $ echo moo
++  moo

test-git-branch-loss.diff

+# HG changeset patch
+# Parent 878ae1d1bd73f5e079f6990b4a93e9a9f5405fb8
+diff -r 878ae1d1bd73 -r a68fb6d66262 tests/test-git-branch-loss.t
+--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
++++ b/tests/test-git-branch-loss.t	Mon Sep 24 23:09:28 2012 -0400
+@@ -0,0 +1,434 @@
++# Fails for some reason, need to investigate
++#   $ "$TESTDIR/hghave" git || exit 80
++
++bail if the user does not have dulwich
++  $ python -c 'import dulwich, dulwich.repo' || exit 80
++
++  $ echo "[extensions]" >> $HGRCPATH
++  $ echo "hggit=$(echo $(dirname $TESTDIR))/hggit" >> $HGRCPATH
++  $ echo 'hgext.graphlog =' >> $HGRCPATH
++
++  $ GIT_AUTHOR_NAME='test'; export GIT_AUTHOR_NAME
++  $ GIT_AUTHOR_EMAIL='test@example.org'; export GIT_AUTHOR_EMAIL
++  $ GIT_AUTHOR_DATE="2007-01-01 00:00:00 +0000"; export GIT_AUTHOR_DATE
++  $ GIT_COMMITTER_NAME="$GIT_AUTHOR_NAME"; export GIT_COMMITTER_NAME
++  $ GIT_COMMITTER_EMAIL="$GIT_AUTHOR_EMAIL"; export GIT_COMMITTER_EMAIL
++  $ GIT_COMMITTER_DATE="$GIT_AUTHOR_DATE"; export GIT_COMMITTER_DATE
++
++  $ count=10
++  $ commit()
++  > {
++  >     GIT_AUTHOR_DATE="2007-01-01 00:00:$count +0000"
++  >     GIT_COMMITTER_DATE="$GIT_AUTHOR_DATE"
++  >     git commit "$@" >/dev/null 2>/dev/null || echo "git commit error" | sed 's/, 0 deletions(-)//'
++  >     count=`expr $count + 1`
++  > }
++
++  $ mkdir gitrepo
++  $ cd gitrepo
++  $ git init
++  Initialized empty Git repository in $TESTTMP/gitrepo/.git/
++  $ echo alpha > alpha
++  $ git add alpha
++  $ commit -m 'add alpha'
++
++  $ git checkout -b beta 2>&1 | sed s/\'/\"/g
++  Switched to a new branch "beta"
++  $ echo beta > beta
++  $ git add beta
++  $ commit -m 'add beta'
++
++  $ git checkout master 2>&1 | sed s/\'/\"/g
++  Switched to branch "master"
++  $ echo gamma > gamma
++  $ git add gamma
++  $ commit -m 'add gamma'
++
++  $ git checkout -b develop 2>&1 | sed s/\'/\"/g
++  Switched to a new branch "develop"
++  $ echo dev > dev
++  $ git add dev
++  $ commit -m 'add dev'
++
++  $ git checkout master 2>&1 | sed s/\'/\"/g
++  Switched to branch "master"
++  $ git merge beta | sed "s/the '//;s/' strategy//" | sed 's/^Merge.*recursive.*$/Merge successful/' | sed 's/files/file/;s/insertions/insertion/;s/, 0 deletions.*//' | sed 's/|  */| /'
++  Merge successful
++   beta | 1 +
++   1 file changed, 1 insertion(+)
++   create mode 100644 beta
++  $ git log --pretty=medium --all
++  commit 45abb370c52ac9e0347ebb208ee09f6dd482c227
++  Author: test <test@example.org>
++  Date:   Mon Jan 1 00:00:13 2007 +0000
++  
++      add dev
++  
++  commit 4201a42f8acb6e76ad7e3f7d163b6720f41ce588
++  Merge: e5023f9 9497a4e
++  Author: test <test@example.org>
++  Date:   Mon Jan 1 00:00:13 2007 +0000
++  
++      Merge branch 'beta'
++  
++  commit e5023f9e5cb24fdcec7b6c127cec45d8888e35a9
++  Author: test <test@example.org>
++  Date:   Mon Jan 1 00:00:12 2007 +0000
++  
++      add gamma
++  
++  commit 9497a4ee62e16ee641860d7677cdb2589ea15554
++  Author: test <test@example.org>
++  Date:   Mon Jan 1 00:00:11 2007 +0000
++  
++      add beta
++  
++  commit 7eeab2ea75ec1ac0ff3d500b5b6f8a3447dd7c03
++  Author: test <test@example.org>
++  Date:   Mon Jan 1 00:00:10 2007 +0000
++  
++      add alpha
++  $ git branch -a
++    beta
++    develop
++  * master
++  $ cd ..
++
++  $ hg clone gitrepo hgrepo | grep -v '^updating'
++  importing git objects into hg
++  3 files updated, 0 files merged, 0 files removed, 0 files unresolved
++  $ cd hgrepo
++  $ hg update develop
++  1 files updated, 0 files merged, 1 files removed, 0 files unresolved
++  $ echo dev2 >> dev
++  $ hg commit -m "change dev"
++  $ hg log --graph
++  @  changeset:   5:89ef27525c81
++  |  bookmark:    develop
++  |  tag:         tip
++  |  parent:      3:cef87f787c94
++  |  user:        test
++  |  date:        Thu Jan 01 00:00:00 1970 +0000
++  |  summary:     change dev
++  |
++  | o    changeset:   4:d00ec1364b84
++  | |\   bookmark:    master
++  | | |  tag:         default/master
++  | | |  parent:      2:37c124f2d0a0
++  | | |  parent:      1:7bcd915dc873
++  | | |  user:        test <test@example.org>
++  | | |  date:        Mon Jan 01 00:00:13 2007 +0000
++  | | |  summary:     Merge branch 'beta'
++  | | |
++  o---+  changeset:   3:cef87f787c94
++    | |  tag:         default/develop
++   / /   user:        test <test@example.org>
++  | |    date:        Mon Jan 01 00:00:13 2007 +0000
++  | |    summary:     add dev
++  | |
++  | o  changeset:   2:37c124f2d0a0
++  | |  parent:      0:3442585be8a6
++  | |  user:        test <test@example.org>
++  | |  date:        Mon Jan 01 00:00:12 2007 +0000
++  | |  summary:     add gamma
++  | |
++  o |  changeset:   1:7bcd915dc873
++  |/   bookmark:    beta
++  |    tag:         default/beta
++  |    user:        test <test@example.org>
++  |    date:        Mon Jan 01 00:00:11 2007 +0000
++  |    summary:     add beta
++  |
++  o  changeset:   0:3442585be8a6
++     user:        test <test@example.org>
++     date:        Mon Jan 01 00:00:10 2007 +0000
++     summary:     add alpha
++  
++  $ hg bookmarks
++     beta                      1:7bcd915dc873
++   * develop                   5:89ef27525c81
++     master                    4:d00ec1364b84
++  $ hg tags -v
++  tip                                5:89ef27525c81
++  default/master                     4:d00ec1364b84
++  default/develop                    3:cef87f787c94
++  default/beta                       1:7bcd915dc873
++  $ hg heads
++  changeset:   5:89ef27525c81
++  bookmark:    develop
++  tag:         tip
++  parent:      3:cef87f787c94
++  user:        test
++  date:        Thu Jan 01 00:00:00 1970 +0000
++  summary:     change dev
++  
++  changeset:   4:d00ec1364b84
++  bookmark:    master
++  tag:         default/master
++  parent:      2:37c124f2d0a0
++  parent:      1:7bcd915dc873
++  user:        test <test@example.org>
++  date:        Mon Jan 01 00:00:13 2007 +0000
++  summary:     Merge branch 'beta'
++  
++  $ hg gclear
++  clearing out the git cache data
++  $ hg log --graph
++  @  changeset:   5:89ef27525c81
++  |  bookmark:    develop
++  |  tag:         tip
++  |  parent:      3:cef87f787c94
++  |  user:        test
++  |  date:        Thu Jan 01 00:00:00 1970 +0000
++  |  summary:     change dev
++  |
++  | o    changeset:   4:d00ec1364b84
++  | |\   bookmark:    master
++  | | |  tag:         default/master
++  | | |  parent:      2:37c124f2d0a0
++  | | |  parent:      1:7bcd915dc873
++  | | |  user:        test <test@example.org>
++  | | |  date:        Mon Jan 01 00:00:13 2007 +0000
++  | | |  summary:     Merge branch 'beta'
++  | | |
++  o---+  changeset:   3:cef87f787c94
++    | |  tag:         default/develop
++   / /   user:        test <test@example.org>
++  | |    date:        Mon Jan 01 00:00:13 2007 +0000
++  | |    summary:     add dev
++  | |
++  | o  changeset:   2:37c124f2d0a0
++  | |  parent:      0:3442585be8a6
++  | |  user:        test <test@example.org>
++  | |  date:        Mon Jan 01 00:00:12 2007 +0000
++  | |  summary:     add gamma
++  | |
++  o |  changeset:   1:7bcd915dc873
++  |/   bookmark:    beta
++  |    tag:         default/beta
++  |    user:        test <test@example.org>
++  |    date:        Mon Jan 01 00:00:11 2007 +0000
++  |    summary:     add beta
++  |
++  o  changeset:   0:3442585be8a6
++     user:        test <test@example.org>
++     date:        Mon Jan 01 00:00:10 2007 +0000
++     summary:     add alpha
++  
++  $ hg bookmarks
++     beta                      1:7bcd915dc873
++   * develop                   5:89ef27525c81
++     master                    4:d00ec1364b84
++  $ hg tags -v
++  tip                                5:89ef27525c81
++  default/master                     4:d00ec1364b84
++  default/develop                    3:cef87f787c94
++  default/beta                       1:7bcd915dc873
++  $ hg heads
++  changeset:   5:89ef27525c81
++  bookmark:    develop
++  tag:         tip
++  parent:      3:cef87f787c94
++  user:        test
++  date:        Thu Jan 01 00:00:00 1970 +0000
++  summary:     change dev
++  
++  changeset:   4:d00ec1364b84
++  bookmark:    master
++  tag:         default/master
++  parent:      2:37c124f2d0a0
++  parent:      1:7bcd915dc873
++  user:        test <test@example.org>
++  date:        Mon Jan 01 00:00:13 2007 +0000
++  summary:     Merge branch 'beta'
++  
++  $ hg pull ../gitrepo
++  pulling from ../gitrepo
++  exporting hg objects to git
++  (run 'hg heads .' to see heads, 'hg merge' to merge)
++  $ hg log --graph
++  @  changeset:   5:89ef27525c81
++  |  bookmark:    develop
++  |  tag:         tip
++  |  parent:      3:cef87f787c94
++  |  user:        test
++  |  date:        Thu Jan 01 00:00:00 1970 +0000
++  |  summary:     change dev
++  |
++  | o    changeset:   4:d00ec1364b84
++  | |\   bookmark:    master
++  | | |  tag:         default/master
++  | | |  parent:      2:37c124f2d0a0
++  | | |  parent:      1:7bcd915dc873
++  | | |  user:        test <test@example.org>
++  | | |  date:        Mon Jan 01 00:00:13 2007 +0000
++  | | |  summary:     Merge branch 'beta'
++  | | |
++  o---+  changeset:   3:cef87f787c94
++    | |  tag:         default/develop
++   / /   user:        test <test@example.org>
++  | |    date:        Mon Jan 01 00:00:13 2007 +0000
++  | |    summary:     add dev
++  | |
++  | o  changeset:   2:37c124f2d0a0
++  | |  parent:      0:3442585be8a6
++  | |  user:        test <test@example.org>
++  | |  date:        Mon Jan 01 00:00:12 2007 +0000
++  | |  summary:     add gamma
++  | |
++  o |  changeset:   1:7bcd915dc873
++  |/   bookmark:    beta
++  |    tag:         default/beta
++  |    user:        test <test@example.org>
++  |    date:        Mon Jan 01 00:00:11 2007 +0000
++  |    summary:     add beta
++  |
++  o  changeset:   0:3442585be8a6
++     user:        test <test@example.org>
++     date:        Mon Jan 01 00:00:10 2007 +0000
++     summary:     add alpha
++  
++  $ hg bookmarks
++     beta                      1:7bcd915dc873
++   * develop                   5:89ef27525c81
++     master                    4:d00ec1364b84
++  $ hg tags -v
++  tip                                5:89ef27525c81
++  default/master                     4:d00ec1364b84
++  default/develop                    3:cef87f787c94
++  default/beta                       1:7bcd915dc873
++  $ hg heads
++  changeset:   5:89ef27525c81
++  bookmark:    develop
++  tag:         tip
++  parent:      3:cef87f787c94
++  user:        test
++  date:        Thu Jan 01 00:00:00 1970 +0000
++  summary:     change dev
++  
++  changeset:   4:d00ec1364b84
++  bookmark:    master
++  tag:         default/master
++  parent:      2:37c124f2d0a0
++  parent:      1:7bcd915dc873
++  user:        test <test@example.org>
++  date:        Mon Jan 01 00:00:13 2007 +0000
++  summary:     Merge branch 'beta'
++  
++  $ hg push ../gitrepo
++  pushing to ../gitrepo
++  creating and sending data
++  $ hg log --graph
++  @  changeset:   5:89ef27525c81
++  |  bookmark:    develop
++  |  tag:         tip
++  |  parent:      3:cef87f787c94
++  |  user:        test
++  |  date:        Thu Jan 01 00:00:00 1970 +0000
++  |  summary:     change dev
++  |
++  | o    changeset:   4:d00ec1364b84
++  | |\   bookmark:    master
++  | | |  tag:         default/master
++  | | |  parent:      2:37c124f2d0a0
++  | | |  parent:      1:7bcd915dc873
++  | | |  user:        test <test@example.org>
++  | | |  date:        Mon Jan 01 00:00:13 2007 +0000
++  | | |  summary:     Merge branch 'beta'
++  | | |
++  o---+  changeset:   3:cef87f787c94
++    | |  tag:         default/develop
++   / /   user:        test <test@example.org>
++  | |    date:        Mon Jan 01 00:00:13 2007 +0000
++  | |    summary:     add dev
++  | |
++  | o  changeset:   2:37c124f2d0a0
++  | |  parent:      0:3442585be8a6
++  | |  user:        test <test@example.org>
++  | |  date:        Mon Jan 01 00:00:12 2007 +0000
++  | |  summary:     add gamma
++  | |
++  o |  changeset:   1:7bcd915dc873
++  |/   bookmark:    beta
++  |    tag:         default/beta
++  |    user:        test <test@example.org>
++  |    date:        Mon Jan 01 00:00:11 2007 +0000
++  |    summary:     add beta
++  |
++  o  changeset:   0:3442585be8a6
++     user:        test <test@example.org>
++     date:        Mon Jan 01 00:00:10 2007 +0000
++     summary:     add alpha
++  
++  $ hg bookmarks
++     beta                      1:7bcd915dc873
++   * develop                   5:89ef27525c81
++     master                    4:d00ec1364b84
++  $ hg tags -v
++  tip                                5:89ef27525c81
++  default/master                     4:d00ec1364b84
++  default/develop                    3:cef87f787c94
++  default/beta                       1:7bcd915dc873
++  $ hg heads
++  changeset:   5:89ef27525c81
++  bookmark:    develop
++  tag:         tip
++  parent:      3:cef87f787c94
++  user:        test
++  date:        Thu Jan 01 00:00:00 1970 +0000
++  summary:     change dev
++  
++  changeset:   4:d00ec1364b84
++  bookmark:    master
++  tag:         default/master
++  parent:      2:37c124f2d0a0
++  parent:      1:7bcd915dc873
++  user:        test <test@example.org>
++  date:        Mon Jan 01 00:00:13 2007 +0000
++  summary:     Merge branch 'beta'
++  
++  $ cd ..
++
++  $ cd gitrepo
++  $ git log --pretty=medium --all
++  commit 4201a42f8acb6e76ad7e3f7d163b6720f41ce588
++  Merge: e5023f9 9497a4e
++  Author: test <test@example.org>
++  Date:   Mon Jan 1 00:00:13 2007 +0000
++  
++      Merge branch 'beta'
++  
++  commit e5023f9e5cb24fdcec7b6c127cec45d8888e35a9
++  Author: test <test@example.org>
++  Date:   Mon Jan 1 00:00:12 2007 +0000
++  
++      add gamma
++  
++  commit 9497a4ee62e16ee641860d7677cdb2589ea15554
++  Author: test <test@example.org>
++  Date:   Mon Jan 1 00:00:11 2007 +0000
++  
++      add beta
++  
++  commit 7eeab2ea75ec1ac0ff3d500b5b6f8a3447dd7c03
++  Author: test <test@example.org>
++  Date:   Mon Jan 1 00:00:10 2007 +0000
++  
++      add alpha
++  
++  commit c15cff1d053993efc9f84b44e7852ea12187bda1
++  Author: test <none@none>
++  Date:   Thu Jan 1 00:00:00 1970 +0000
++  
++      change dev
++  
++  commit 45abb370c52ac9e0347ebb208ee09f6dd482c227
++  Author: test <test@example.org>
++  Date:   Mon Jan 1 00:00:13 2007 +0000
++  
++      add dev
++  $ git branch -a
++    beta
++    develop
++  * master
++  $ cd ..

verify_tree_and_parent_objects_are_in_git_repo

+# HG changeset patch
+# User Gregory Szorc <gregory.szorc@gmail.com>
+# Date 1348284386 25200
+# Node ID 2db03c124dde9c84de1006526f497d867094a231
+# Parent  95b937230a1352d738c81bbe1e3b3a031e071956
+Verify tree and parent objects are in Git repo
+
+When exporting Git commits, verify that the tree and parents objects
+exist in the repository before allowing the commit to be exported. If a
+tree or parent commit is missing, then the repository is not valid and
+the export should not be allowed.
+
+diff --git a/hggit/git_handler.py b/hggit/git_handler.py
+--- a/hggit/git_handler.py
++++ b/hggit/git_handler.py
+@@ -379,24 +379,32 @@
+             commit.commit_time = commit.author_time
+             commit.commit_timezone = commit.author_timezone
+ 
+         commit.parents = []
+         for parent in self.get_git_parents(ctx):
+             hgsha = hex(parent.node())
+             git_sha = self.map_git_get(hgsha)
+             if git_sha:
++                if git_sha not in self.git.object_store:
++                    raise hgutil.Abort(_('Parent SHA-1 not present in Git'
++                                         'repo: %s' % git_sha))
++
+                 commit.parents.append(git_sha)
+ 
+         commit.message = self.get_git_message(ctx)
+ 
+         if 'encoding' in extra:
+             commit.encoding = extra['encoding']
+ 
+         tree_sha = commit_tree(self.git.object_store, self.iterblobs(ctx))
++        if tree_sha not in self.git.object_store:
++            raise hgutil.Abort(_('Tree SHA-1 not present in Git repo: %s' %
++                tree_sha))
++
+         commit.tree = tree_sha
+ 
+         self.git.object_store.add_object(commit)
+         self.map_set(commit.id, ctx.hex())
+ 
+         self.swap_out_encoding(oldenc)
+         return commit.id
+ 
+
+-- 
+You received this message because you are subscribed to the Google Groups "hg-git" group.
+To post to this group, send email to hg-git@googlegroups.com.
+To unsubscribe from this group, send email to hg-git+unsubscribe@googlegroups.com.
+For more options, visit this group at http://groups.google.com/group/hg-git?hl=en.
+