##// END OF EJS Templates
py3: use b'%d' to convert int to bytes instead of str()...
Pulkit Goyal -
r37599:5b836a4c default
parent child Browse files
Show More
@@ -1,478 +1,478 b''
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 from __future__ import absolute_import
8 8
9 9 import os
10 10
11 11 from mercurial.i18n import _
12 12 from mercurial import (
13 13 config,
14 14 error,
15 15 node as nodemod,
16 16 )
17 17
18 18 from . import (
19 19 common,
20 20 )
21 21
22 22 class submodule(object):
23 23 def __init__(self, path, node, url):
24 24 self.path = path
25 25 self.node = node
26 26 self.url = url
27 27
28 28 def hgsub(self):
29 29 return "%s = [git]%s" % (self.path, self.url)
30 30
31 31 def hgsubstate(self):
32 32 return "%s %s" % (self.node, self.path)
33 33
34 34 # Keys in extra fields that should not be copied if the user requests.
35 35 bannedextrakeys = {
36 36 # Git commit object built-ins.
37 37 'tree',
38 38 'parent',
39 39 'author',
40 40 'committer',
41 41 # Mercurial built-ins.
42 42 'branch',
43 43 'close',
44 44 }
45 45
46 46 class convert_git(common.converter_source, common.commandline):
47 47 # Windows does not support GIT_DIR= construct while other systems
48 48 # cannot remove environment variable. Just assume none have
49 49 # both issues.
50 50
51 51 def _gitcmd(self, cmd, *args, **kwargs):
52 52 return cmd('--git-dir=%s' % self.path, *args, **kwargs)
53 53
54 54 def gitrun0(self, *args, **kwargs):
55 55 return self._gitcmd(self.run0, *args, **kwargs)
56 56
57 57 def gitrun(self, *args, **kwargs):
58 58 return self._gitcmd(self.run, *args, **kwargs)
59 59
60 60 def gitrunlines0(self, *args, **kwargs):
61 61 return self._gitcmd(self.runlines0, *args, **kwargs)
62 62
63 63 def gitrunlines(self, *args, **kwargs):
64 64 return self._gitcmd(self.runlines, *args, **kwargs)
65 65
66 66 def gitpipe(self, *args, **kwargs):
67 67 return self._gitcmd(self._run3, *args, **kwargs)
68 68
69 69 def __init__(self, ui, repotype, path, revs=None):
70 70 super(convert_git, self).__init__(ui, repotype, path, revs=revs)
71 71 common.commandline.__init__(self, ui, 'git')
72 72
73 73 # Pass an absolute path to git to prevent from ever being interpreted
74 74 # as a URL
75 75 path = os.path.abspath(path)
76 76
77 77 if os.path.isdir(path + "/.git"):
78 78 path += "/.git"
79 79 if not os.path.exists(path + "/objects"):
80 80 raise common.NoRepo(_("%s does not look like a Git repository") %
81 81 path)
82 82
83 83 # The default value (50) is based on the default for 'git diff'.
84 84 similarity = ui.configint('convert', 'git.similarity')
85 85 if similarity < 0 or similarity > 100:
86 86 raise error.Abort(_('similarity must be between 0 and 100'))
87 87 if similarity > 0:
88 88 self.simopt = ['-C%d%%' % similarity]
89 89 findcopiesharder = ui.configbool('convert', 'git.findcopiesharder')
90 90 if findcopiesharder:
91 91 self.simopt.append('--find-copies-harder')
92 92
93 93 renamelimit = ui.configint('convert', 'git.renamelimit')
94 94 self.simopt.append('-l%d' % renamelimit)
95 95 else:
96 96 self.simopt = []
97 97
98 98 common.checktool('git', 'git')
99 99
100 100 self.path = path
101 101 self.submodules = []
102 102
103 103 self.catfilepipe = self.gitpipe('cat-file', '--batch')
104 104
105 105 self.copyextrakeys = self.ui.configlist('convert', 'git.extrakeys')
106 106 banned = set(self.copyextrakeys) & bannedextrakeys
107 107 if banned:
108 108 raise error.Abort(_('copying of extra key is forbidden: %s') %
109 109 _(', ').join(sorted(banned)))
110 110
111 111 committeractions = self.ui.configlist('convert', 'git.committeractions')
112 112
113 113 messagedifferent = None
114 114 messagealways = None
115 115 for a in committeractions:
116 116 if a.startswith(('messagedifferent', 'messagealways')):
117 117 k = a
118 118 v = None
119 119 if '=' in a:
120 120 k, v = a.split('=', 1)
121 121
122 122 if k == 'messagedifferent':
123 123 messagedifferent = v or 'committer:'
124 124 elif k == 'messagealways':
125 125 messagealways = v or 'committer:'
126 126
127 127 if messagedifferent and messagealways:
128 128 raise error.Abort(_('committeractions cannot define both '
129 129 'messagedifferent and messagealways'))
130 130
131 131 dropcommitter = 'dropcommitter' in committeractions
132 132 replaceauthor = 'replaceauthor' in committeractions
133 133
134 134 if dropcommitter and replaceauthor:
135 135 raise error.Abort(_('committeractions cannot define both '
136 136 'dropcommitter and replaceauthor'))
137 137
138 138 if dropcommitter and messagealways:
139 139 raise error.Abort(_('committeractions cannot define both '
140 140 'dropcommitter and messagealways'))
141 141
142 142 if not messagedifferent and not messagealways:
143 143 messagedifferent = 'committer:'
144 144
145 145 self.committeractions = {
146 146 'dropcommitter': dropcommitter,
147 147 'replaceauthor': replaceauthor,
148 148 'messagedifferent': messagedifferent,
149 149 'messagealways': messagealways,
150 150 }
151 151
152 152 def after(self):
153 153 for f in self.catfilepipe:
154 154 f.close()
155 155
156 156 def getheads(self):
157 157 if not self.revs:
158 158 output, status = self.gitrun('rev-parse', '--branches', '--remotes')
159 159 heads = output.splitlines()
160 160 if status:
161 161 raise error.Abort(_('cannot retrieve git heads'))
162 162 else:
163 163 heads = []
164 164 for rev in self.revs:
165 165 rawhead, ret = self.gitrun('rev-parse', '--verify', rev)
166 166 heads.append(rawhead[:-1])
167 167 if ret:
168 168 raise error.Abort(_('cannot retrieve git head "%s"') % rev)
169 169 return heads
170 170
171 171 def catfile(self, rev, ftype):
172 172 if rev == nodemod.nullhex:
173 173 raise IOError
174 174 self.catfilepipe[0].write(rev+'\n')
175 175 self.catfilepipe[0].flush()
176 176 info = self.catfilepipe[1].readline().split()
177 177 if info[1] != ftype:
178 178 raise error.Abort(_('cannot read %r object at %s') % (ftype, rev))
179 179 size = int(info[2])
180 180 data = self.catfilepipe[1].read(size)
181 181 if len(data) < size:
182 182 raise error.Abort(_('cannot read %r object at %s: unexpected size')
183 183 % (ftype, rev))
184 184 # read the trailing newline
185 185 self.catfilepipe[1].read(1)
186 186 return data
187 187
188 188 def getfile(self, name, rev):
189 189 if rev == nodemod.nullhex:
190 190 return None, None
191 191 if name == '.hgsub':
192 192 data = '\n'.join([m.hgsub() for m in self.submoditer()])
193 193 mode = ''
194 194 elif name == '.hgsubstate':
195 195 data = '\n'.join([m.hgsubstate() for m in self.submoditer()])
196 196 mode = ''
197 197 else:
198 198 data = self.catfile(rev, "blob")
199 199 mode = self.modecache[(name, rev)]
200 200 return data, mode
201 201
202 202 def submoditer(self):
203 203 null = nodemod.nullhex
204 204 for m in sorted(self.submodules, key=lambda p: p.path):
205 205 if m.node != null:
206 206 yield m
207 207
208 208 def parsegitmodules(self, content):
209 209 """Parse the formatted .gitmodules file, example file format:
210 210 [submodule "sub"]\n
211 211 \tpath = sub\n
212 212 \turl = git://giturl\n
213 213 """
214 214 self.submodules = []
215 215 c = config.config()
216 216 # Each item in .gitmodules starts with whitespace that cant be parsed
217 217 c.parse('.gitmodules', '\n'.join(line.strip() for line in
218 218 content.split('\n')))
219 219 for sec in c.sections():
220 220 s = c[sec]
221 221 if 'url' in s and 'path' in s:
222 222 self.submodules.append(submodule(s['path'], '', s['url']))
223 223
224 224 def retrievegitmodules(self, version):
225 225 modules, ret = self.gitrun('show', '%s:%s' % (version, '.gitmodules'))
226 226 if ret:
227 227 # This can happen if a file is in the repo that has permissions
228 228 # 160000, but there is no .gitmodules file.
229 229 self.ui.warn(_("warning: cannot read submodules config file in "
230 230 "%s\n") % version)
231 231 return
232 232
233 233 try:
234 234 self.parsegitmodules(modules)
235 235 except error.ParseError:
236 236 self.ui.warn(_("warning: unable to parse .gitmodules in %s\n")
237 237 % version)
238 238 return
239 239
240 240 for m in self.submodules:
241 241 node, ret = self.gitrun('rev-parse', '%s:%s' % (version, m.path))
242 242 if ret:
243 243 continue
244 244 m.node = node.strip()
245 245
246 246 def getchanges(self, version, full):
247 247 if full:
248 248 raise error.Abort(_("convert from git does not support --full"))
249 249 self.modecache = {}
250 250 cmd = ['diff-tree','-z', '--root', '-m', '-r'] + self.simopt + [version]
251 251 output, status = self.gitrun(*cmd)
252 252 if status:
253 253 raise error.Abort(_('cannot read changes in %s') % version)
254 254 changes = []
255 255 copies = {}
256 256 seen = set()
257 257 entry = None
258 258 subexists = [False]
259 259 subdeleted = [False]
260 260 difftree = output.split('\x00')
261 261 lcount = len(difftree)
262 262 i = 0
263 263
264 264 skipsubmodules = self.ui.configbool('convert', 'git.skipsubmodules')
265 265 def add(entry, f, isdest):
266 266 seen.add(f)
267 267 h = entry[3]
268 268 p = (entry[1] == "100755")
269 269 s = (entry[1] == "120000")
270 270 renamesource = (not isdest and entry[4][0] == 'R')
271 271
272 272 if f == '.gitmodules':
273 273 if skipsubmodules:
274 274 return
275 275
276 276 subexists[0] = True
277 277 if entry[4] == 'D' or renamesource:
278 278 subdeleted[0] = True
279 279 changes.append(('.hgsub', nodemod.nullhex))
280 280 else:
281 281 changes.append(('.hgsub', ''))
282 282 elif entry[1] == '160000' or entry[0] == ':160000':
283 283 if not skipsubmodules:
284 284 subexists[0] = True
285 285 else:
286 286 if renamesource:
287 287 h = nodemod.nullhex
288 288 self.modecache[(f, h)] = (p and "x") or (s and "l") or ""
289 289 changes.append((f, h))
290 290
291 291 while i < lcount:
292 292 l = difftree[i]
293 293 i += 1
294 294 if not entry:
295 295 if not l.startswith(':'):
296 296 continue
297 297 entry = l.split()
298 298 continue
299 299 f = l
300 300 if entry[4][0] == 'C':
301 301 copysrc = f
302 302 copydest = difftree[i]
303 303 i += 1
304 304 f = copydest
305 305 copies[copydest] = copysrc
306 306 if f not in seen:
307 307 add(entry, f, False)
308 308 # A file can be copied multiple times, or modified and copied
309 309 # simultaneously. So f can be repeated even if fdest isn't.
310 310 if entry[4][0] == 'R':
311 311 # rename: next line is the destination
312 312 fdest = difftree[i]
313 313 i += 1
314 314 if fdest not in seen:
315 315 add(entry, fdest, True)
316 316 # .gitmodules isn't imported at all, so it being copied to
317 317 # and fro doesn't really make sense
318 318 if f != '.gitmodules' and fdest != '.gitmodules':
319 319 copies[fdest] = f
320 320 entry = None
321 321
322 322 if subexists[0]:
323 323 if subdeleted[0]:
324 324 changes.append(('.hgsubstate', nodemod.nullhex))
325 325 else:
326 326 self.retrievegitmodules(version)
327 327 changes.append(('.hgsubstate', ''))
328 328 return (changes, copies, set())
329 329
330 330 def getcommit(self, version):
331 331 c = self.catfile(version, "commit") # read the commit hash
332 332 end = c.find("\n\n")
333 333 message = c[end + 2:]
334 334 message = self.recode(message)
335 335 l = c[:end].splitlines()
336 336 parents = []
337 337 author = committer = None
338 338 extra = {}
339 339 for e in l[1:]:
340 340 n, v = e.split(" ", 1)
341 341 if n == "author":
342 342 p = v.split()
343 343 tm, tz = p[-2:]
344 344 author = " ".join(p[:-2])
345 345 if author[0] == "<":
346 346 author = author[1:-1]
347 347 author = self.recode(author)
348 348 if n == "committer":
349 349 p = v.split()
350 350 tm, tz = p[-2:]
351 351 committer = " ".join(p[:-2])
352 352 if committer[0] == "<":
353 353 committer = committer[1:-1]
354 354 committer = self.recode(committer)
355 355 if n == "parent":
356 356 parents.append(v)
357 357 if n in self.copyextrakeys:
358 358 extra[n] = v
359 359
360 360 if self.committeractions['dropcommitter']:
361 361 committer = None
362 362 elif self.committeractions['replaceauthor']:
363 363 author = committer
364 364
365 365 if committer:
366 366 messagealways = self.committeractions['messagealways']
367 367 messagedifferent = self.committeractions['messagedifferent']
368 368 if messagealways:
369 369 message += '\n%s %s\n' % (messagealways, committer)
370 370 elif messagedifferent and author != committer:
371 371 message += '\n%s %s\n' % (messagedifferent, committer)
372 372
373 373 tzs, tzh, tzm = tz[-5:-4] + "1", tz[-4:-2], tz[-2:]
374 374 tz = -int(tzs) * (int(tzh) * 3600 + int(tzm))
375 date = tm + " " + str(tz)
375 date = tm + " " + (b"%d" % tz)
376 376 saverev = self.ui.configbool('convert', 'git.saverev')
377 377
378 378 c = common.commit(parents=parents, date=date, author=author,
379 379 desc=message,
380 380 rev=version,
381 381 extra=extra,
382 382 saverev=saverev)
383 383 return c
384 384
385 385 def numcommits(self):
386 386 output, ret = self.gitrunlines('rev-list', '--all')
387 387 if ret:
388 388 raise error.Abort(_('cannot retrieve number of commits in %s') \
389 389 % self.path)
390 390 return len(output)
391 391
392 392 def gettags(self):
393 393 tags = {}
394 394 alltags = {}
395 395 output, status = self.gitrunlines('ls-remote', '--tags', self.path)
396 396
397 397 if status:
398 398 raise error.Abort(_('cannot read tags from %s') % self.path)
399 399 prefix = 'refs/tags/'
400 400
401 401 # Build complete list of tags, both annotated and bare ones
402 402 for line in output:
403 403 line = line.strip()
404 404 if line.startswith("error:") or line.startswith("fatal:"):
405 405 raise error.Abort(_('cannot read tags from %s') % self.path)
406 406 node, tag = line.split(None, 1)
407 407 if not tag.startswith(prefix):
408 408 continue
409 409 alltags[tag[len(prefix):]] = node
410 410
411 411 # Filter out tag objects for annotated tag refs
412 412 for tag in alltags:
413 413 if tag.endswith('^{}'):
414 414 tags[tag[:-3]] = alltags[tag]
415 415 else:
416 416 if tag + '^{}' in alltags:
417 417 continue
418 418 else:
419 419 tags[tag] = alltags[tag]
420 420
421 421 return tags
422 422
423 423 def getchangedfiles(self, version, i):
424 424 changes = []
425 425 if i is None:
426 426 output, status = self.gitrunlines('diff-tree', '--root', '-m',
427 427 '-r', version)
428 428 if status:
429 429 raise error.Abort(_('cannot read changes in %s') % version)
430 430 for l in output:
431 431 if "\t" not in l:
432 432 continue
433 433 m, f = l[:-1].split("\t")
434 434 changes.append(f)
435 435 else:
436 436 output, status = self.gitrunlines('diff-tree', '--name-only',
437 437 '--root', '-r', version,
438 438 '%s^%d' % (version, i + 1), '--')
439 439 if status:
440 440 raise error.Abort(_('cannot read changes in %s') % version)
441 441 changes = [f.rstrip('\n') for f in output]
442 442
443 443 return changes
444 444
445 445 def getbookmarks(self):
446 446 bookmarks = {}
447 447
448 448 # Handle local and remote branches
449 449 remoteprefix = self.ui.config('convert', 'git.remoteprefix')
450 450 reftypes = [
451 451 # (git prefix, hg prefix)
452 452 ('refs/remotes/origin/', remoteprefix + '/'),
453 453 ('refs/heads/', '')
454 454 ]
455 455
456 456 exclude = {
457 457 'refs/remotes/origin/HEAD',
458 458 }
459 459
460 460 try:
461 461 output, status = self.gitrunlines('show-ref')
462 462 for line in output:
463 463 line = line.strip()
464 464 rev, name = line.split(None, 1)
465 465 # Process each type of branch
466 466 for gitprefix, hgprefix in reftypes:
467 467 if not name.startswith(gitprefix) or name in exclude:
468 468 continue
469 469 name = '%s%s' % (hgprefix, name[len(gitprefix):])
470 470 bookmarks[name] = rev
471 471 except Exception:
472 472 pass
473 473
474 474 return bookmarks
475 475
476 476 def checkrevformat(self, revstr, mapname='splicemap'):
477 477 """ git revision string is a 40 byte hex """
478 478 self.checkhexformat(revstr, mapname)
General Comments 0
You need to be logged in to leave comments. Login now