changeset 0:06366111af3c

initial import of the hg-git bridging extension for mercurial
author Scott Chacon <schacon@gmail.com>
date Fri, 20 Feb 2009 18:27:51 -0800
parents
children 0e55a2ecac87
files __init__.py git.py
diffstat 2 files changed, 270 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
new file mode 100644
--- /dev/null
+++ b/__init__.py
@@ -0,0 +1,124 @@
+# git.py - git server bridge
+#
+# Copyright 2008 Scott Chacon <schacon at gmail dot com>
+#   also some code (and help) borrowed from durin42  
+#
+# This software may be used and distributed according to the terms
+# of the GNU General Public License, incorporated herein by reference.
+
+'''push and pull from a Git server
+
+This extension lets you communicate (push and pull) with a Git server.
+This way you can use Git hosting for your project or collaborate with a 
+project that is in Git.  A bridger of worlds, this plugin be.
+
+'''
+
+#
+# Stage One - use Git commands to do the import / pushes, all in one big uggo file
+#
+# Stage Two - implement the Git packfile generation and server communication
+#             in native Python, so we don't need Git locally and don't need
+#             to keep all the git repo data around.  We should just need a SHA
+#             mapping - since everything is append only in both systems it should
+#             be pretty simple to do. 
+#
+
+# just importing every damn thing because i don't know python that well
+# and I have no idea what I actually need
+from mercurial import util, repair, merge, cmdutil, commands, error, hg, url
+from mercurial import extensions, ancestor
+from mercurial.commands import templateopts
+from mercurial.node import nullrev, nullid, short
+from mercurial.i18n import _
+import os, errno
+import subprocess
+
+def gclone(ui, git_url, hg_repo_path=None):
+    ## TODO : add git_url as the default remote path
+    if not hg_repo_path:
+        hg_repo_path = hg.defaultdest(git_url)
+        if hg_repo_path.endswith('.git'):
+            hg_repo_path = hg_repo_path[:-4]
+        hg_repo_path += '-hg'
+    subprocess.call(['hg', 'init', hg_repo_path])
+    clone_git(git_url, git_path(hg_repo_path))
+    import_git_heads(hg_repo_path)
+
+def gpull(ui, repo, source='default', **opts):
+    """fetch from a git repo
+    """
+    lock = wlock = None
+    try:
+        lock = repo.lock()
+        wlock = repo.wlock()
+        ui.write("fetching from the remote\n")
+        git_fetch()
+        import_git_heads()
+        # do the pull
+    finally:
+        del lock, wlock
+
+def gpush(ui, repo, dest='default', **opts):
+    """push to a git repo
+    """
+    lock = wlock = None
+    try:
+        lock = repo.lock()
+        wlock = repo.wlock()
+        ui.write("pushing to the remote\n")
+        # do the push
+    finally:
+        del lock, wlock
+
+def git_path(hg_path=None):
+    return os.path.join(hg_path, '.hg', 'git-remote')
+
+def clone_git(git_url, hg_path=None):
+    git_initialize(git_path(hg_path), git_url)
+    git_fetch(git_path(hg_path))
+    
+def git_initialize(git_path, git_url):
+    # TODO: implement this in pure python - should be strait-forward
+    subprocess.call(['git', '--bare', 'init', git_path])
+    oldwd = os.getcwd()
+    os.chdir(git_path)
+    subprocess.call(['git', 'remote', 'add', 'origin', git_url])
+    os.chdir(oldwd)
+    
+def git_fetch(git_path, remote='origin'):
+    # TODO: implement this in pure python
+    #       - we'll have to handle ssh and git
+    oldwd = os.getcwd()
+    os.chdir(git_path)
+    subprocess.call(['git', 'fetch', remote])
+    os.chdir(oldwd)
+  
+def git_push():
+    # find all the local changesets that aren't mapped
+    # create git commit object shas and map them
+    # stick those objects in a packfile and push them up (over ssh)
+    
+def import_git_heads(hg_path=None):
+    # go through each branch
+      # add all commits we don't have locally
+      # write a SHA<->SHA mapping table
+      # update the local branches to match
+    return subprocess.call(['hg', 'convert', git_path(hg_path), hg_path])
+  
+        
+commands.norepo += " gclone"
+cmdtable = {
+  "gclone":
+      (gclone,
+       [ #('A', 'authors', '', 'username mapping filename'),
+       ],
+       'Clone a git repository into an hg repository.',
+       ),
+  "gpush":
+        (gpush,
+         [('m', 'merge', None, _('merge automatically'))],
+         _('hg gpush remote')),
+  "gpull":
+        (gpull, [], _('hg gpull [--merge] remote')),
+}
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/git.py
@@ -0,0 +1,146 @@
+# git support for the convert extension
+
+import os
+from mercurial import util
+
+from common import NoRepo, commit, converter_source, checktool
+
+class git_tool:
+    # Windows does not support GIT_DIR= construct while other systems
+    # cannot remove environment variable. Just assume none have
+    # both issues.
+    if hasattr(os, 'unsetenv'):
+        def gitcmd(self, s):
+            prevgitdir = os.environ.get('GIT_DIR')
+            os.environ['GIT_DIR'] = self.path
+            try:
+                return util.popen(s, 'rb')
+            finally:
+                if prevgitdir is None:
+                    del os.environ['GIT_DIR']
+                else:
+                    os.environ['GIT_DIR'] = prevgitdir
+    else:
+        def gitcmd(self, s):
+            return util.popen('GIT_DIR=%s %s' % (self.path, s), 'rb')
+
+    def __init__(self, ui, path, rev=None):
+        super(convert_git, self).__init__(ui, path, rev=rev)
+
+        if os.path.isdir(path + "/.git"):
+            path += "/.git"
+        if not os.path.exists(path + "/objects"):
+            raise NoRepo("%s does not look like a Git repo" % path)
+
+        checktool('git', 'git')
+
+        self.path = path
+
+    def getheads(self):
+        if not self.rev:
+            return self.gitcmd('git rev-parse --branches --remotes').read().splitlines()
+        else:
+            fh = self.gitcmd("git rev-parse --verify %s" % self.rev)
+            return [fh.read()[:-1]]
+
+    def catfile(self, rev, type):
+        if rev == "0" * 40: raise IOError()
+        fh = self.gitcmd("git cat-file %s %s" % (type, rev))
+        return fh.read()
+
+    def getfile(self, name, rev):
+        return self.catfile(rev, "blob")
+
+    def getmode(self, name, rev):
+        return self.modecache[(name, rev)]
+
+    def getchanges(self, version):
+        self.modecache = {}
+        fh = self.gitcmd("git diff-tree -z --root -m -r %s" % version)
+        changes = []
+        seen = {}
+        entry = None
+        for l in fh.read().split('\x00'):
+            if not entry:
+                if not l.startswith(':'):
+                    continue
+                entry = l
+                continue
+            f = l
+            if f not in seen:
+                seen[f] = 1
+                entry = entry.split()
+                h = entry[3]
+                p = (entry[1] == "100755")
+                s = (entry[1] == "120000")
+                self.modecache[(f, h)] = (p and "x") or (s and "l") or ""
+                changes.append((f, h))
+            entry = None
+        return (changes, {})
+
+    def getcommit(self, version):
+        c = self.catfile(version, "commit") # read the commit hash
+        end = c.find("\n\n")
+        message = c[end+2:]
+        message = self.recode(message)
+        l = c[:end].splitlines()
+        manifest = l[0].split()[1]
+        parents = []
+        for e in l[1:]:
+            n, v = e.split(" ", 1)
+            if n == "author":
+                p = v.split()
+                tm, tz = p[-2:]
+                author = " ".join(p[:-2])
+                if author[0] == "<": author = author[1:-1]
+                author = self.recode(author)
+            if n == "committer":
+                p = v.split()
+                tm, tz = p[-2:]
+                committer = " ".join(p[:-2])
+                if committer[0] == "<": committer = committer[1:-1]
+                committer = self.recode(committer)
+                message += "\ncommitter: %s\n" % committer
+            if n == "parent": parents.append(v)
+
+        tzs, tzh, tzm = tz[-5:-4] + "1", tz[-4:-2], tz[-2:]
+        tz = -int(tzs) * (int(tzh) * 3600 + int(tzm))
+        date = tm + " " + str(tz)
+
+        c = commit(parents=parents, date=date, author=author, desc=message,
+                   rev=version)
+        return c
+
+    def gettags(self):
+        tags = {}
+        fh = self.gitcmd('git ls-remote --tags "%s"' % self.path)
+        prefix = 'refs/tags/'
+        for line in fh:
+            line = line.strip()
+            if not line.endswith("^{}"):
+                continue
+            node, tag = line.split(None, 1)
+            if not tag.startswith(prefix):
+                continue
+            tag = tag[len(prefix):-3]
+            tags[tag] = node
+
+        return tags
+
+    def getchangedfiles(self, version, i):
+        changes = []
+        if i is None:
+            fh = self.gitcmd("git diff-tree --root -m -r %s" % version)
+            for l in fh:
+                if "\t" not in l:
+                    continue
+                m, f = l[:-1].split("\t")
+                changes.append(f)
+            fh.close()
+        else:
+            fh = self.gitcmd('git diff-tree --name-only --root -r %s "%s^%s" --'
+                             % (version, version, i+1))
+            changes = [f.rstrip('\n') for f in fh]
+            fh.close()
+
+        return changes