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