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