view hgext/convert/git.py @ 5377:756a43a30e34

convert: readd --filemap To handle merges correctly, this revision adds a filemap_source class that wraps a converter_source and does the work necessary to calculate the subgraph we're interested in. The wrapped converter_source must provide a new getchangedfiles method that, given a revision rev, and an index N, returns the list of files that are different in rev and its Nth parent. The implementation depends on the ability to skip some revisions and to change the parents field of the commit objects that we returned earlier. To make the conversion restartable, we assume the revisons in the revmapfile are topologically sorted.
author Alexis S. L. Carvalho <alexis@cecm.usp.br>
date Thu, 04 Oct 2007 23:21:37 -0300
parents 24de027551c1
children a5a7f7fd5554
line wrap: on
line source

# git support for the convert extension

import os
from mercurial import util

from common import NoRepo, commit, converter_source

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 os.popen(s)
            finally:
                if prevgitdir is None:
                    del os.environ['GIT_DIR']
                else:
                    os.environ['GIT_DIR'] = prevgitdir
    else:
        def gitcmd(self, s):
            return os.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("couldn't open GIT repo %s" % path)
        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 2>%s" % (type, rev,
                                                      util.nulldev))
        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)
        author = author or "unknown"

        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" 2>%s' % (self.path,
                                                             util.nulldev))
        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