##// END OF EJS Templates
convert: for git's getchanges, use explicit index for iteration...
Siddharth Agarwal -
r22467:333d6547 default
parent child Browse files
Show More
@@ -1,347 +1,352
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 for l in fh.read().split('\x00'):
193 difftree = fh.read().split('\x00')
194 lcount = len(difftree)
195 i = 0
196 while i < lcount:
197 l = difftree[i]
198 i += 1
194 if not entry:
199 if not entry:
195 if not l.startswith(':'):
200 if not l.startswith(':'):
196 continue
201 continue
197 entry = l
202 entry = l
198 continue
203 continue
199 f = l
204 f = l
200 if f not in seen:
205 if f not in seen:
201 seen.add(f)
206 seen.add(f)
202 entry = entry.split()
207 entry = entry.split()
203 h = entry[3]
208 h = entry[3]
204 p = (entry[1] == "100755")
209 p = (entry[1] == "100755")
205 s = (entry[1] == "120000")
210 s = (entry[1] == "120000")
206
211
207 if f == '.gitmodules':
212 if f == '.gitmodules':
208 subexists = True
213 subexists = True
209 if entry[4] == 'D':
214 if entry[4] == 'D':
210 subdeleted = True
215 subdeleted = True
211 changes.append(('.hgsub', hex(nullid)))
216 changes.append(('.hgsub', hex(nullid)))
212 else:
217 else:
213 changes.append(('.hgsub', ''))
218 changes.append(('.hgsub', ''))
214 elif entry[1] == '160000' or entry[0] == ':160000':
219 elif entry[1] == '160000' or entry[0] == ':160000':
215 subexists = True
220 subexists = True
216 else:
221 else:
217 self.modecache[(f, h)] = (p and "x") or (s and "l") or ""
222 self.modecache[(f, h)] = (p and "x") or (s and "l") or ""
218 changes.append((f, h))
223 changes.append((f, h))
219 entry = None
224 entry = None
220 if fh.close():
225 if fh.close():
221 raise util.Abort(_('cannot read changes in %s') % version)
226 raise util.Abort(_('cannot read changes in %s') % version)
222
227
223 if subexists:
228 if subexists:
224 if subdeleted:
229 if subdeleted:
225 changes.append(('.hgsubstate', hex(nullid)))
230 changes.append(('.hgsubstate', hex(nullid)))
226 else:
231 else:
227 self.retrievegitmodules(version)
232 self.retrievegitmodules(version)
228 changes.append(('.hgsubstate', ''))
233 changes.append(('.hgsubstate', ''))
229 return (changes, {})
234 return (changes, {})
230
235
231 def getcommit(self, version):
236 def getcommit(self, version):
232 c = self.catfile(version, "commit") # read the commit hash
237 c = self.catfile(version, "commit") # read the commit hash
233 end = c.find("\n\n")
238 end = c.find("\n\n")
234 message = c[end + 2:]
239 message = c[end + 2:]
235 message = self.recode(message)
240 message = self.recode(message)
236 l = c[:end].splitlines()
241 l = c[:end].splitlines()
237 parents = []
242 parents = []
238 author = committer = None
243 author = committer = None
239 for e in l[1:]:
244 for e in l[1:]:
240 n, v = e.split(" ", 1)
245 n, v = e.split(" ", 1)
241 if n == "author":
246 if n == "author":
242 p = v.split()
247 p = v.split()
243 tm, tz = p[-2:]
248 tm, tz = p[-2:]
244 author = " ".join(p[:-2])
249 author = " ".join(p[:-2])
245 if author[0] == "<": author = author[1:-1]
250 if author[0] == "<": author = author[1:-1]
246 author = self.recode(author)
251 author = self.recode(author)
247 if n == "committer":
252 if n == "committer":
248 p = v.split()
253 p = v.split()
249 tm, tz = p[-2:]
254 tm, tz = p[-2:]
250 committer = " ".join(p[:-2])
255 committer = " ".join(p[:-2])
251 if committer[0] == "<": committer = committer[1:-1]
256 if committer[0] == "<": committer = committer[1:-1]
252 committer = self.recode(committer)
257 committer = self.recode(committer)
253 if n == "parent":
258 if n == "parent":
254 parents.append(v)
259 parents.append(v)
255
260
256 if committer and committer != author:
261 if committer and committer != author:
257 message += "\ncommitter: %s\n" % committer
262 message += "\ncommitter: %s\n" % committer
258 tzs, tzh, tzm = tz[-5:-4] + "1", tz[-4:-2], tz[-2:]
263 tzs, tzh, tzm = tz[-5:-4] + "1", tz[-4:-2], tz[-2:]
259 tz = -int(tzs) * (int(tzh) * 3600 + int(tzm))
264 tz = -int(tzs) * (int(tzh) * 3600 + int(tzm))
260 date = tm + " " + str(tz)
265 date = tm + " " + str(tz)
261
266
262 c = commit(parents=parents, date=date, author=author, desc=message,
267 c = commit(parents=parents, date=date, author=author, desc=message,
263 rev=version)
268 rev=version)
264 return c
269 return c
265
270
266 def numcommits(self):
271 def numcommits(self):
267 return len([None for _ in self.gitopen('git rev-list --all')])
272 return len([None for _ in self.gitopen('git rev-list --all')])
268
273
269 def gettags(self):
274 def gettags(self):
270 tags = {}
275 tags = {}
271 alltags = {}
276 alltags = {}
272 fh = self.gitopen('git ls-remote --tags "%s"' % self.path,
277 fh = self.gitopen('git ls-remote --tags "%s"' % self.path,
273 err=subprocess.STDOUT)
278 err=subprocess.STDOUT)
274 prefix = 'refs/tags/'
279 prefix = 'refs/tags/'
275
280
276 # Build complete list of tags, both annotated and bare ones
281 # Build complete list of tags, both annotated and bare ones
277 for line in fh:
282 for line in fh:
278 line = line.strip()
283 line = line.strip()
279 if line.startswith("error:") or line.startswith("fatal:"):
284 if line.startswith("error:") or line.startswith("fatal:"):
280 raise util.Abort(_('cannot read tags from %s') % self.path)
285 raise util.Abort(_('cannot read tags from %s') % self.path)
281 node, tag = line.split(None, 1)
286 node, tag = line.split(None, 1)
282 if not tag.startswith(prefix):
287 if not tag.startswith(prefix):
283 continue
288 continue
284 alltags[tag[len(prefix):]] = node
289 alltags[tag[len(prefix):]] = node
285 if fh.close():
290 if fh.close():
286 raise util.Abort(_('cannot read tags from %s') % self.path)
291 raise util.Abort(_('cannot read tags from %s') % self.path)
287
292
288 # Filter out tag objects for annotated tag refs
293 # Filter out tag objects for annotated tag refs
289 for tag in alltags:
294 for tag in alltags:
290 if tag.endswith('^{}'):
295 if tag.endswith('^{}'):
291 tags[tag[:-3]] = alltags[tag]
296 tags[tag[:-3]] = alltags[tag]
292 else:
297 else:
293 if tag + '^{}' in alltags:
298 if tag + '^{}' in alltags:
294 continue
299 continue
295 else:
300 else:
296 tags[tag] = alltags[tag]
301 tags[tag] = alltags[tag]
297
302
298 return tags
303 return tags
299
304
300 def getchangedfiles(self, version, i):
305 def getchangedfiles(self, version, i):
301 changes = []
306 changes = []
302 if i is None:
307 if i is None:
303 fh = self.gitopen("git diff-tree --root -m -r %s" % version)
308 fh = self.gitopen("git diff-tree --root -m -r %s" % version)
304 for l in fh:
309 for l in fh:
305 if "\t" not in l:
310 if "\t" not in l:
306 continue
311 continue
307 m, f = l[:-1].split("\t")
312 m, f = l[:-1].split("\t")
308 changes.append(f)
313 changes.append(f)
309 else:
314 else:
310 fh = self.gitopen('git diff-tree --name-only --root -r %s '
315 fh = self.gitopen('git diff-tree --name-only --root -r %s '
311 '"%s^%s" --' % (version, version, i + 1))
316 '"%s^%s" --' % (version, version, i + 1))
312 changes = [f.rstrip('\n') for f in fh]
317 changes = [f.rstrip('\n') for f in fh]
313 if fh.close():
318 if fh.close():
314 raise util.Abort(_('cannot read changes in %s') % version)
319 raise util.Abort(_('cannot read changes in %s') % version)
315
320
316 return changes
321 return changes
317
322
318 def getbookmarks(self):
323 def getbookmarks(self):
319 bookmarks = {}
324 bookmarks = {}
320
325
321 # Interesting references in git are prefixed
326 # Interesting references in git are prefixed
322 prefix = 'refs/heads/'
327 prefix = 'refs/heads/'
323 prefixlen = len(prefix)
328 prefixlen = len(prefix)
324
329
325 # factor two commands
330 # factor two commands
326 gitcmd = { 'remote/': 'git ls-remote --heads origin',
331 gitcmd = { 'remote/': 'git ls-remote --heads origin',
327 '': 'git show-ref'}
332 '': 'git show-ref'}
328
333
329 # Origin heads
334 # Origin heads
330 for reftype in gitcmd:
335 for reftype in gitcmd:
331 try:
336 try:
332 fh = self.gitopen(gitcmd[reftype], err=subprocess.PIPE)
337 fh = self.gitopen(gitcmd[reftype], err=subprocess.PIPE)
333 for line in fh:
338 for line in fh:
334 line = line.strip()
339 line = line.strip()
335 rev, name = line.split(None, 1)
340 rev, name = line.split(None, 1)
336 if not name.startswith(prefix):
341 if not name.startswith(prefix):
337 continue
342 continue
338 name = '%s%s' % (reftype, name[prefixlen:])
343 name = '%s%s' % (reftype, name[prefixlen:])
339 bookmarks[name] = rev
344 bookmarks[name] = rev
340 except Exception:
345 except Exception:
341 pass
346 pass
342
347
343 return bookmarks
348 return bookmarks
344
349
345 def checkrevformat(self, revstr, mapname='splicemap'):
350 def checkrevformat(self, revstr, mapname='splicemap'):
346 """ git revision string is a 40 byte hex """
351 """ git revision string is a 40 byte hex """
347 self.checkhexformat(revstr, mapname)
352 self.checkhexformat(revstr, mapname)
General Comments 0
You need to be logged in to leave comments. Login now