30 return "%s = [git]%s" % (self.path, self.url) |
30 return "%s = [git]%s" % (self.path, self.url) |
31 |
31 |
32 def hgsubstate(self): |
32 def hgsubstate(self): |
33 return "%s %s" % (self.node, self.path) |
33 return "%s %s" % (self.node, self.path) |
34 |
34 |
35 class convert_git(common.converter_source): |
35 class convert_git(common.converter_source, common.commandline): |
36 # Windows does not support GIT_DIR= construct while other systems |
36 # Windows does not support GIT_DIR= construct while other systems |
37 # cannot remove environment variable. Just assume none have |
37 # cannot remove environment variable. Just assume none have |
38 # both issues. |
38 # both issues. |
39 if util.safehasattr(os, 'unsetenv'): |
39 |
40 def gitopen(self, s, err=None): |
40 def _gitcmd(self, cmd, *args, **kwargs): |
41 prevgitdir = os.environ.get('GIT_DIR') |
41 return cmd('--git-dir=%s' % self.path, *args, **kwargs) |
42 os.environ['GIT_DIR'] = self.path |
42 |
43 try: |
43 def gitrun0(self, *args, **kwargs): |
44 if err == subprocess.PIPE: |
44 return self._gitcmd(self.run0, *args, **kwargs) |
45 (stdin, stdout, stderr) = util.popen3(s) |
45 |
46 return stdout |
46 def gitrun(self, *args, **kwargs): |
47 elif err == subprocess.STDOUT: |
47 return self._gitcmd(self.run, *args, **kwargs) |
48 return self.popen_with_stderr(s) |
48 |
49 else: |
49 def gitrunlines0(self, *args, **kwargs): |
50 return util.popen(s, 'rb') |
50 return self._gitcmd(self.runlines0, *args, **kwargs) |
51 finally: |
51 |
52 if prevgitdir is None: |
52 def gitrunlines(self, *args, **kwargs): |
53 del os.environ['GIT_DIR'] |
53 return self._gitcmd(self.runlines, *args, **kwargs) |
54 else: |
54 |
55 os.environ['GIT_DIR'] = prevgitdir |
55 def gitpipe(self, *args, **kwargs): |
56 |
56 return self._gitcmd(self._run3, *args, **kwargs) |
57 def gitpipe(self, s): |
|
58 prevgitdir = os.environ.get('GIT_DIR') |
|
59 os.environ['GIT_DIR'] = self.path |
|
60 try: |
|
61 return util.popen3(s) |
|
62 finally: |
|
63 if prevgitdir is None: |
|
64 del os.environ['GIT_DIR'] |
|
65 else: |
|
66 os.environ['GIT_DIR'] = prevgitdir |
|
67 |
|
68 else: |
|
69 def gitopen(self, s, err=None): |
|
70 if err == subprocess.PIPE: |
|
71 (sin, so, se) = util.popen3('GIT_DIR=%s %s' % (self.path, s)) |
|
72 return so |
|
73 elif err == subprocess.STDOUT: |
|
74 return self.popen_with_stderr(s) |
|
75 else: |
|
76 return util.popen('GIT_DIR=%s %s' % (self.path, s), 'rb') |
|
77 |
|
78 def gitpipe(self, s): |
|
79 return util.popen3('GIT_DIR=%s %s' % (self.path, s)) |
|
80 |
|
81 def popen_with_stderr(self, s): |
|
82 p = subprocess.Popen(s, shell=True, bufsize=-1, |
|
83 close_fds=util.closefds, |
|
84 stdin=subprocess.PIPE, |
|
85 stdout=subprocess.PIPE, |
|
86 stderr=subprocess.STDOUT, |
|
87 universal_newlines=False, |
|
88 env=None) |
|
89 return p.stdout |
|
90 |
57 |
91 def gitread(self, s): |
58 def gitread(self, s): |
92 fh = self.gitopen(s) |
59 fh = self.gitopen(s) |
93 data = fh.read() |
60 data = fh.read() |
94 return data, fh.close() |
61 return data, fh.close() |
95 |
62 |
96 def __init__(self, ui, path, revs=None): |
63 def __init__(self, ui, path, revs=None): |
97 super(convert_git, self).__init__(ui, path, revs=revs) |
64 super(convert_git, self).__init__(ui, path, revs=revs) |
|
65 common.commandline.__init__(self, ui, 'git') |
98 |
66 |
99 if os.path.isdir(path + "/.git"): |
67 if os.path.isdir(path + "/.git"): |
100 path += "/.git" |
68 path += "/.git" |
101 if not os.path.exists(path + "/objects"): |
69 if not os.path.exists(path + "/objects"): |
102 raise common.NoRepo(_("%s does not look like a Git repository") % |
70 raise common.NoRepo(_("%s does not look like a Git repository") % |
105 # The default value (50) is based on the default for 'git diff'. |
73 # The default value (50) is based on the default for 'git diff'. |
106 similarity = ui.configint('convert', 'git.similarity', default=50) |
74 similarity = ui.configint('convert', 'git.similarity', default=50) |
107 if similarity < 0 or similarity > 100: |
75 if similarity < 0 or similarity > 100: |
108 raise error.Abort(_('similarity must be between 0 and 100')) |
76 raise error.Abort(_('similarity must be between 0 and 100')) |
109 if similarity > 0: |
77 if similarity > 0: |
110 self.simopt = '-C%d%%' % similarity |
78 self.simopt = ['-C%d%%' % similarity] |
111 findcopiesharder = ui.configbool('convert', 'git.findcopiesharder', |
79 findcopiesharder = ui.configbool('convert', 'git.findcopiesharder', |
112 False) |
80 False) |
113 if findcopiesharder: |
81 if findcopiesharder: |
114 self.simopt += ' --find-copies-harder' |
82 self.simopt.append('--find-copies-harder') |
115 else: |
83 else: |
116 self.simopt = '' |
84 self.simopt = [] |
117 |
85 |
118 common.checktool('git', 'git') |
86 common.checktool('git', 'git') |
119 |
87 |
120 self.path = path |
88 self.path = path |
121 self.submodules = [] |
89 self.submodules = [] |
122 |
90 |
123 self.catfilepipe = self.gitpipe('git cat-file --batch') |
91 self.catfilepipe = self.gitpipe('cat-file', '--batch') |
124 |
92 |
125 def after(self): |
93 def after(self): |
126 for f in self.catfilepipe: |
94 for f in self.catfilepipe: |
127 f.close() |
95 f.close() |
128 |
96 |
129 def getheads(self): |
97 def getheads(self): |
130 if not self.revs: |
98 if not self.revs: |
131 heads, ret = self.gitread('git rev-parse --branches --remotes') |
99 output, status = self.gitrun('rev-parse', '--branches', '--remotes') |
132 heads = heads.splitlines() |
100 heads = output.splitlines() |
133 if ret: |
101 if status: |
134 raise error.Abort(_('cannot retrieve git heads')) |
102 raise error.Abort(_('cannot retrieve git heads')) |
135 else: |
103 else: |
136 heads = [] |
104 heads = [] |
137 for rev in self.revs: |
105 for rev in self.revs: |
138 rawhead, ret = self.gitread("git rev-parse --verify %s" % rev) |
106 rawhead, ret = self.gitrun('rev-parse', '--verify', rev) |
139 heads.append(rawhead[:-1]) |
107 heads.append(rawhead[:-1]) |
140 if ret: |
108 if ret: |
141 raise error.Abort(_('cannot retrieve git head "%s"') % rev) |
109 raise error.Abort(_('cannot retrieve git head "%s"') % rev) |
142 return heads |
110 return heads |
143 |
111 |
193 s = c[sec] |
161 s = c[sec] |
194 if 'url' in s and 'path' in s: |
162 if 'url' in s and 'path' in s: |
195 self.submodules.append(submodule(s['path'], '', s['url'])) |
163 self.submodules.append(submodule(s['path'], '', s['url'])) |
196 |
164 |
197 def retrievegitmodules(self, version): |
165 def retrievegitmodules(self, version): |
198 modules, ret = self.gitread("git show %s:%s" % (version, '.gitmodules')) |
166 modules, ret = self.gitrun('show', '%s:%s' % (version, '.gitmodules')) |
199 if ret: |
167 if ret: |
200 # This can happen if a file is in the repo that has permissions |
168 # This can happen if a file is in the repo that has permissions |
201 # 160000, but there is no .gitmodules file. |
169 # 160000, but there is no .gitmodules file. |
202 self.ui.warn(_("warning: cannot read submodules config file in " |
170 self.ui.warn(_("warning: cannot read submodules config file in " |
203 "%s\n") % version) |
171 "%s\n") % version) |
209 self.ui.warn(_("warning: unable to parse .gitmodules in %s\n") |
177 self.ui.warn(_("warning: unable to parse .gitmodules in %s\n") |
210 % version) |
178 % version) |
211 return |
179 return |
212 |
180 |
213 for m in self.submodules: |
181 for m in self.submodules: |
214 node, ret = self.gitread("git rev-parse %s:%s" % (version, m.path)) |
182 node, ret = self.gitrun('rev-parse', '%s:%s' % (version, m.path)) |
215 if ret: |
183 if ret: |
216 continue |
184 continue |
217 m.node = node.strip() |
185 m.node = node.strip() |
218 |
186 |
219 def getchanges(self, version, full): |
187 def getchanges(self, version, full): |
220 if full: |
188 if full: |
221 raise error.Abort(_("convert from git does not support --full")) |
189 raise error.Abort(_("convert from git does not support --full")) |
222 self.modecache = {} |
190 self.modecache = {} |
223 fh = self.gitopen("git diff-tree -z --root -m -r %s %s" % ( |
191 cmd = ['diff-tree','-z', '--root', '-m', '-r'] + self.simopt + [version] |
224 self.simopt, version)) |
192 output, status = self.gitrun(*cmd) |
|
193 if status: |
|
194 raise error.Abort(_('cannot read changes in %s') % version) |
225 changes = [] |
195 changes = [] |
226 copies = {} |
196 copies = {} |
227 seen = set() |
197 seen = set() |
228 entry = None |
198 entry = None |
229 subexists = [False] |
199 subexists = [False] |
230 subdeleted = [False] |
200 subdeleted = [False] |
231 difftree = fh.read().split('\x00') |
201 difftree = output.split('\x00') |
232 lcount = len(difftree) |
202 lcount = len(difftree) |
233 i = 0 |
203 i = 0 |
234 |
204 |
235 skipsubmodules = self.ui.configbool('convert', 'git.skipsubmodules', |
205 skipsubmodules = self.ui.configbool('convert', 'git.skipsubmodules', |
236 False) |
206 False) |
336 desc=message, |
304 desc=message, |
337 rev=version) |
305 rev=version) |
338 return c |
306 return c |
339 |
307 |
340 def numcommits(self): |
308 def numcommits(self): |
341 return len([None for _ in self.gitopen('git rev-list --all')]) |
309 output, ret = self.gitrunlines('rev-list', '--all') |
|
310 if ret: |
|
311 raise error.Abort(_('cannot retrieve number of commits in %s') \ |
|
312 % self.path) |
|
313 return len(output) |
342 |
314 |
343 def gettags(self): |
315 def gettags(self): |
344 tags = {} |
316 tags = {} |
345 alltags = {} |
317 alltags = {} |
346 fh = self.gitopen('git ls-remote --tags "%s"' % self.path, |
318 output, status = self.gitrunlines('ls-remote', '--tags', self.path) |
347 err=subprocess.STDOUT) |
319 |
|
320 if status: |
|
321 raise error.Abort(_('cannot read tags from %s') % self.path) |
348 prefix = 'refs/tags/' |
322 prefix = 'refs/tags/' |
349 |
323 |
350 # Build complete list of tags, both annotated and bare ones |
324 # Build complete list of tags, both annotated and bare ones |
351 for line in fh: |
325 for line in output: |
352 line = line.strip() |
326 line = line.strip() |
353 if line.startswith("error:") or line.startswith("fatal:"): |
327 if line.startswith("error:") or line.startswith("fatal:"): |
354 raise error.Abort(_('cannot read tags from %s') % self.path) |
328 raise error.Abort(_('cannot read tags from %s') % self.path) |
355 node, tag = line.split(None, 1) |
329 node, tag = line.split(None, 1) |
356 if not tag.startswith(prefix): |
330 if not tag.startswith(prefix): |
357 continue |
331 continue |
358 alltags[tag[len(prefix):]] = node |
332 alltags[tag[len(prefix):]] = node |
359 if fh.close(): |
|
360 raise error.Abort(_('cannot read tags from %s') % self.path) |
|
361 |
333 |
362 # Filter out tag objects for annotated tag refs |
334 # Filter out tag objects for annotated tag refs |
363 for tag in alltags: |
335 for tag in alltags: |
364 if tag.endswith('^{}'): |
336 if tag.endswith('^{}'): |
365 tags[tag[:-3]] = alltags[tag] |
337 tags[tag[:-3]] = alltags[tag] |
372 return tags |
344 return tags |
373 |
345 |
374 def getchangedfiles(self, version, i): |
346 def getchangedfiles(self, version, i): |
375 changes = [] |
347 changes = [] |
376 if i is None: |
348 if i is None: |
377 fh = self.gitopen("git diff-tree --root -m -r %s" % version) |
349 output, status = self.gitrunlines('diff-tree', '--root', '-m', |
378 for l in fh: |
350 '-r', version) |
|
351 if status: |
|
352 raise error.Abort(_('cannot read changes in %s') % version) |
|
353 for l in output: |
379 if "\t" not in l: |
354 if "\t" not in l: |
380 continue |
355 continue |
381 m, f = l[:-1].split("\t") |
356 m, f = l[:-1].split("\t") |
382 changes.append(f) |
357 changes.append(f) |
383 else: |
358 else: |
384 fh = self.gitopen('git diff-tree --name-only --root -r %s ' |
359 output, status = self.gitrunlines('diff-tree', '--name-only', |
385 '"%s^%s" --' % (version, version, i + 1)) |
360 '--root', '-r', version, |
386 changes = [f.rstrip('\n') for f in fh] |
361 '%s^%s' % (version, i + 1), '--') |
387 if fh.close(): |
362 changes = [f.rstrip('\n') for f in output] |
388 raise error.Abort(_('cannot read changes in %s') % version) |
|
389 |
363 |
390 return changes |
364 return changes |
391 |
365 |
392 def getbookmarks(self): |
366 def getbookmarks(self): |
393 bookmarks = {} |
367 bookmarks = {} |