##// END OF EJS Templates
convert: for git, factor out code to add entries to a separate function...
Siddharth Agarwal -
r22469:15bc0431 default
parent child Browse files
Show More
@@ -1,351 +1,355 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 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 subexists = False
192 subdeleted = False
191 subexists = [False]
192 subdeleted = [False]
193 193 difftree = fh.read().split('\x00')
194 194 lcount = len(difftree)
195 195 i = 0
196
197 def add(entry, f):
198 seen.add(f)
199 h = entry[3]
200 p = (entry[1] == "100755")
201 s = (entry[1] == "120000")
202
203 if f == '.gitmodules':
204 subexists[0] = True
205 if entry[4] == 'D':
206 subdeleted[0] = True
207 changes.append(('.hgsub', hex(nullid)))
208 else:
209 changes.append(('.hgsub', ''))
210 elif entry[1] == '160000' or entry[0] == ':160000':
211 subexists[0] = True
212 else:
213 self.modecache[(f, h)] = (p and "x") or (s and "l") or ""
214 changes.append((f, h))
215
196 216 while i < lcount:
197 217 l = difftree[i]
198 218 i += 1
199 219 if not entry:
200 220 if not l.startswith(':'):
201 221 continue
202 222 entry = l.split()
203 223 continue
204 224 f = l
205 225 if f not in seen:
206 seen.add(f)
207 h = entry[3]
208 p = (entry[1] == "100755")
209 s = (entry[1] == "120000")
210
211 if f == '.gitmodules':
212 subexists = True
213 if entry[4] == 'D':
214 subdeleted = True
215 changes.append(('.hgsub', hex(nullid)))
216 else:
217 changes.append(('.hgsub', ''))
218 elif entry[1] == '160000' or entry[0] == ':160000':
219 subexists = True
220 else:
221 self.modecache[(f, h)] = (p and "x") or (s and "l") or ""
222 changes.append((f, h))
226 add(entry, f)
223 227 entry = None
224 228 if fh.close():
225 229 raise util.Abort(_('cannot read changes in %s') % version)
226 230
227 if subexists:
228 if subdeleted:
231 if subexists[0]:
232 if subdeleted[0]:
229 233 changes.append(('.hgsubstate', hex(nullid)))
230 234 else:
231 235 self.retrievegitmodules(version)
232 236 changes.append(('.hgsubstate', ''))
233 237 return (changes, {})
234 238
235 239 def getcommit(self, version):
236 240 c = self.catfile(version, "commit") # read the commit hash
237 241 end = c.find("\n\n")
238 242 message = c[end + 2:]
239 243 message = self.recode(message)
240 244 l = c[:end].splitlines()
241 245 parents = []
242 246 author = committer = None
243 247 for e in l[1:]:
244 248 n, v = e.split(" ", 1)
245 249 if n == "author":
246 250 p = v.split()
247 251 tm, tz = p[-2:]
248 252 author = " ".join(p[:-2])
249 253 if author[0] == "<": author = author[1:-1]
250 254 author = self.recode(author)
251 255 if n == "committer":
252 256 p = v.split()
253 257 tm, tz = p[-2:]
254 258 committer = " ".join(p[:-2])
255 259 if committer[0] == "<": committer = committer[1:-1]
256 260 committer = self.recode(committer)
257 261 if n == "parent":
258 262 parents.append(v)
259 263
260 264 if committer and committer != author:
261 265 message += "\ncommitter: %s\n" % committer
262 266 tzs, tzh, tzm = tz[-5:-4] + "1", tz[-4:-2], tz[-2:]
263 267 tz = -int(tzs) * (int(tzh) * 3600 + int(tzm))
264 268 date = tm + " " + str(tz)
265 269
266 270 c = commit(parents=parents, date=date, author=author, desc=message,
267 271 rev=version)
268 272 return c
269 273
270 274 def numcommits(self):
271 275 return len([None for _ in self.gitopen('git rev-list --all')])
272 276
273 277 def gettags(self):
274 278 tags = {}
275 279 alltags = {}
276 280 fh = self.gitopen('git ls-remote --tags "%s"' % self.path,
277 281 err=subprocess.STDOUT)
278 282 prefix = 'refs/tags/'
279 283
280 284 # Build complete list of tags, both annotated and bare ones
281 285 for line in fh:
282 286 line = line.strip()
283 287 if line.startswith("error:") or line.startswith("fatal:"):
284 288 raise util.Abort(_('cannot read tags from %s') % self.path)
285 289 node, tag = line.split(None, 1)
286 290 if not tag.startswith(prefix):
287 291 continue
288 292 alltags[tag[len(prefix):]] = node
289 293 if fh.close():
290 294 raise util.Abort(_('cannot read tags from %s') % self.path)
291 295
292 296 # Filter out tag objects for annotated tag refs
293 297 for tag in alltags:
294 298 if tag.endswith('^{}'):
295 299 tags[tag[:-3]] = alltags[tag]
296 300 else:
297 301 if tag + '^{}' in alltags:
298 302 continue
299 303 else:
300 304 tags[tag] = alltags[tag]
301 305
302 306 return tags
303 307
304 308 def getchangedfiles(self, version, i):
305 309 changes = []
306 310 if i is None:
307 311 fh = self.gitopen("git diff-tree --root -m -r %s" % version)
308 312 for l in fh:
309 313 if "\t" not in l:
310 314 continue
311 315 m, f = l[:-1].split("\t")
312 316 changes.append(f)
313 317 else:
314 318 fh = self.gitopen('git diff-tree --name-only --root -r %s '
315 319 '"%s^%s" --' % (version, version, i + 1))
316 320 changes = [f.rstrip('\n') for f in fh]
317 321 if fh.close():
318 322 raise util.Abort(_('cannot read changes in %s') % version)
319 323
320 324 return changes
321 325
322 326 def getbookmarks(self):
323 327 bookmarks = {}
324 328
325 329 # Interesting references in git are prefixed
326 330 prefix = 'refs/heads/'
327 331 prefixlen = len(prefix)
328 332
329 333 # factor two commands
330 334 gitcmd = { 'remote/': 'git ls-remote --heads origin',
331 335 '': 'git show-ref'}
332 336
333 337 # Origin heads
334 338 for reftype in gitcmd:
335 339 try:
336 340 fh = self.gitopen(gitcmd[reftype], err=subprocess.PIPE)
337 341 for line in fh:
338 342 line = line.strip()
339 343 rev, name = line.split(None, 1)
340 344 if not name.startswith(prefix):
341 345 continue
342 346 name = '%s%s' % (reftype, name[prefixlen:])
343 347 bookmarks[name] = rev
344 348 except Exception:
345 349 pass
346 350
347 351 return bookmarks
348 352
349 353 def checkrevformat(self, revstr, mapname='splicemap'):
350 354 """ git revision string is a 40 byte hex """
351 355 self.checkhexformat(revstr, mapname)
General Comments 0
You need to be logged in to leave comments. Login now