git.py
303 lines
| 10.3 KiB
| text/x-python
|
PythonLexer
Martin Geisler
|
r8250 | # git.py - git support for the convert extension | ||
# | ||||
# Copyright 2005-2009 Matt Mackall <mpm@selenic.com> and others | ||||
# | ||||
# This software may be used and distributed according to the terms of the | ||||
Matt Mackall
|
r10263 | # GNU General Public License version 2 or any later version. | ||
Brendan Cully
|
r4536 | |||
import os | ||||
Ross Lagerwall
|
r18570 | import subprocess | ||
YaNan Xu
|
r17929 | from mercurial import util, config | ||
Martin Geisler
|
r12144 | from mercurial.node import hex, nullid | ||
Martin Geisler
|
r10939 | from mercurial.i18n import _ | ||
Brendan Cully
|
r4536 | |||
Patrick Mezard
|
r5497 | from common import NoRepo, commit, converter_source, checktool | ||
Brendan Cully
|
r4536 | |||
YaNan Xu
|
r17929 | class submodule(object): | ||
def __init__(self, path, node, url): | ||||
self.path = path | ||||
self.node = node | ||||
self.url = url | ||||
def hgsub(self): | ||||
return "%s = [git]%s" % (self.path, self.url) | ||||
def hgsubstate(self): | ||||
return "%s %s" % (self.node, self.path) | ||||
Brendan Cully
|
r4536 | class convert_git(converter_source): | ||
Patrick Mezard
|
r5217 | # Windows does not support GIT_DIR= construct while other systems | ||
# cannot remove environment variable. Just assume none have | ||||
# both issues. | ||||
Augie Fackler
|
r14945 | if util.safehasattr(os, 'unsetenv'): | ||
Ross Lagerwall
|
r18570 | def gitopen(self, s, err=None): | ||
Patrick Mezard
|
r5217 | prevgitdir = os.environ.get('GIT_DIR') | ||
os.environ['GIT_DIR'] = self.path | ||||
try: | ||||
Ross Lagerwall
|
r18570 | if err == subprocess.PIPE: | ||
Edouard Gomez
|
r13756 | (stdin, stdout, stderr) = util.popen3(s) | ||
return stdout | ||||
Ross Lagerwall
|
r18570 | elif err == subprocess.STDOUT: | ||
return self.popen_with_stderr(s) | ||||
Edouard Gomez
|
r13756 | else: | ||
return util.popen(s, 'rb') | ||||
Patrick Mezard
|
r5217 | finally: | ||
if prevgitdir is None: | ||||
del os.environ['GIT_DIR'] | ||||
else: | ||||
os.environ['GIT_DIR'] = prevgitdir | ||||
else: | ||||
Ross Lagerwall
|
r18570 | def gitopen(self, s, err=None): | ||
if err == subprocess.PIPE: | ||||
Edouard Gomez
|
r13756 | (sin, so, se) = util.popen3('GIT_DIR=%s %s' % (self.path, s)) | ||
timeless
|
r14177 | return so | ||
Ross Lagerwall
|
r18570 | elif err == subprocess.STDOUT: | ||
return self.popen_with_stderr(s) | ||||
Edouard Gomez
|
r13756 | else: | ||
Mads Kiilerich
|
r14735 | return util.popen('GIT_DIR=%s %s' % (self.path, s), 'rb') | ||
Brendan Cully
|
r4767 | |||
Ross Lagerwall
|
r18570 | def popen_with_stderr(self, s): | ||
p = subprocess.Popen(s, shell=True, bufsize=-1, | ||||
close_fds=util.closefds, | ||||
stdin=subprocess.PIPE, | ||||
stdout=subprocess.PIPE, | ||||
stderr=subprocess.STDOUT, | ||||
universal_newlines=False, | ||||
env=None) | ||||
return p.stdout | ||||
Patrick Mezard
|
r10986 | def gitread(self, s): | ||
fh = self.gitopen(s) | ||||
data = fh.read() | ||||
return data, fh.close() | ||||
Brendan Cully
|
r4760 | def __init__(self, ui, path, rev=None): | ||
Brendan Cully
|
r4807 | super(convert_git, self).__init__(ui, path, rev=rev) | ||
Brendan Cully
|
r4536 | if os.path.isdir(path + "/.git"): | ||
path += "/.git" | ||||
if not os.path.exists(path + "/objects"): | ||||
Martin Geisler
|
r10939 | raise NoRepo(_("%s does not look like a Git repository") % path) | ||
Patrick Mezard
|
r5497 | |||
Dhruva Krishnamurthy
|
r6837 | checktool('git', 'git') | ||
Patrick Mezard
|
r5497 | |||
Brendan Cully
|
r4536 | self.path = path | ||
YaNan Xu
|
r17929 | self.submodules = [] | ||
Brendan Cully
|
r4536 | |||
def getheads(self): | ||||
Brendan Cully
|
r4768 | if not self.rev: | ||
Patrick Mezard
|
r10986 | heads, ret = self.gitread('git rev-parse --branches --remotes') | ||
heads = heads.splitlines() | ||||
Brendan Cully
|
r4768 | else: | ||
Patrick Mezard
|
r10986 | heads, ret = self.gitread("git rev-parse --verify %s" % self.rev) | ||
heads = [heads[:-1]] | ||||
if ret: | ||||
raise util.Abort(_('cannot retrieve git heads')) | ||||
return heads | ||||
Brendan Cully
|
r4536 | |||
def catfile(self, rev, type): | ||||
Martin Geisler
|
r12144 | if rev == hex(nullid): | ||
Brodie Rao
|
r16687 | raise IOError | ||
Patrick Mezard
|
r10986 | data, ret = self.gitread("git cat-file %s %s" % (type, rev)) | ||
if ret: | ||||
raise util.Abort(_('cannot read %r object at %s') % (type, rev)) | ||||
return data | ||||
Brendan Cully
|
r4536 | |||
def getfile(self, name, rev): | ||||
YaNan Xu
|
r17929 | if name == '.hgsub': | ||
data = '\n'.join([m.hgsub() for m in self.submoditer()]) | ||||
mode = '' | ||||
elif name == '.hgsubstate': | ||||
data = '\n'.join([m.hgsubstate() for m in self.submoditer()]) | ||||
mode = '' | ||||
else: | ||||
data = self.catfile(rev, "blob") | ||||
mode = self.modecache[(name, rev)] | ||||
Patrick Mezard
|
r11134 | return data, mode | ||
Brendan Cully
|
r4536 | |||
YaNan Xu
|
r17929 | def submoditer(self): | ||
null = hex(nullid) | ||||
for m in sorted(self.submodules, key=lambda p: p.path): | ||||
if m.node != null: | ||||
yield m | ||||
def parsegitmodules(self, content): | ||||
"""Parse the formatted .gitmodules file, example file format: | ||||
[submodule "sub"]\n | ||||
\tpath = sub\n | ||||
\turl = git://giturl\n | ||||
""" | ||||
self.submodules = [] | ||||
c = config.config() | ||||
# Each item in .gitmodules starts with \t that cant be parsed | ||||
c.parse('.gitmodules', content.replace('\t','')) | ||||
for sec in c.sections(): | ||||
s = c[sec] | ||||
if 'url' in s and 'path' in s: | ||||
self.submodules.append(submodule(s['path'], '', s['url'])) | ||||
def retrievegitmodules(self, version): | ||||
modules, ret = self.gitread("git show %s:%s" % (version, '.gitmodules')) | ||||
if ret: | ||||
Bryan O'Sullivan
|
r17930 | raise util.Abort(_('cannot read submodules config file in %s') % | ||
version) | ||||
YaNan Xu
|
r17929 | self.parsegitmodules(modules) | ||
for m in self.submodules: | ||||
node, ret = self.gitread("git rev-parse %s:%s" % (version, m.path)) | ||||
if ret: | ||||
continue | ||||
m.node = node.strip() | ||||
Brendan Cully
|
r4536 | def getchanges(self, version): | ||
self.modecache = {} | ||||
Patrick Mezard
|
r10985 | fh = self.gitopen("git diff-tree -z --root -m -r %s" % version) | ||
Brendan Cully
|
r4536 | changes = [] | ||
Benoit Boissinot
|
r8456 | seen = set() | ||
Patrick Mezard
|
r7242 | entry = None | ||
YaNan Xu
|
r17929 | subexists = False | ||
Patrick Mezard
|
r7242 | for l in fh.read().split('\x00'): | ||
if not entry: | ||||
if not l.startswith(':'): | ||||
continue | ||||
entry = l | ||||
Alexis S. L. Carvalho
|
r5335 | continue | ||
Patrick Mezard
|
r7242 | f = l | ||
if f not in seen: | ||||
Benoit Boissinot
|
r8456 | seen.add(f) | ||
Patrick Mezard
|
r7242 | entry = entry.split() | ||
h = entry[3] | ||||
p = (entry[1] == "100755") | ||||
s = (entry[1] == "120000") | ||||
YaNan Xu
|
r17929 | |||
if f == '.gitmodules': | ||||
subexists = True | ||||
changes.append(('.hgsub', '')) | ||||
elif entry[1] == '160000' or entry[0] == ':160000': | ||||
subexists = True | ||||
else: | ||||
self.modecache[(f, h)] = (p and "x") or (s and "l") or "" | ||||
changes.append((f, h)) | ||||
Patrick Mezard
|
r7242 | entry = None | ||
Patrick Mezard
|
r10987 | if fh.close(): | ||
raise util.Abort(_('cannot read changes in %s') % version) | ||||
YaNan Xu
|
r17929 | |||
if subexists: | ||||
self.retrievegitmodules(version) | ||||
changes.append(('.hgsubstate', '')) | ||||
Brendan Cully
|
r5121 | return (changes, {}) | ||
Brendan Cully
|
r4536 | |||
def getcommit(self, version): | ||||
c = self.catfile(version, "commit") # read the commit hash | ||||
end = c.find("\n\n") | ||||
Matt Mackall
|
r10282 | message = c[end + 2:] | ||
Brendan Cully
|
r4759 | message = self.recode(message) | ||
Brendan Cully
|
r4536 | l = c[:end].splitlines() | ||
parents = [] | ||||
Richard Quirk
|
r8271 | author = committer = None | ||
Brendan Cully
|
r4536 | 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] | ||||
Brendan Cully
|
r4759 | author = self.recode(author) | ||
Brendan Cully
|
r4536 | if n == "committer": | ||
p = v.split() | ||||
tm, tz = p[-2:] | ||||
committer = " ".join(p[:-2]) | ||||
if committer[0] == "<": committer = committer[1:-1] | ||||
Brendan Cully
|
r4759 | committer = self.recode(committer) | ||
Matt Mackall
|
r10282 | if n == "parent": | ||
parents.append(v) | ||||
Brendan Cully
|
r4536 | |||
Richard Quirk
|
r8271 | if committer and committer != author: | ||
message += "\ncommitter: %s\n" % committer | ||||
Brendan Cully
|
r4536 | tzs, tzh, tzm = tz[-5:-4] + "1", tz[-4:-2], tz[-2:] | ||
tz = -int(tzs) * (int(tzh) * 3600 + int(tzm)) | ||||
date = tm + " " + str(tz) | ||||
Brendan Cully
|
r4873 | c = commit(parents=parents, date=date, author=author, desc=message, | ||
rev=version) | ||||
Brendan Cully
|
r4536 | return c | ||
def gettags(self): | ||||
tags = {} | ||||
Edouard Gomez
|
r16259 | alltags = {} | ||
Ross Lagerwall
|
r18570 | fh = self.gitopen('git ls-remote --tags "%s"' % self.path, | ||
err=subprocess.STDOUT) | ||||
Brendan Cully
|
r4536 | prefix = 'refs/tags/' | ||
Edouard Gomez
|
r16259 | |||
# Build complete list of tags, both annotated and bare ones | ||||
Brendan Cully
|
r4536 | for line in fh: | ||
line = line.strip() | ||||
Augie Fackler
|
r18572 | if line.startswith("error:") or line.startswith("fatal:"): | ||
Ross Lagerwall
|
r18570 | raise util.Abort(_('cannot read tags from %s') % self.path) | ||
Brendan Cully
|
r4536 | node, tag = line.split(None, 1) | ||
if not tag.startswith(prefix): | ||||
continue | ||||
Edouard Gomez
|
r16259 | alltags[tag[len(prefix):]] = node | ||
Patrick Mezard
|
r10987 | if fh.close(): | ||
raise util.Abort(_('cannot read tags from %s') % self.path) | ||||
Brendan Cully
|
r4536 | |||
Edouard Gomez
|
r16259 | # Filter out tag objects for annotated tag refs | ||
for tag in alltags: | ||||
if tag.endswith('^{}'): | ||||
tags[tag[:-3]] = alltags[tag] | ||||
else: | ||||
if tag + '^{}' in alltags: | ||||
continue | ||||
else: | ||||
tags[tag] = alltags[tag] | ||||
Brendan Cully
|
r4536 | return tags | ||
Alexis S. L. Carvalho
|
r5380 | |||
def getchangedfiles(self, version, i): | ||||
changes = [] | ||||
if i is None: | ||||
Patrick Mezard
|
r10985 | fh = self.gitopen("git diff-tree --root -m -r %s" % version) | ||
Alexis S. L. Carvalho
|
r5380 | for l in fh: | ||
if "\t" not in l: | ||||
continue | ||||
m, f = l[:-1].split("\t") | ||||
changes.append(f) | ||||
else: | ||||
Brodie Rao
|
r16683 | fh = self.gitopen('git diff-tree --name-only --root -r %s ' | ||
'"%s^%s" --' % (version, version, i + 1)) | ||||
Alexis S. L. Carvalho
|
r5380 | changes = [f.rstrip('\n') for f in fh] | ||
Patrick Mezard
|
r10987 | if fh.close(): | ||
raise util.Abort(_('cannot read changes in %s') % version) | ||||
Alexis S. L. Carvalho
|
r5380 | |||
return changes | ||||
Edouard Gomez
|
r13756 | |||
def getbookmarks(self): | ||||
bookmarks = {} | ||||
# Interesting references in git are prefixed | ||||
prefix = 'refs/heads/' | ||||
prefixlen = len(prefix) | ||||
# factor two commands | ||||
gitcmd = { 'remote/': 'git ls-remote --heads origin', | ||||
'': 'git show-ref'} | ||||
# Origin heads | ||||
for reftype in gitcmd: | ||||
try: | ||||
Ross Lagerwall
|
r18570 | fh = self.gitopen(gitcmd[reftype], err=subprocess.PIPE) | ||
Edouard Gomez
|
r13756 | for line in fh: | ||
line = line.strip() | ||||
rev, name = line.split(None, 1) | ||||
if not name.startswith(prefix): | ||||
continue | ||||
name = '%s%s' % (reftype, name[prefixlen:]) | ||||
bookmarks[name] = rev | ||||
Brodie Rao
|
r16689 | except Exception: | ||
Edouard Gomez
|
r13756 | pass | ||
return bookmarks | ||||
Ben Goswami
|
r19121 | |||
def checkrevformat(self, revstr): | ||||
""" git revision string is a 40 byte hex """ | ||||
self.checkhexformat(revstr) | ||||