subrepo: support for adding a git subrepo
authorEric Eisner <ede@mit.edu>
Sun, 14 Nov 2010 18:15:26 -0500
changeset 12992 2b73a3279a9f
parent 12991 23de1a7f8e82
child 12993 a91334380699
subrepo: support for adding a git subrepo gitsubrepo based on patch from David Soria Parra: http://bitbucket.org/segv/davids-poor-git-subrepo-attempt/
mercurial/subrepo.py
tests/test-subrepo-git.t
--- a/mercurial/subrepo.py	Mon Nov 15 10:57:49 2010 -0600
+++ b/mercurial/subrepo.py	Sun Nov 14 18:15:26 2010 -0500
@@ -5,7 +5,7 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-import errno, os, re, xml.dom.minidom, shutil, urlparse, posixpath
+import errno, os, re, xml.dom.minidom, shutil, subprocess, urlparse, posixpath
 from i18n import _
 import config, util, node, error, cmdutil
 hg = None
@@ -576,7 +576,87 @@
         return self._svncommand(['cat'], name)
 
 
+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))
+
+        # print git's stderr, which is mostly progress and useful info
+        p = subprocess.Popen(cmd, shell=True, bufsize=-1,
+                             close_fds=(os.name == 'posix'),
+                             stdout=subprocess.PIPE)
+        retdata = p.stdout.read()
+        # wait for the child to exit to avoid race condition.
+        p.wait()
+
+        if p.returncode != 0:
+            # there are certain error codes that are ok
+            command = None
+            for arg in commands:
+                if not arg.startswith('-'):
+                    command = arg
+                    break
+            if command == 'cat-file':
+                return retdata, p.returncode
+            if command in ('commit', 'status') and p.returncode == 1:
+                return retdata, p.returncode
+            # for all others, abort
+            raise util.Abort('git %s error %d' % (command, p.returncode))
+
+        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 dirty(self):
+        if self._state[1] != self._gitstate(): # version checked out changed?
+            return True
+        # check for staged changes or modified files; ignore untracked files
+        # docs say --porcelain flag is future-proof format
+        changed = self._gitcommand(['status', '--porcelain',
+                                    '--untracked-files=no'])
+        return bool(changed.strip())
+
+    def commit(self, text, user, date):
+        cmd = ['commit', '-a', '-m', text]
+        if user:
+            cmd += ['--author', user]
+        if date:
+            # git's date parser silently ignores when seconds < 1e9
+            # convert to ISO8601
+            cmd += ['--date', util.datestr(date, '%Y-%m-%dT%H:%M:%S %1%2')]
+        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,
     }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-subrepo-git.t	Sun Nov 14 18:15:26 2010 -0500
@@ -0,0 +1,58 @@
+  $ "$TESTDIR/hghave" git || exit 80
+
+make git commits repeatable
+
+  $ GIT_AUTHOR_NAME='test'; export GIT_AUTHOR_NAME
+  $ GIT_AUTHOR_EMAIL='test@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
+  $ 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