##// END OF EJS Templates
convert: for git's getchanges, always split entry line into components...
Siddharth Agarwal -
r22468:5910184f default
parent child Browse files
Show More
@@ -1,352 +1,351
1 # git.py - git support for the convert extension
1 # git.py - git support for the convert extension
2 #
2 #
3 # Copyright 2005-2009 Matt Mackall <mpm@selenic.com> and others
3 # Copyright 2005-2009 Matt Mackall <mpm@selenic.com> and others
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 import os
8 import os
9 import subprocess
9 import subprocess
10 from mercurial import util, config
10 from mercurial import util, config
11 from mercurial.node import hex, nullid
11 from mercurial.node import hex, nullid
12 from mercurial.i18n import _
12 from mercurial.i18n import _
13
13
14 from common import NoRepo, commit, converter_source, checktool
14 from common import NoRepo, commit, converter_source, checktool
15
15
16 class submodule(object):
16 class submodule(object):
17 def __init__(self, path, node, url):
17 def __init__(self, path, node, url):
18 self.path = path
18 self.path = path
19 self.node = node
19 self.node = node
20 self.url = url
20 self.url = url
21
21
22 def hgsub(self):
22 def hgsub(self):
23 return "%s = [git]%s" % (self.path, self.url)
23 return "%s = [git]%s" % (self.path, self.url)
24
24
25 def hgsubstate(self):
25 def hgsubstate(self):
26 return "%s %s" % (self.node, self.path)
26 return "%s %s" % (self.node, self.path)
27
27
28 class convert_git(converter_source):
28 class convert_git(converter_source):
29 # Windows does not support GIT_DIR= construct while other systems
29 # Windows does not support GIT_DIR= construct while other systems
30 # cannot remove environment variable. Just assume none have
30 # cannot remove environment variable. Just assume none have
31 # both issues.
31 # both issues.
32 if util.safehasattr(os, 'unsetenv'):
32 if util.safehasattr(os, 'unsetenv'):
33 def gitopen(self, s, err=None):
33 def gitopen(self, s, err=None):
34 prevgitdir = os.environ.get('GIT_DIR')
34 prevgitdir = os.environ.get('GIT_DIR')
35 os.environ['GIT_DIR'] = self.path
35 os.environ['GIT_DIR'] = self.path
36 try:
36 try:
37 if err == subprocess.PIPE:
37 if err == subprocess.PIPE:
38 (stdin, stdout, stderr) = util.popen3(s)
38 (stdin, stdout, stderr) = util.popen3(s)
39 return stdout
39 return stdout
40 elif err == subprocess.STDOUT:
40 elif err == subprocess.STDOUT:
41 return self.popen_with_stderr(s)
41 return self.popen_with_stderr(s)
42 else:
42 else:
43 return util.popen(s, 'rb')
43 return util.popen(s, 'rb')
44 finally:
44 finally:
45 if prevgitdir is None:
45 if prevgitdir is None:
46 del os.environ['GIT_DIR']
46 del os.environ['GIT_DIR']
47 else:
47 else:
48 os.environ['GIT_DIR'] = prevgitdir
48 os.environ['GIT_DIR'] = prevgitdir
49
49
50 def gitpipe(self, s):
50 def gitpipe(self, s):
51 prevgitdir = os.environ.get('GIT_DIR')
51 prevgitdir = os.environ.get('GIT_DIR')
52 os.environ['GIT_DIR'] = self.path
52 os.environ['GIT_DIR'] = self.path
53 try:
53 try:
54 return util.popen3(s)
54 return util.popen3(s)
55 finally:
55 finally:
56 if prevgitdir is None:
56 if prevgitdir is None:
57 del os.environ['GIT_DIR']
57 del os.environ['GIT_DIR']
58 else:
58 else:
59 os.environ['GIT_DIR'] = prevgitdir
59 os.environ['GIT_DIR'] = prevgitdir
60
60
61 else:
61 else:
62 def gitopen(self, s, err=None):
62 def gitopen(self, s, err=None):
63 if err == subprocess.PIPE:
63 if err == subprocess.PIPE:
64 (sin, so, se) = util.popen3('GIT_DIR=%s %s' % (self.path, s))
64 (sin, so, se) = util.popen3('GIT_DIR=%s %s' % (self.path, s))
65 return so
65 return so
66 elif err == subprocess.STDOUT:
66 elif err == subprocess.STDOUT:
67 return self.popen_with_stderr(s)
67 return self.popen_with_stderr(s)
68 else:
68 else:
69 return util.popen('GIT_DIR=%s %s' % (self.path, s), 'rb')
69 return util.popen('GIT_DIR=%s %s' % (self.path, s), 'rb')
70
70
71 def gitpipe(self, s):
71 def gitpipe(self, s):
72 return util.popen3('GIT_DIR=%s %s' % (self.path, s))
72 return util.popen3('GIT_DIR=%s %s' % (self.path, s))
73
73
74 def popen_with_stderr(self, s):
74 def popen_with_stderr(self, s):
75 p = subprocess.Popen(s, shell=True, bufsize=-1,
75 p = subprocess.Popen(s, shell=True, bufsize=-1,
76 close_fds=util.closefds,
76 close_fds=util.closefds,
77 stdin=subprocess.PIPE,
77 stdin=subprocess.PIPE,
78 stdout=subprocess.PIPE,
78 stdout=subprocess.PIPE,
79 stderr=subprocess.STDOUT,
79 stderr=subprocess.STDOUT,
80 universal_newlines=False,
80 universal_newlines=False,
81 env=None)
81 env=None)
82 return p.stdout
82 return p.stdout
83
83
84 def gitread(self, s):
84 def gitread(self, s):
85 fh = self.gitopen(s)
85 fh = self.gitopen(s)
86 data = fh.read()
86 data = fh.read()
87 return data, fh.close()
87 return data, fh.close()
88
88
89 def __init__(self, ui, path, rev=None):
89 def __init__(self, ui, path, rev=None):
90 super(convert_git, self).__init__(ui, path, rev=rev)
90 super(convert_git, self).__init__(ui, path, rev=rev)
91
91
92 if os.path.isdir(path + "/.git"):
92 if os.path.isdir(path + "/.git"):
93 path += "/.git"
93 path += "/.git"
94 if not os.path.exists(path + "/objects"):
94 if not os.path.exists(path + "/objects"):
95 raise NoRepo(_("%s does not look like a Git repository") % path)
95 raise NoRepo(_("%s does not look like a Git repository") % path)
96
96
97 checktool('git', 'git')
97 checktool('git', 'git')
98
98
99 self.path = path
99 self.path = path
100 self.submodules = []
100 self.submodules = []
101
101
102 self.catfilepipe = self.gitpipe('git cat-file --batch')
102 self.catfilepipe = self.gitpipe('git cat-file --batch')
103
103
104 def after(self):
104 def after(self):
105 for f in self.catfilepipe:
105 for f in self.catfilepipe:
106 f.close()
106 f.close()
107
107
108 def getheads(self):
108 def getheads(self):
109 if not self.rev:
109 if not self.rev:
110 heads, ret = self.gitread('git rev-parse --branches --remotes')
110 heads, ret = self.gitread('git rev-parse --branches --remotes')
111 heads = heads.splitlines()
111 heads = heads.splitlines()
112 else:
112 else:
113 heads, ret = self.gitread("git rev-parse --verify %s" % self.rev)
113 heads, ret = self.gitread("git rev-parse --verify %s" % self.rev)
114 heads = [heads[:-1]]
114 heads = [heads[:-1]]
115 if ret:
115 if ret:
116 raise util.Abort(_('cannot retrieve git heads'))
116 raise util.Abort(_('cannot retrieve git heads'))
117 return heads
117 return heads
118
118
119 def catfile(self, rev, type):
119 def catfile(self, rev, type):
120 if rev == hex(nullid):
120 if rev == hex(nullid):
121 raise IOError
121 raise IOError
122 self.catfilepipe[0].write(rev+'\n')
122 self.catfilepipe[0].write(rev+'\n')
123 self.catfilepipe[0].flush()
123 self.catfilepipe[0].flush()
124 info = self.catfilepipe[1].readline().split()
124 info = self.catfilepipe[1].readline().split()
125 if info[1] != type:
125 if info[1] != type:
126 raise util.Abort(_('cannot read %r object at %s') % (type, rev))
126 raise util.Abort(_('cannot read %r object at %s') % (type, rev))
127 size = int(info[2])
127 size = int(info[2])
128 data = self.catfilepipe[1].read(size)
128 data = self.catfilepipe[1].read(size)
129 if len(data) < size:
129 if len(data) < size:
130 raise util.Abort(_('cannot read %r object at %s: unexpected size')
130 raise util.Abort(_('cannot read %r object at %s: unexpected size')
131 % (type, rev))
131 % (type, rev))
132 # read the trailing newline
132 # read the trailing newline
133 self.catfilepipe[1].read(1)
133 self.catfilepipe[1].read(1)
134 return data
134 return data
135
135
136 def getfile(self, name, rev):
136 def getfile(self, name, rev):
137 if rev == hex(nullid):
137 if rev == hex(nullid):
138 return None, None
138 return None, None
139 if name == '.hgsub':
139 if name == '.hgsub':
140 data = '\n'.join([m.hgsub() for m in self.submoditer()])
140 data = '\n'.join([m.hgsub() for m in self.submoditer()])
141 mode = ''
141 mode = ''
142 elif name == '.hgsubstate':
142 elif name == '.hgsubstate':
143 data = '\n'.join([m.hgsubstate() for m in self.submoditer()])
143 data = '\n'.join([m.hgsubstate() for m in self.submoditer()])
144 mode = ''
144 mode = ''
145 else:
145 else:
146 data = self.catfile(rev, "blob")
146 data = self.catfile(rev, "blob")
147 mode = self.modecache[(name, rev)]
147 mode = self.modecache[(name, rev)]
148 return data, mode
148 return data, mode
149
149
150 def submoditer(self):
150 def submoditer(self):
151 null = hex(nullid)
151 null = hex(nullid)
152 for m in sorted(self.submodules, key=lambda p: p.path):
152 for m in sorted(self.submodules, key=lambda p: p.path):
153 if m.node != null:
153 if m.node != null:
154 yield m
154 yield m
155
155
156 def parsegitmodules(self, content):
156 def parsegitmodules(self, content):
157 """Parse the formatted .gitmodules file, example file format:
157 """Parse the formatted .gitmodules file, example file format:
158 [submodule "sub"]\n
158 [submodule "sub"]\n
159 \tpath = sub\n
159 \tpath = sub\n
160 \turl = git://giturl\n
160 \turl = git://giturl\n
161 """
161 """
162 self.submodules = []
162 self.submodules = []
163 c = config.config()
163 c = config.config()
164 # Each item in .gitmodules starts with \t that cant be parsed
164 # Each item in .gitmodules starts with \t that cant be parsed
165 c.parse('.gitmodules', content.replace('\t',''))
165 c.parse('.gitmodules', content.replace('\t',''))
166 for sec in c.sections():
166 for sec in c.sections():
167 s = c[sec]
167 s = c[sec]
168 if 'url' in s and 'path' in s:
168 if 'url' in s and 'path' in s:
169 self.submodules.append(submodule(s['path'], '', s['url']))
169 self.submodules.append(submodule(s['path'], '', s['url']))
170
170
171 def retrievegitmodules(self, version):
171 def retrievegitmodules(self, version):
172 modules, ret = self.gitread("git show %s:%s" % (version, '.gitmodules'))
172 modules, ret = self.gitread("git show %s:%s" % (version, '.gitmodules'))
173 if ret:
173 if ret:
174 raise util.Abort(_('cannot read submodules config file in %s') %
174 raise util.Abort(_('cannot read submodules config file in %s') %
175 version)
175 version)
176 self.parsegitmodules(modules)
176 self.parsegitmodules(modules)
177 for m in self.submodules:
177 for m in self.submodules:
178 node, ret = self.gitread("git rev-parse %s:%s" % (version, m.path))
178 node, ret = self.gitread("git rev-parse %s:%s" % (version, m.path))
179 if ret:
179 if ret:
180 continue
180 continue
181 m.node = node.strip()
181 m.node = node.strip()
182
182
183 def getchanges(self, version, full):
183 def getchanges(self, version, full):
184 if full:
184 if full:
185 raise util.Abort(_("convert from git do not support --full"))
185 raise util.Abort(_("convert from git do not support --full"))
186 self.modecache = {}
186 self.modecache = {}
187 fh = self.gitopen("git diff-tree -z --root -m -r %s" % version)
187 fh = self.gitopen("git diff-tree -z --root -m -r %s" % version)
188 changes = []
188 changes = []
189 seen = set()
189 seen = set()
190 entry = None
190 entry = None
191 subexists = False
191 subexists = False
192 subdeleted = False
192 subdeleted = False
193 difftree = fh.read().split('\x00')
193 difftree = fh.read().split('\x00')
194 lcount = len(difftree)
194 lcount = len(difftree)
195 i = 0
195 i = 0
196 while i < lcount:
196 while i < lcount:
197 l = difftree[i]
197 l = difftree[i]
198 i += 1
198 i += 1
199 if not entry:
199 if not entry:
200 if not l.startswith(':'):
200 if not l.startswith(':'):
201 continue
201 continue
202 entry = l
202 entry = l.split()
203 continue
203 continue
204 f = l
204 f = l
205 if f not in seen:
205 if f not in seen:
206 seen.add(f)
206 seen.add(f)
207 entry = entry.split()
208 h = entry[3]
207 h = entry[3]
209 p = (entry[1] == "100755")
208 p = (entry[1] == "100755")
210 s = (entry[1] == "120000")
209 s = (entry[1] == "120000")
211
210
212 if f == '.gitmodules':
211 if f == '.gitmodules':
213 subexists = True
212 subexists = True
214 if entry[4] == 'D':
213 if entry[4] == 'D':
215 subdeleted = True
214 subdeleted = True
216 changes.append(('.hgsub', hex(nullid)))
215 changes.append(('.hgsub', hex(nullid)))
217 else:
216 else:
218 changes.append(('.hgsub', ''))
217 changes.append(('.hgsub', ''))
219 elif entry[1] == '160000' or entry[0] == ':160000':
218 elif entry[1] == '160000' or entry[0] == ':160000':
220 subexists = True
219 subexists = True
221 else:
220 else:
222 self.modecache[(f, h)] = (p and "x") or (s and "l") or ""
221 self.modecache[(f, h)] = (p and "x") or (s and "l") or ""
223 changes.append((f, h))
222 changes.append((f, h))
224 entry = None
223 entry = None
225 if fh.close():
224 if fh.close():
226 raise util.Abort(_('cannot read changes in %s') % version)
225 raise util.Abort(_('cannot read changes in %s') % version)
227
226
228 if subexists:
227 if subexists:
229 if subdeleted:
228 if subdeleted:
230 changes.append(('.hgsubstate', hex(nullid)))
229 changes.append(('.hgsubstate', hex(nullid)))
231 else:
230 else:
232 self.retrievegitmodules(version)
231 self.retrievegitmodules(version)
233 changes.append(('.hgsubstate', ''))
232 changes.append(('.hgsubstate', ''))
234 return (changes, {})
233 return (changes, {})
235
234
236 def getcommit(self, version):
235 def getcommit(self, version):
237 c = self.catfile(version, "commit") # read the commit hash
236 c = self.catfile(version, "commit") # read the commit hash
238 end = c.find("\n\n")
237 end = c.find("\n\n")
239 message = c[end + 2:]
238 message = c[end + 2:]
240 message = self.recode(message)
239 message = self.recode(message)
241 l = c[:end].splitlines()
240 l = c[:end].splitlines()
242 parents = []
241 parents = []
243 author = committer = None
242 author = committer = None
244 for e in l[1:]:
243 for e in l[1:]:
245 n, v = e.split(" ", 1)
244 n, v = e.split(" ", 1)
246 if n == "author":
245 if n == "author":
247 p = v.split()
246 p = v.split()
248 tm, tz = p[-2:]
247 tm, tz = p[-2:]
249 author = " ".join(p[:-2])
248 author = " ".join(p[:-2])
250 if author[0] == "<": author = author[1:-1]
249 if author[0] == "<": author = author[1:-1]
251 author = self.recode(author)
250 author = self.recode(author)
252 if n == "committer":
251 if n == "committer":
253 p = v.split()
252 p = v.split()
254 tm, tz = p[-2:]
253 tm, tz = p[-2:]
255 committer = " ".join(p[:-2])
254 committer = " ".join(p[:-2])
256 if committer[0] == "<": committer = committer[1:-1]
255 if committer[0] == "<": committer = committer[1:-1]
257 committer = self.recode(committer)
256 committer = self.recode(committer)
258 if n == "parent":
257 if n == "parent":
259 parents.append(v)
258 parents.append(v)
260
259
261 if committer and committer != author:
260 if committer and committer != author:
262 message += "\ncommitter: %s\n" % committer
261 message += "\ncommitter: %s\n" % committer
263 tzs, tzh, tzm = tz[-5:-4] + "1", tz[-4:-2], tz[-2:]
262 tzs, tzh, tzm = tz[-5:-4] + "1", tz[-4:-2], tz[-2:]
264 tz = -int(tzs) * (int(tzh) * 3600 + int(tzm))
263 tz = -int(tzs) * (int(tzh) * 3600 + int(tzm))
265 date = tm + " " + str(tz)
264 date = tm + " " + str(tz)
266
265
267 c = commit(parents=parents, date=date, author=author, desc=message,
266 c = commit(parents=parents, date=date, author=author, desc=message,
268 rev=version)
267 rev=version)
269 return c
268 return c
270
269
271 def numcommits(self):
270 def numcommits(self):
272 return len([None for _ in self.gitopen('git rev-list --all')])
271 return len([None for _ in self.gitopen('git rev-list --all')])
273
272
274 def gettags(self):
273 def gettags(self):
275 tags = {}
274 tags = {}
276 alltags = {}
275 alltags = {}
277 fh = self.gitopen('git ls-remote --tags "%s"' % self.path,
276 fh = self.gitopen('git ls-remote --tags "%s"' % self.path,
278 err=subprocess.STDOUT)
277 err=subprocess.STDOUT)
279 prefix = 'refs/tags/'
278 prefix = 'refs/tags/'
280
279
281 # Build complete list of tags, both annotated and bare ones
280 # Build complete list of tags, both annotated and bare ones
282 for line in fh:
281 for line in fh:
283 line = line.strip()
282 line = line.strip()
284 if line.startswith("error:") or line.startswith("fatal:"):
283 if line.startswith("error:") or line.startswith("fatal:"):
285 raise util.Abort(_('cannot read tags from %s') % self.path)
284 raise util.Abort(_('cannot read tags from %s') % self.path)
286 node, tag = line.split(None, 1)
285 node, tag = line.split(None, 1)
287 if not tag.startswith(prefix):
286 if not tag.startswith(prefix):
288 continue
287 continue
289 alltags[tag[len(prefix):]] = node
288 alltags[tag[len(prefix):]] = node
290 if fh.close():
289 if fh.close():
291 raise util.Abort(_('cannot read tags from %s') % self.path)
290 raise util.Abort(_('cannot read tags from %s') % self.path)
292
291
293 # Filter out tag objects for annotated tag refs
292 # Filter out tag objects for annotated tag refs
294 for tag in alltags:
293 for tag in alltags:
295 if tag.endswith('^{}'):
294 if tag.endswith('^{}'):
296 tags[tag[:-3]] = alltags[tag]
295 tags[tag[:-3]] = alltags[tag]
297 else:
296 else:
298 if tag + '^{}' in alltags:
297 if tag + '^{}' in alltags:
299 continue
298 continue
300 else:
299 else:
301 tags[tag] = alltags[tag]
300 tags[tag] = alltags[tag]
302
301
303 return tags
302 return tags
304
303
305 def getchangedfiles(self, version, i):
304 def getchangedfiles(self, version, i):
306 changes = []
305 changes = []
307 if i is None:
306 if i is None:
308 fh = self.gitopen("git diff-tree --root -m -r %s" % version)
307 fh = self.gitopen("git diff-tree --root -m -r %s" % version)
309 for l in fh:
308 for l in fh:
310 if "\t" not in l:
309 if "\t" not in l:
311 continue
310 continue
312 m, f = l[:-1].split("\t")
311 m, f = l[:-1].split("\t")
313 changes.append(f)
312 changes.append(f)
314 else:
313 else:
315 fh = self.gitopen('git diff-tree --name-only --root -r %s '
314 fh = self.gitopen('git diff-tree --name-only --root -r %s '
316 '"%s^%s" --' % (version, version, i + 1))
315 '"%s^%s" --' % (version, version, i + 1))
317 changes = [f.rstrip('\n') for f in fh]
316 changes = [f.rstrip('\n') for f in fh]
318 if fh.close():
317 if fh.close():
319 raise util.Abort(_('cannot read changes in %s') % version)
318 raise util.Abort(_('cannot read changes in %s') % version)
320
319
321 return changes
320 return changes
322
321
323 def getbookmarks(self):
322 def getbookmarks(self):
324 bookmarks = {}
323 bookmarks = {}
325
324
326 # Interesting references in git are prefixed
325 # Interesting references in git are prefixed
327 prefix = 'refs/heads/'
326 prefix = 'refs/heads/'
328 prefixlen = len(prefix)
327 prefixlen = len(prefix)
329
328
330 # factor two commands
329 # factor two commands
331 gitcmd = { 'remote/': 'git ls-remote --heads origin',
330 gitcmd = { 'remote/': 'git ls-remote --heads origin',
332 '': 'git show-ref'}
331 '': 'git show-ref'}
333
332
334 # Origin heads
333 # Origin heads
335 for reftype in gitcmd:
334 for reftype in gitcmd:
336 try:
335 try:
337 fh = self.gitopen(gitcmd[reftype], err=subprocess.PIPE)
336 fh = self.gitopen(gitcmd[reftype], err=subprocess.PIPE)
338 for line in fh:
337 for line in fh:
339 line = line.strip()
338 line = line.strip()
340 rev, name = line.split(None, 1)
339 rev, name = line.split(None, 1)
341 if not name.startswith(prefix):
340 if not name.startswith(prefix):
342 continue
341 continue
343 name = '%s%s' % (reftype, name[prefixlen:])
342 name = '%s%s' % (reftype, name[prefixlen:])
344 bookmarks[name] = rev
343 bookmarks[name] = rev
345 except Exception:
344 except Exception:
346 pass
345 pass
347
346
348 return bookmarks
347 return bookmarks
349
348
350 def checkrevformat(self, revstr, mapname='splicemap'):
349 def checkrevformat(self, revstr, mapname='splicemap'):
351 """ git revision string is a 40 byte hex """
350 """ git revision string is a 40 byte hex """
352 self.checkhexformat(revstr, mapname)
351 self.checkhexformat(revstr, mapname)
General Comments 0
You need to be logged in to leave comments. Login now