##// END OF EJS Templates
gitlog: implement __iter__ on changelog...
Augie Fackler -
r44965:8b5f5d8a default
parent child Browse files
Show More
@@ -1,463 +1,466 b''
1 1 from __future__ import absolute_import
2 2
3 3 import pygit2
4 4
5 5 from mercurial.i18n import _
6 6
7 7 from mercurial import (
8 8 ancestor,
9 9 changelog as hgchangelog,
10 10 dagop,
11 11 encoding,
12 12 error,
13 13 manifest,
14 14 node as nodemod,
15 15 pycompat,
16 16 )
17 17 from mercurial.interfaces import (
18 18 repository,
19 19 util as interfaceutil,
20 20 )
21 21 from mercurial.utils import stringutil
22 22 from . import (
23 23 gitutil,
24 24 index,
25 25 manifest as gitmanifest,
26 26 )
27 27
28 28
29 29 class baselog(object): # revlog.revlog):
30 30 """Common implementations between changelog and manifestlog."""
31 31
32 32 def __init__(self, gr, db):
33 33 self.gitrepo = gr
34 34 self._db = db
35 35
36 36 def __len__(self):
37 37 return int(
38 38 self._db.execute('SELECT COUNT(*) FROM changelog').fetchone()[0]
39 39 )
40 40
41 41 def rev(self, n):
42 42 if n == nodemod.nullid:
43 43 return -1
44 44 t = self._db.execute(
45 45 'SELECT rev FROM changelog WHERE node = ?', (gitutil.togitnode(n),)
46 46 ).fetchone()
47 47 if t is None:
48 48 raise error.LookupError(n, b'00changelog.i', _(b'no node %d'))
49 49 return t[0]
50 50
51 51 def node(self, r):
52 52 if r == nodemod.nullrev:
53 53 return nodemod.nullid
54 54 t = self._db.execute(
55 55 'SELECT node FROM changelog WHERE rev = ?', (r,)
56 56 ).fetchone()
57 57 if t is None:
58 58 raise error.LookupError(r, b'00changelog.i', _(b'no node'))
59 59 return nodemod.bin(t[0])
60 60
61 61 def hasnode(self, n):
62 62 t = self._db.execute(
63 63 'SELECT node FROM changelog WHERE node = ?', (n,)
64 64 ).fetchone()
65 65 return t is not None
66 66
67 67
68 68 class baselogindex(object):
69 69 def __init__(self, log):
70 70 self._log = log
71 71
72 72 def has_node(self, n):
73 73 return self._log.rev(n) != -1
74 74
75 75 def __len__(self):
76 76 return len(self._log)
77 77
78 78 def __getitem__(self, idx):
79 79 p1rev, p2rev = self._log.parentrevs(idx)
80 80 # TODO: it's messy that the index leaks so far out of the
81 81 # storage layer that we have to implement things like reading
82 82 # this raw tuple, which exposes revlog internals.
83 83 return (
84 84 # Pretend offset is just the index, since we don't really care.
85 85 idx,
86 86 # Same with lengths
87 87 idx, # length
88 88 idx, # rawsize
89 89 -1, # delta base
90 90 idx, # linkrev TODO is this right?
91 91 p1rev,
92 92 p2rev,
93 93 self._log.node(idx),
94 94 )
95 95
96 96
97 97 # TODO: an interface for the changelog type?
98 98 class changelog(baselog):
99 99 def __contains__(self, rev):
100 100 try:
101 101 self.node(rev)
102 102 return True
103 103 except error.LookupError:
104 104 return False
105 105
106 def __iter__(self):
107 return iter(pycompat.xrange(len(self)))
108
106 109 @property
107 110 def filteredrevs(self):
108 111 # TODO: we should probably add a refs/hg/ namespace for hidden
109 112 # heads etc, but that's an idea for later.
110 113 return set()
111 114
112 115 @property
113 116 def index(self):
114 117 return baselogindex(self)
115 118
116 119 @property
117 120 def nodemap(self):
118 121 r = {
119 122 nodemod.bin(v[0]): v[1]
120 123 for v in self._db.execute('SELECT node, rev FROM changelog')
121 124 }
122 125 r[nodemod.nullid] = nodemod.nullrev
123 126 return r
124 127
125 128 def tip(self):
126 129 t = self._db.execute(
127 130 'SELECT node FROM changelog ORDER BY rev DESC LIMIT 1'
128 131 ).fetchone()
129 132 if t:
130 133 return nodemod.bin(t[0])
131 134 return nodemod.nullid
132 135
133 136 def revs(self, start=0, stop=None):
134 137 if stop is None:
135 138 stop = self.tip()
136 139 t = self._db.execute(
137 140 'SELECT rev FROM changelog '
138 141 'WHERE rev >= ? AND rev <= ? '
139 142 'ORDER BY REV ASC',
140 143 (start, stop),
141 144 )
142 145 return (int(r[0]) for r in t)
143 146
144 147 def _partialmatch(self, id):
145 148 if nodemod.wdirhex.startswith(id):
146 149 raise error.WdirUnsupported
147 150 candidates = [
148 151 nodemod.bin(x[0])
149 152 for x in self._db.execute(
150 153 'SELECT node FROM changelog WHERE node LIKE ?', (id + b'%',)
151 154 )
152 155 ]
153 156 if nodemod.nullhex.startswith(id):
154 157 candidates.append(nodemod.nullid)
155 158 if len(candidates) > 1:
156 159 raise error.AmbiguousPrefixLookupError(
157 160 id, b'00changelog.i', _(b'ambiguous identifier')
158 161 )
159 162 if candidates:
160 163 return candidates[0]
161 164 return None
162 165
163 166 def flags(self, rev):
164 167 return 0
165 168
166 169 def shortest(self, node, minlength=1):
167 170 nodehex = nodemod.hex(node)
168 171 for attempt in pycompat.xrange(minlength, len(nodehex) + 1):
169 172 candidate = nodehex[:attempt]
170 173 matches = int(
171 174 self._db.execute(
172 175 'SELECT COUNT(*) FROM changelog WHERE node LIKE ?',
173 176 (pycompat.sysstr(candidate + b'%'),),
174 177 ).fetchone()[0]
175 178 )
176 179 if matches == 1:
177 180 return candidate
178 181 return nodehex
179 182
180 183 def headrevs(self, revs=None):
181 184 realheads = [
182 185 int(x[0])
183 186 for x in self._db.execute(
184 187 'SELECT rev FROM changelog '
185 188 'INNER JOIN heads ON changelog.node = heads.node'
186 189 )
187 190 ]
188 191 if revs:
189 192 return sorted([r for r in revs if r in realheads])
190 193 return sorted(realheads)
191 194
192 195 def changelogrevision(self, nodeorrev):
193 196 # Ensure we have a node id
194 197 if isinstance(nodeorrev, int):
195 198 n = self.node(nodeorrev)
196 199 else:
197 200 n = nodeorrev
198 201 # handle looking up nullid
199 202 if n == nodemod.nullid:
200 203 return hgchangelog._changelogrevision(extra={})
201 204 hn = gitutil.togitnode(n)
202 205 # We've got a real commit!
203 206 files = [
204 207 r[0]
205 208 for r in self._db.execute(
206 209 'SELECT filename FROM changedfiles '
207 210 'WHERE node = ? and filenode != ?',
208 211 (hn, gitutil.nullgit),
209 212 )
210 213 ]
211 214 filesremoved = [
212 215 r[0]
213 216 for r in self._db.execute(
214 217 'SELECT filename FROM changedfiles '
215 218 'WHERE node = ? and filenode = ?',
216 219 (hn, nodemod.nullhex),
217 220 )
218 221 ]
219 222 c = self.gitrepo[hn]
220 223 return hgchangelog._changelogrevision(
221 224 manifest=n, # pretend manifest the same as the commit node
222 225 user=b'%s <%s>'
223 226 % (c.author.name.encode('utf8'), c.author.email.encode('utf8')),
224 227 date=(c.author.time, -c.author.offset * 60),
225 228 files=files,
226 229 # TODO filesadded in the index
227 230 filesremoved=filesremoved,
228 231 description=c.message.encode('utf8'),
229 232 # TODO do we want to handle extra? how?
230 233 extra={b'branch': b'default'},
231 234 )
232 235
233 236 def ancestors(self, revs, stoprev=0, inclusive=False):
234 237 revs = list(revs)
235 238 tip = self.rev(self.tip())
236 239 for r in revs:
237 240 if r > tip:
238 241 raise IndexError(b'Invalid rev %r' % r)
239 242 return ancestor.lazyancestors(
240 243 self.parentrevs, revs, stoprev=stoprev, inclusive=inclusive
241 244 )
242 245
243 246 # Cleanup opportunity: this is *identical* to the revlog.py version
244 247 def descendants(self, revs):
245 248 return dagop.descendantrevs(revs, self.revs, self.parentrevs)
246 249
247 250 def reachableroots(self, minroot, heads, roots, includepath=False):
248 251 return dagop._reachablerootspure(
249 252 self.parentrevs, minroot, roots, heads, includepath
250 253 )
251 254
252 255 # Cleanup opportunity: this is *identical* to the revlog.py version
253 256 def isancestor(self, a, b):
254 257 a, b = self.rev(a), self.rev(b)
255 258 return self.isancestorrev(a, b)
256 259
257 260 # Cleanup opportunity: this is *identical* to the revlog.py version
258 261 def isancestorrev(self, a, b):
259 262 if a == nodemod.nullrev:
260 263 return True
261 264 elif a == b:
262 265 return True
263 266 elif a > b:
264 267 return False
265 268 return bool(self.reachableroots(a, [b], [a], includepath=False))
266 269
267 270 def parentrevs(self, rev):
268 271 n = self.node(rev)
269 272 hn = gitutil.togitnode(n)
270 273 c = self.gitrepo[hn]
271 274 p1 = p2 = nodemod.nullrev
272 275 if c.parents:
273 276 p1 = self.rev(c.parents[0].id.raw)
274 277 if len(c.parents) > 2:
275 278 raise error.Abort(b'TODO octopus merge handling')
276 279 if len(c.parents) == 2:
277 280 p2 = self.rev(c.parents[1].id.raw)
278 281 return p1, p2
279 282
280 283 # Private method is used at least by the tags code.
281 284 _uncheckedparentrevs = parentrevs
282 285
283 286 def commonancestorsheads(self, a, b):
284 287 # TODO the revlog verson of this has a C path, so we probably
285 288 # need to optimize this...
286 289 a, b = self.rev(a), self.rev(b)
287 290 return [
288 291 self.node(n)
289 292 for n in ancestor.commonancestorsheads(self.parentrevs, a, b)
290 293 ]
291 294
292 295 def branchinfo(self, rev):
293 296 """Git doesn't do named branches, so just put everything on default."""
294 297 return b'default', False
295 298
296 299 def delayupdate(self, tr):
297 300 # TODO: I think we can elide this because we're just dropping
298 301 # an object in the git repo?
299 302 pass
300 303
301 304 def add(
302 305 self,
303 306 manifest,
304 307 files,
305 308 desc,
306 309 transaction,
307 310 p1,
308 311 p2,
309 312 user,
310 313 date=None,
311 314 extra=None,
312 315 p1copies=None,
313 316 p2copies=None,
314 317 filesadded=None,
315 318 filesremoved=None,
316 319 ):
317 320 parents = []
318 321 hp1, hp2 = gitutil.togitnode(p1), gitutil.togitnode(p2)
319 322 if p1 != nodemod.nullid:
320 323 parents.append(hp1)
321 324 if p2 and p2 != nodemod.nullid:
322 325 parents.append(hp2)
323 326 assert date is not None
324 327 timestamp, tz = date
325 328 sig = pygit2.Signature(
326 329 encoding.unifromlocal(stringutil.person(user)),
327 330 encoding.unifromlocal(stringutil.email(user)),
328 331 timestamp,
329 332 -(tz // 60),
330 333 )
331 334 oid = self.gitrepo.create_commit(
332 335 None, sig, sig, desc, gitutil.togitnode(manifest), parents
333 336 )
334 337 # Set up an internal reference to force the commit into the
335 338 # changelog. Hypothetically, we could even use this refs/hg/
336 339 # namespace to allow for anonymous heads on git repos, which
337 340 # would be neat.
338 341 self.gitrepo.references.create(
339 342 'refs/hg/internal/latest-commit', oid, force=True
340 343 )
341 344 # Reindex now to pick up changes. We omit the progress
342 345 # callback because this will be very quick.
343 346 index._index_repo(self.gitrepo, self._db)
344 347 return oid.raw
345 348
346 349
347 350 class manifestlog(baselog):
348 351 def __getitem__(self, node):
349 352 return self.get(b'', node)
350 353
351 354 def get(self, relpath, node):
352 355 if node == nodemod.nullid:
353 356 # TODO: this should almost certainly be a memgittreemanifestctx
354 357 return manifest.memtreemanifestctx(self, relpath)
355 358 commit = self.gitrepo[gitutil.togitnode(node)]
356 359 t = commit.tree
357 360 if relpath:
358 361 parts = relpath.split(b'/')
359 362 for p in parts:
360 363 te = t[p]
361 364 t = self.gitrepo[te.id]
362 365 return gitmanifest.gittreemanifestctx(self.gitrepo, t)
363 366
364 367
365 368 @interfaceutil.implementer(repository.ifilestorage)
366 369 class filelog(baselog):
367 370 def __init__(self, gr, db, path):
368 371 super(filelog, self).__init__(gr, db)
369 372 assert isinstance(path, bytes)
370 373 self.path = path
371 374
372 375 def read(self, node):
373 376 if node == nodemod.nullid:
374 377 return b''
375 378 return self.gitrepo[gitutil.togitnode(node)].data
376 379
377 380 def lookup(self, node):
378 381 if len(node) not in (20, 40):
379 382 node = int(node)
380 383 if isinstance(node, int):
381 384 assert False, b'todo revnums for nodes'
382 385 if len(node) == 40:
383 386 node = nodemod.bin(node)
384 387 hnode = gitutil.togitnode(node)
385 388 if hnode in self.gitrepo:
386 389 return node
387 390 raise error.LookupError(self.path, node, _(b'no match found'))
388 391
389 392 def cmp(self, node, text):
390 393 """Returns True if text is different than content at `node`."""
391 394 return self.read(node) != text
392 395
393 396 def add(self, text, meta, transaction, link, p1=None, p2=None):
394 397 assert not meta # Should we even try to handle this?
395 398 return self.gitrepo.create_blob(text).raw
396 399
397 400 def __iter__(self):
398 401 for clrev in self._db.execute(
399 402 '''
400 403 SELECT rev FROM changelog
401 404 INNER JOIN changedfiles ON changelog.node = changedfiles.node
402 405 WHERE changedfiles.filename = ? AND changedfiles.filenode != ?
403 406 ''',
404 407 (pycompat.fsdecode(self.path), gitutil.nullgit),
405 408 ):
406 409 yield clrev[0]
407 410
408 411 def linkrev(self, fr):
409 412 return fr
410 413
411 414 def rev(self, node):
412 415 row = self._db.execute(
413 416 '''
414 417 SELECT rev FROM changelog
415 418 INNER JOIN changedfiles ON changelog.node = changedfiles.node
416 419 WHERE changedfiles.filename = ? AND changedfiles.filenode = ?''',
417 420 (pycompat.fsdecode(self.path), gitutil.togitnode(node)),
418 421 ).fetchone()
419 422 if row is None:
420 423 raise error.LookupError(self.path, node, _(b'no such node'))
421 424 return int(row[0])
422 425
423 426 def node(self, rev):
424 427 maybe = self._db.execute(
425 428 '''SELECT filenode FROM changedfiles
426 429 INNER JOIN changelog ON changelog.node = changedfiles.node
427 430 WHERE changelog.rev = ? AND filename = ?
428 431 ''',
429 432 (rev, pycompat.fsdecode(self.path)),
430 433 ).fetchone()
431 434 if maybe is None:
432 435 raise IndexError('gitlog %r out of range %d' % (self.path, rev))
433 436 return nodemod.bin(maybe[0])
434 437
435 438 def parents(self, node):
436 439 gn = gitutil.togitnode(node)
437 440 gp = pycompat.fsdecode(self.path)
438 441 ps = []
439 442 for p in self._db.execute(
440 443 '''SELECT p1filenode, p2filenode FROM changedfiles
441 444 WHERE filenode = ? AND filename = ?
442 445 ''',
443 446 (gn, gp),
444 447 ).fetchone():
445 448 if p is None:
446 449 commit = self._db.execute(
447 450 "SELECT node FROM changedfiles "
448 451 "WHERE filenode = ? AND filename = ?",
449 452 (gn, gp),
450 453 ).fetchone()[0]
451 454 # This filelog is missing some data. Build the
452 455 # filelog, then recurse (which will always find data).
453 456 if pycompat.ispy3:
454 457 commit = commit.decode('ascii')
455 458 index.fill_in_filelog(self.gitrepo, self._db, commit, gp, gn)
456 459 return self.parents(node)
457 460 else:
458 461 ps.append(nodemod.bin(p))
459 462 return ps
460 463
461 464 def renamed(self, node):
462 465 # TODO: renames/copies
463 466 return False
General Comments 0
You need to be logged in to leave comments. Login now