##// END OF EJS Templates
convert: use git diff-tree -Cn% instead of --find-copies=n% for older git...
Thomas Arendsen Hein -
r23206:a7f25a31 stable
parent child Browse files
Show More
@@ -1,385 +1,385 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
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 # The default value (50) is based on the default for 'git diff'.
98 98 similarity = ui.configint('convert', 'git.similarity', default=50)
99 99 if similarity < 0 or similarity > 100:
100 100 raise util.Abort(_('similarity must be between 0 and 100'))
101 101 if similarity > 0:
102 self.simopt = '--find-copies=%d%%' % similarity
102 self.simopt = '-C%d%%' % similarity
103 103 findcopiesharder = ui.configbool('convert', 'git.findcopiesharder',
104 104 False)
105 105 if findcopiesharder:
106 106 self.simopt += ' --find-copies-harder'
107 107 else:
108 108 self.simopt = ''
109 109
110 110 checktool('git', 'git')
111 111
112 112 self.path = path
113 113 self.submodules = []
114 114
115 115 self.catfilepipe = self.gitpipe('git cat-file --batch')
116 116
117 117 def after(self):
118 118 for f in self.catfilepipe:
119 119 f.close()
120 120
121 121 def getheads(self):
122 122 if not self.rev:
123 123 heads, ret = self.gitread('git rev-parse --branches --remotes')
124 124 heads = heads.splitlines()
125 125 else:
126 126 heads, ret = self.gitread("git rev-parse --verify %s" % self.rev)
127 127 heads = [heads[:-1]]
128 128 if ret:
129 129 raise util.Abort(_('cannot retrieve git heads'))
130 130 return heads
131 131
132 132 def catfile(self, rev, type):
133 133 if rev == hex(nullid):
134 134 raise IOError
135 135 self.catfilepipe[0].write(rev+'\n')
136 136 self.catfilepipe[0].flush()
137 137 info = self.catfilepipe[1].readline().split()
138 138 if info[1] != type:
139 139 raise util.Abort(_('cannot read %r object at %s') % (type, rev))
140 140 size = int(info[2])
141 141 data = self.catfilepipe[1].read(size)
142 142 if len(data) < size:
143 143 raise util.Abort(_('cannot read %r object at %s: unexpected size')
144 144 % (type, rev))
145 145 # read the trailing newline
146 146 self.catfilepipe[1].read(1)
147 147 return data
148 148
149 149 def getfile(self, name, rev):
150 150 if rev == hex(nullid):
151 151 return None, None
152 152 if name == '.hgsub':
153 153 data = '\n'.join([m.hgsub() for m in self.submoditer()])
154 154 mode = ''
155 155 elif name == '.hgsubstate':
156 156 data = '\n'.join([m.hgsubstate() for m in self.submoditer()])
157 157 mode = ''
158 158 else:
159 159 data = self.catfile(rev, "blob")
160 160 mode = self.modecache[(name, rev)]
161 161 return data, mode
162 162
163 163 def submoditer(self):
164 164 null = hex(nullid)
165 165 for m in sorted(self.submodules, key=lambda p: p.path):
166 166 if m.node != null:
167 167 yield m
168 168
169 169 def parsegitmodules(self, content):
170 170 """Parse the formatted .gitmodules file, example file format:
171 171 [submodule "sub"]\n
172 172 \tpath = sub\n
173 173 \turl = git://giturl\n
174 174 """
175 175 self.submodules = []
176 176 c = config.config()
177 177 # Each item in .gitmodules starts with \t that cant be parsed
178 178 c.parse('.gitmodules', content.replace('\t',''))
179 179 for sec in c.sections():
180 180 s = c[sec]
181 181 if 'url' in s and 'path' in s:
182 182 self.submodules.append(submodule(s['path'], '', s['url']))
183 183
184 184 def retrievegitmodules(self, version):
185 185 modules, ret = self.gitread("git show %s:%s" % (version, '.gitmodules'))
186 186 if ret:
187 187 raise util.Abort(_('cannot read submodules config file in %s') %
188 188 version)
189 189 self.parsegitmodules(modules)
190 190 for m in self.submodules:
191 191 node, ret = self.gitread("git rev-parse %s:%s" % (version, m.path))
192 192 if ret:
193 193 continue
194 194 m.node = node.strip()
195 195
196 196 def getchanges(self, version, full):
197 197 if full:
198 198 raise util.Abort(_("convert from git do not support --full"))
199 199 self.modecache = {}
200 200 fh = self.gitopen("git diff-tree -z --root -m -r %s %s" % (
201 201 self.simopt, version))
202 202 changes = []
203 203 copies = {}
204 204 seen = set()
205 205 entry = None
206 206 subexists = [False]
207 207 subdeleted = [False]
208 208 difftree = fh.read().split('\x00')
209 209 lcount = len(difftree)
210 210 i = 0
211 211
212 212 def add(entry, f, isdest):
213 213 seen.add(f)
214 214 h = entry[3]
215 215 p = (entry[1] == "100755")
216 216 s = (entry[1] == "120000")
217 217 renamesource = (not isdest and entry[4][0] == 'R')
218 218
219 219 if f == '.gitmodules':
220 220 subexists[0] = True
221 221 if entry[4] == 'D' or renamesource:
222 222 subdeleted[0] = True
223 223 changes.append(('.hgsub', hex(nullid)))
224 224 else:
225 225 changes.append(('.hgsub', ''))
226 226 elif entry[1] == '160000' or entry[0] == ':160000':
227 227 subexists[0] = True
228 228 else:
229 229 if renamesource:
230 230 h = hex(nullid)
231 231 self.modecache[(f, h)] = (p and "x") or (s and "l") or ""
232 232 changes.append((f, h))
233 233
234 234 while i < lcount:
235 235 l = difftree[i]
236 236 i += 1
237 237 if not entry:
238 238 if not l.startswith(':'):
239 239 continue
240 240 entry = l.split()
241 241 continue
242 242 f = l
243 243 if f not in seen:
244 244 add(entry, f, False)
245 245 # A file can be copied multiple times, or modified and copied
246 246 # simultaneously. So f can be repeated even if fdest isn't.
247 247 if entry[4][0] in 'RC':
248 248 # rename or copy: next line is the destination
249 249 fdest = difftree[i]
250 250 i += 1
251 251 if fdest not in seen:
252 252 add(entry, fdest, True)
253 253 # .gitmodules isn't imported at all, so it being copied to
254 254 # and fro doesn't really make sense
255 255 if f != '.gitmodules' and fdest != '.gitmodules':
256 256 copies[fdest] = f
257 257 entry = None
258 258 if fh.close():
259 259 raise util.Abort(_('cannot read changes in %s') % version)
260 260
261 261 if subexists[0]:
262 262 if subdeleted[0]:
263 263 changes.append(('.hgsubstate', hex(nullid)))
264 264 else:
265 265 self.retrievegitmodules(version)
266 266 changes.append(('.hgsubstate', ''))
267 267 return (changes, copies)
268 268
269 269 def getcommit(self, version):
270 270 c = self.catfile(version, "commit") # read the commit hash
271 271 end = c.find("\n\n")
272 272 message = c[end + 2:]
273 273 message = self.recode(message)
274 274 l = c[:end].splitlines()
275 275 parents = []
276 276 author = committer = None
277 277 for e in l[1:]:
278 278 n, v = e.split(" ", 1)
279 279 if n == "author":
280 280 p = v.split()
281 281 tm, tz = p[-2:]
282 282 author = " ".join(p[:-2])
283 283 if author[0] == "<": author = author[1:-1]
284 284 author = self.recode(author)
285 285 if n == "committer":
286 286 p = v.split()
287 287 tm, tz = p[-2:]
288 288 committer = " ".join(p[:-2])
289 289 if committer[0] == "<": committer = committer[1:-1]
290 290 committer = self.recode(committer)
291 291 if n == "parent":
292 292 parents.append(v)
293 293
294 294 if committer and committer != author:
295 295 message += "\ncommitter: %s\n" % committer
296 296 tzs, tzh, tzm = tz[-5:-4] + "1", tz[-4:-2], tz[-2:]
297 297 tz = -int(tzs) * (int(tzh) * 3600 + int(tzm))
298 298 date = tm + " " + str(tz)
299 299
300 300 c = commit(parents=parents, date=date, author=author, desc=message,
301 301 rev=version)
302 302 return c
303 303
304 304 def numcommits(self):
305 305 return len([None for _ in self.gitopen('git rev-list --all')])
306 306
307 307 def gettags(self):
308 308 tags = {}
309 309 alltags = {}
310 310 fh = self.gitopen('git ls-remote --tags "%s"' % self.path,
311 311 err=subprocess.STDOUT)
312 312 prefix = 'refs/tags/'
313 313
314 314 # Build complete list of tags, both annotated and bare ones
315 315 for line in fh:
316 316 line = line.strip()
317 317 if line.startswith("error:") or line.startswith("fatal:"):
318 318 raise util.Abort(_('cannot read tags from %s') % self.path)
319 319 node, tag = line.split(None, 1)
320 320 if not tag.startswith(prefix):
321 321 continue
322 322 alltags[tag[len(prefix):]] = node
323 323 if fh.close():
324 324 raise util.Abort(_('cannot read tags from %s') % self.path)
325 325
326 326 # Filter out tag objects for annotated tag refs
327 327 for tag in alltags:
328 328 if tag.endswith('^{}'):
329 329 tags[tag[:-3]] = alltags[tag]
330 330 else:
331 331 if tag + '^{}' in alltags:
332 332 continue
333 333 else:
334 334 tags[tag] = alltags[tag]
335 335
336 336 return tags
337 337
338 338 def getchangedfiles(self, version, i):
339 339 changes = []
340 340 if i is None:
341 341 fh = self.gitopen("git diff-tree --root -m -r %s" % version)
342 342 for l in fh:
343 343 if "\t" not in l:
344 344 continue
345 345 m, f = l[:-1].split("\t")
346 346 changes.append(f)
347 347 else:
348 348 fh = self.gitopen('git diff-tree --name-only --root -r %s '
349 349 '"%s^%s" --' % (version, version, i + 1))
350 350 changes = [f.rstrip('\n') for f in fh]
351 351 if fh.close():
352 352 raise util.Abort(_('cannot read changes in %s') % version)
353 353
354 354 return changes
355 355
356 356 def getbookmarks(self):
357 357 bookmarks = {}
358 358
359 359 # Interesting references in git are prefixed
360 360 prefix = 'refs/heads/'
361 361 prefixlen = len(prefix)
362 362
363 363 # factor two commands
364 364 gitcmd = { 'remote/': 'git ls-remote --heads origin',
365 365 '': 'git show-ref'}
366 366
367 367 # Origin heads
368 368 for reftype in gitcmd:
369 369 try:
370 370 fh = self.gitopen(gitcmd[reftype], err=subprocess.PIPE)
371 371 for line in fh:
372 372 line = line.strip()
373 373 rev, name = line.split(None, 1)
374 374 if not name.startswith(prefix):
375 375 continue
376 376 name = '%s%s' % (reftype, name[prefixlen:])
377 377 bookmarks[name] = rev
378 378 except Exception:
379 379 pass
380 380
381 381 return bookmarks
382 382
383 383 def checkrevformat(self, revstr, mapname='splicemap'):
384 384 """ git revision string is a 40 byte hex """
385 385 self.checkhexformat(revstr, mapname)
General Comments 0
You need to be logged in to leave comments. Login now