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