hgext/convert/git.py
author Dhruva Krishnamurthy <dhruvakm@gmail.com>
Thu, 24 Jul 2008 22:44:15 +0200
changeset 6837 e30c56f337b1
parent 6001 30d2fecaab76
child 7222 c1dc903dc7b6
permissions -rw-r--r--
convert: use git executable only, with subcommands The latest GIT has some changes in the way it is installed. Only the 'git' executable need to be in the path. All other commands are treated as sub commands of 'git'.

# git support for the convert extension

import os
from mercurial import util

from common import NoRepo, commit, converter_source, checktool

class convert_git(converter_source):
    # 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)
            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))

    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').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 --root -m -r %s" % version)
        changes = []
        seen = {}
        for l in fh:
            if "\t" not in l:
                continue
            m, f = l[:-1].split("\t")
            if f in seen:
                continue
            seen[f] = 1
            m = m.split()
            h = m[3]
            p = (m[1] == "100755")
            s = (m[1] == "120000")
            self.modecache[(f, h)] = (p and "x") or (s and "l") or ""
            changes.append((f, h))
        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