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