[PATCH 1 of 3] subrepo: support for adding a git subrepo

Eric Eisner ede at MIT.EDU
Thu Nov 11 13:43:25 CST 2010


# HG changeset patch
# User Eric Eisner <ede at mit.edu>
# Date 1289503448 18000
# Node ID ccc59ab604a8160d44847754f8b48dedb25d2f16
# Parent  e80128e40c044ea6619c3dc160df69ad36a8e5ff
subrepo: support for adding a git subrepo

gitsubrepo based on patch from David Soria Parra:
http://bitbucket.org/segv/davids-poor-git-subrepo-attempt/

diff --git a/mercurial/subrepo.py b/mercurial/subrepo.py
--- a/mercurial/subrepo.py
+++ b/mercurial/subrepo.py
@@ -576,7 +576,93 @@ class svnsubrepo(abstractsubrepo):
         return self._svncommand(['cat'], name)
 
 
+# Subrepository implementation for git.
+#
+# We try to call out the git plumbing command set as often as possible. Git
+# porcelain might change so we cannot use porcelain directly.
+class gitsubrepo(object):
+    def __init__(self, ctx, path, state):
+        # TODO add git version check.
+        self._state = state
+        self._ctx = ctx
+        self._path = ctx._repo.wjoin(path)
+        self._ui = ctx._repo.ui
+
+    def _gitcommand(self, commands):
+        return self._gitdir(commands)[0]
+
+    def _gitdir(self, commands):
+        commands = ['--no-pager', '--git-dir=%s/.git' % self._path,
+                    '--work-tree=%s' % self._path] + commands
+        return self._gitnodir(commands)
+
+    def _gitnodir(self, commands):
+        """Calls the git command
+
+        The methods tries to call the git command. versions previor to 1.6.0
+        are not supported and very probably fail.
+        """
+        cmd = ['git'] + commands
+        cmd = [util.shellquote(arg) for arg in cmd]
+        cmd = util.quotecommand(' '.join(cmd))
+
+        p = util.popenp(cmd)
+        write, read, err = p.stdin, p.stdout, p.stderr
+        retdata = read.read()
+        err = err.read().strip()
+
+        if err:
+            raise util.Abort(err)
+        # we have to wait for the child to exit to avoid race condition.
+        p.wait()
+        return retdata, p.returncode
+
+    def _gitstate(self):
+        return self._gitcommand(['rev-parse', 'HEAD']).strip()
+
+    def _githavelocally(self, revision):
+        out, code = self._gitdir(['cat-file', '-e', revision])
+        return code == 0
+
+    def _fetch(self, source, revision):
+        if not os.path.exists('%s/.git' % self._path):
+            self._gitnodir(['clone', source, self._path])
+        if self._githavelocally(revision):
+            return
+        self._ui.status(_('pulling subrepo %s\n') % self._path)
+        self._gitcommand(['fetch', '--all', source])
+        if not self._githavelocally(revision):
+            raise util.Abort(_("revision %s does not exist in subrepo %s\n") %
+                               (revision, source))
+
+    def dirty(self):
+        if self._state[1] != self._gitstate(): # version checked out changed?
+            return True
+        # check for staged changes or modified files; ignore untracked files
+        changed = self._gitcommand(['status', '--porcelain',
+                                    '--untracked-files=no'])
+        return bool(changed.strip())
+
+    def get(self, state):
+        source, revision, kind = state
+        self._fetch(source, revision)
+        if self._gitstate() != revision:
+            self._ui.status(self._gitcommand(['checkout', '-f', revision]))
+
+    def commit(self, text, user, date):
+        cmd = ['commit', '-a', '-m', text]
+        if user:
+            cmd += ['--author', user]
+        # TODO: git's date parser silently ignores when seconds < 1e9
+        if date:
+            cmd += ['--date', '%s %s' % date]
+        self._gitcommand(cmd)
+        # make sure commit works otherwise HEAD might not exist under certain
+        # circumstances
+        return self._gitstate()
+
 types = {
     'hg': hgsubrepo,
     'svn': svnsubrepo,
+    'git': gitsubrepo,
     }
diff --git a/mercurial/util.py b/mercurial/util.py
--- a/mercurial/util.py
+++ b/mercurial/util.py
@@ -72,6 +72,15 @@ def popen3(cmd, env=None, newlines=False
                          env=env)
     return p.stdin, p.stdout, p.stderr
 
+def popenp(cmd, env=None, newlines=False):
+    p = subprocess.Popen(cmd, shell=True, bufsize=-1,
+                         close_fds=closefds,
+                         stdin=subprocess.PIPE, stdout=subprocess.PIPE,
+                         stderr=subprocess.PIPE,
+                         universal_newlines=newlines,
+                         env=env)
+    return p
+
 def version():
     """Return version information if available."""
     try:
diff --git a/tests/test-subrepo-git.t b/tests/test-subrepo-git.t
new file mode 100644
--- /dev/null
+++ b/tests/test-subrepo-git.t
@@ -0,0 +1,59 @@
+  $ "$TESTDIR/hghave" git || exit 80
+
+make git commits repeatable
+
+  $ GIT_AUTHOR_NAME='test'; export GIT_AUTHOR_NAME
+  $ GIT_AUTHOR_EMAIL='test at example.org'; export GIT_AUTHOR_EMAIL
+  $ GIT_AUTHOR_DATE='1234567891 +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
+
+root hg repo
+
+  $ hg init t
+  $ cd t
+  $ echo a > a
+  $ hg add a
+  $ hg commit -m a
+  $ cd ..
+
+new external git repo
+
+  $ mkdir gitroot
+  $ GITROOT=file://$TESTTMP/gitroot ; export GITROOT
+  $ cd gitroot
+  $ git init -q
+  $ echo g > g
+  $ git add g
+  $ git commit -q -m g
+
+add subrepo clone
+
+  $ cd ../t
+  $ echo 's = [git]../gitroot' > .hgsub
+  $ git clone -q ../gitroot s
+  $ hg add .hgsub
+  $ hg commit -m 'new git subrepo'
+  committing subrepository $TESTTMP/t/s
+  $ hg debugsub
+  path s
+   source   ../gitroot
+   revision da5f5b1d8ffcf62fb8327bcd3c89a4367a6018e7
+
+record a new commit from upstream
+
+  $ cd ../gitroot
+  $ echo gg >> g
+  $ git commit -q -a -m gg
+
+  $ cd ../t/s
+  $ git pull -q
+
+  $ cd ..
+  $ hg commit -m 'update git subrepo'
+  committing subrepository $TESTTMP/t/s
+  $ hg debugsub
+  path s
+   source   ../gitroot
+   revision 126f2a14290cd5ce061fdedc430170e8d39e1c5a


More information about the Mercurial-devel mailing list