##// END OF EJS Templates
gitlog: add tiprev() function...
Augie Fackler -
r46537:6aacc395 default
parent child Browse files
Show More
@@ -1,527 +1,535
1 1 from __future__ import absolute_import
2 2
3 3 from mercurial.i18n import _
4 4
5 5 from mercurial import (
6 6 ancestor,
7 7 changelog as hgchangelog,
8 8 dagop,
9 9 encoding,
10 10 error,
11 11 manifest,
12 12 node as nodemod,
13 13 pycompat,
14 14 )
15 15 from mercurial.interfaces import (
16 16 repository,
17 17 util as interfaceutil,
18 18 )
19 19 from mercurial.utils import stringutil
20 20 from . import (
21 21 gitutil,
22 22 index,
23 23 manifest as gitmanifest,
24 24 )
25 25
26 26 pygit2 = gitutil.get_pygit2()
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 # TODO: this appears to be an enumerated type, and should probably
100 100 # be part of the public changelog interface
101 101 _copiesstorage = b'extra'
102 102
103 103 def __contains__(self, rev):
104 104 try:
105 105 self.node(rev)
106 106 return True
107 107 except error.LookupError:
108 108 return False
109 109
110 110 def __iter__(self):
111 111 return iter(pycompat.xrange(len(self)))
112 112
113 113 @property
114 114 def filteredrevs(self):
115 115 # TODO: we should probably add a refs/hg/ namespace for hidden
116 116 # heads etc, but that's an idea for later.
117 117 return set()
118 118
119 119 @property
120 120 def index(self):
121 121 return baselogindex(self)
122 122
123 123 @property
124 124 def nodemap(self):
125 125 r = {
126 126 nodemod.bin(v[0]): v[1]
127 127 for v in self._db.execute('SELECT node, rev FROM changelog')
128 128 }
129 129 r[nodemod.nullid] = nodemod.nullrev
130 130 return r
131 131
132 132 def tip(self):
133 133 t = self._db.execute(
134 134 'SELECT node FROM changelog ORDER BY rev DESC LIMIT 1'
135 135 ).fetchone()
136 136 if t:
137 137 return nodemod.bin(t[0])
138 138 return nodemod.nullid
139 139
140 140 def revs(self, start=0, stop=None):
141 141 if stop is None:
142 142 stop = self.tip()
143 143 t = self._db.execute(
144 144 'SELECT rev FROM changelog '
145 145 'WHERE rev >= ? AND rev <= ? '
146 146 'ORDER BY REV ASC',
147 147 (start, stop),
148 148 )
149 149 return (int(r[0]) for r in t)
150 150
151 def tiprev(self):
152 t = self._db.execute(
153 'SELECT rev FROM changelog '
154 'ORDER BY REV DESC '
155 'LIMIT 1'
156 )
157 return next(t)
158
151 159 def _partialmatch(self, id):
152 160 if nodemod.wdirhex.startswith(id):
153 161 raise error.WdirUnsupported
154 162 candidates = [
155 163 nodemod.bin(x[0])
156 164 for x in self._db.execute(
157 165 'SELECT node FROM changelog WHERE node LIKE ?', (id + b'%',)
158 166 )
159 167 ]
160 168 if nodemod.nullhex.startswith(id):
161 169 candidates.append(nodemod.nullid)
162 170 if len(candidates) > 1:
163 171 raise error.AmbiguousPrefixLookupError(
164 172 id, b'00changelog.i', _(b'ambiguous identifier')
165 173 )
166 174 if candidates:
167 175 return candidates[0]
168 176 return None
169 177
170 178 def flags(self, rev):
171 179 return 0
172 180
173 181 def shortest(self, node, minlength=1):
174 182 nodehex = nodemod.hex(node)
175 183 for attempt in pycompat.xrange(minlength, len(nodehex) + 1):
176 184 candidate = nodehex[:attempt]
177 185 matches = int(
178 186 self._db.execute(
179 187 'SELECT COUNT(*) FROM changelog WHERE node LIKE ?',
180 188 (pycompat.sysstr(candidate + b'%'),),
181 189 ).fetchone()[0]
182 190 )
183 191 if matches == 1:
184 192 return candidate
185 193 return nodehex
186 194
187 195 def headrevs(self, revs=None):
188 196 realheads = [
189 197 int(x[0])
190 198 for x in self._db.execute(
191 199 'SELECT rev FROM changelog '
192 200 'INNER JOIN heads ON changelog.node = heads.node'
193 201 )
194 202 ]
195 203 if revs:
196 204 return sorted([r for r in revs if r in realheads])
197 205 return sorted(realheads)
198 206
199 207 def changelogrevision(self, nodeorrev):
200 208 # Ensure we have a node id
201 209 if isinstance(nodeorrev, int):
202 210 n = self.node(nodeorrev)
203 211 else:
204 212 n = nodeorrev
205 213 # handle looking up nullid
206 214 if n == nodemod.nullid:
207 215 return hgchangelog._changelogrevision(extra={})
208 216 hn = gitutil.togitnode(n)
209 217 # We've got a real commit!
210 218 files = [
211 219 r[0]
212 220 for r in self._db.execute(
213 221 'SELECT filename FROM changedfiles '
214 222 'WHERE node = ? and filenode != ?',
215 223 (hn, gitutil.nullgit),
216 224 )
217 225 ]
218 226 filesremoved = [
219 227 r[0]
220 228 for r in self._db.execute(
221 229 'SELECT filename FROM changedfiles '
222 230 'WHERE node = ? and filenode = ?',
223 231 (hn, nodemod.nullhex),
224 232 )
225 233 ]
226 234 c = self.gitrepo[hn]
227 235 return hgchangelog._changelogrevision(
228 236 manifest=n, # pretend manifest the same as the commit node
229 237 user=b'%s <%s>'
230 238 % (c.author.name.encode('utf8'), c.author.email.encode('utf8')),
231 239 date=(c.author.time, -c.author.offset * 60),
232 240 files=files,
233 241 # TODO filesadded in the index
234 242 filesremoved=filesremoved,
235 243 description=c.message.encode('utf8'),
236 244 # TODO do we want to handle extra? how?
237 245 extra={b'branch': b'default'},
238 246 )
239 247
240 248 def ancestors(self, revs, stoprev=0, inclusive=False):
241 249 revs = list(revs)
242 250 tip = self.rev(self.tip())
243 251 for r in revs:
244 252 if r > tip:
245 253 raise IndexError(b'Invalid rev %r' % r)
246 254 return ancestor.lazyancestors(
247 255 self.parentrevs, revs, stoprev=stoprev, inclusive=inclusive
248 256 )
249 257
250 258 # Cleanup opportunity: this is *identical* to the revlog.py version
251 259 def descendants(self, revs):
252 260 return dagop.descendantrevs(revs, self.revs, self.parentrevs)
253 261
254 262 def incrementalmissingrevs(self, common=None):
255 263 """Return an object that can be used to incrementally compute the
256 264 revision numbers of the ancestors of arbitrary sets that are not
257 265 ancestors of common. This is an ancestor.incrementalmissingancestors
258 266 object.
259 267
260 268 'common' is a list of revision numbers. If common is not supplied, uses
261 269 nullrev.
262 270 """
263 271 if common is None:
264 272 common = [nodemod.nullrev]
265 273
266 274 return ancestor.incrementalmissingancestors(self.parentrevs, common)
267 275
268 276 def findmissing(self, common=None, heads=None):
269 277 """Return the ancestors of heads that are not ancestors of common.
270 278
271 279 More specifically, return a list of nodes N such that every N
272 280 satisfies the following constraints:
273 281
274 282 1. N is an ancestor of some node in 'heads'
275 283 2. N is not an ancestor of any node in 'common'
276 284
277 285 The list is sorted by revision number, meaning it is
278 286 topologically sorted.
279 287
280 288 'heads' and 'common' are both lists of node IDs. If heads is
281 289 not supplied, uses all of the revlog's heads. If common is not
282 290 supplied, uses nullid."""
283 291 if common is None:
284 292 common = [nodemod.nullid]
285 293 if heads is None:
286 294 heads = self.heads()
287 295
288 296 common = [self.rev(n) for n in common]
289 297 heads = [self.rev(n) for n in heads]
290 298
291 299 inc = self.incrementalmissingrevs(common=common)
292 300 return [self.node(r) for r in inc.missingancestors(heads)]
293 301
294 302 def children(self, node):
295 303 """find the children of a given node"""
296 304 c = []
297 305 p = self.rev(node)
298 306 for r in self.revs(start=p + 1):
299 307 prevs = [pr for pr in self.parentrevs(r) if pr != nodemod.nullrev]
300 308 if prevs:
301 309 for pr in prevs:
302 310 if pr == p:
303 311 c.append(self.node(r))
304 312 elif p == nodemod.nullrev:
305 313 c.append(self.node(r))
306 314 return c
307 315
308 316 def reachableroots(self, minroot, heads, roots, includepath=False):
309 317 return dagop._reachablerootspure(
310 318 self.parentrevs, minroot, roots, heads, includepath
311 319 )
312 320
313 321 # Cleanup opportunity: this is *identical* to the revlog.py version
314 322 def isancestor(self, a, b):
315 323 a, b = self.rev(a), self.rev(b)
316 324 return self.isancestorrev(a, b)
317 325
318 326 # Cleanup opportunity: this is *identical* to the revlog.py version
319 327 def isancestorrev(self, a, b):
320 328 if a == nodemod.nullrev:
321 329 return True
322 330 elif a == b:
323 331 return True
324 332 elif a > b:
325 333 return False
326 334 return bool(self.reachableroots(a, [b], [a], includepath=False))
327 335
328 336 def parentrevs(self, rev):
329 337 n = self.node(rev)
330 338 hn = gitutil.togitnode(n)
331 339 if hn != gitutil.nullgit:
332 340 c = self.gitrepo[hn]
333 341 else:
334 342 return nodemod.nullrev, nodemod.nullrev
335 343 p1 = p2 = nodemod.nullrev
336 344 if c.parents:
337 345 p1 = self.rev(c.parents[0].id.raw)
338 346 if len(c.parents) > 2:
339 347 raise error.Abort(b'TODO octopus merge handling')
340 348 if len(c.parents) == 2:
341 349 p2 = self.rev(c.parents[1].id.raw)
342 350 return p1, p2
343 351
344 352 # Private method is used at least by the tags code.
345 353 _uncheckedparentrevs = parentrevs
346 354
347 355 def commonancestorsheads(self, a, b):
348 356 # TODO the revlog verson of this has a C path, so we probably
349 357 # need to optimize this...
350 358 a, b = self.rev(a), self.rev(b)
351 359 return [
352 360 self.node(n)
353 361 for n in ancestor.commonancestorsheads(self.parentrevs, a, b)
354 362 ]
355 363
356 364 def branchinfo(self, rev):
357 365 """Git doesn't do named branches, so just put everything on default."""
358 366 return b'default', False
359 367
360 368 def delayupdate(self, tr):
361 369 # TODO: I think we can elide this because we're just dropping
362 370 # an object in the git repo?
363 371 pass
364 372
365 373 def add(
366 374 self,
367 375 manifest,
368 376 files,
369 377 desc,
370 378 transaction,
371 379 p1,
372 380 p2,
373 381 user,
374 382 date=None,
375 383 extra=None,
376 384 p1copies=None,
377 385 p2copies=None,
378 386 filesadded=None,
379 387 filesremoved=None,
380 388 ):
381 389 parents = []
382 390 hp1, hp2 = gitutil.togitnode(p1), gitutil.togitnode(p2)
383 391 if p1 != nodemod.nullid:
384 392 parents.append(hp1)
385 393 if p2 and p2 != nodemod.nullid:
386 394 parents.append(hp2)
387 395 assert date is not None
388 396 timestamp, tz = date
389 397 sig = pygit2.Signature(
390 398 encoding.unifromlocal(stringutil.person(user)),
391 399 encoding.unifromlocal(stringutil.email(user)),
392 400 int(timestamp),
393 401 -int(tz // 60),
394 402 )
395 403 oid = self.gitrepo.create_commit(
396 404 None, sig, sig, desc, gitutil.togitnode(manifest), parents
397 405 )
398 406 # Set up an internal reference to force the commit into the
399 407 # changelog. Hypothetically, we could even use this refs/hg/
400 408 # namespace to allow for anonymous heads on git repos, which
401 409 # would be neat.
402 410 self.gitrepo.references.create(
403 411 'refs/hg/internal/latest-commit', oid, force=True
404 412 )
405 413 # Reindex now to pick up changes. We omit the progress
406 414 # and log callbacks because this will be very quick.
407 415 index._index_repo(self.gitrepo, self._db)
408 416 return oid.raw
409 417
410 418
411 419 class manifestlog(baselog):
412 420 def __getitem__(self, node):
413 421 return self.get(b'', node)
414 422
415 423 def get(self, relpath, node):
416 424 if node == nodemod.nullid:
417 425 # TODO: this should almost certainly be a memgittreemanifestctx
418 426 return manifest.memtreemanifestctx(self, relpath)
419 427 commit = self.gitrepo[gitutil.togitnode(node)]
420 428 t = commit.tree
421 429 if relpath:
422 430 parts = relpath.split(b'/')
423 431 for p in parts:
424 432 te = t[p]
425 433 t = self.gitrepo[te.id]
426 434 return gitmanifest.gittreemanifestctx(self.gitrepo, t)
427 435
428 436
429 437 @interfaceutil.implementer(repository.ifilestorage)
430 438 class filelog(baselog):
431 439 def __init__(self, gr, db, path):
432 440 super(filelog, self).__init__(gr, db)
433 441 assert isinstance(path, bytes)
434 442 self.path = path
435 443
436 444 def read(self, node):
437 445 if node == nodemod.nullid:
438 446 return b''
439 447 return self.gitrepo[gitutil.togitnode(node)].data
440 448
441 449 def lookup(self, node):
442 450 if len(node) not in (20, 40):
443 451 node = int(node)
444 452 if isinstance(node, int):
445 453 assert False, b'todo revnums for nodes'
446 454 if len(node) == 40:
447 455 node = nodemod.bin(node)
448 456 hnode = gitutil.togitnode(node)
449 457 if hnode in self.gitrepo:
450 458 return node
451 459 raise error.LookupError(self.path, node, _(b'no match found'))
452 460
453 461 def cmp(self, node, text):
454 462 """Returns True if text is different than content at `node`."""
455 463 return self.read(node) != text
456 464
457 465 def add(self, text, meta, transaction, link, p1=None, p2=None):
458 466 assert not meta # Should we even try to handle this?
459 467 return self.gitrepo.create_blob(text).raw
460 468
461 469 def __iter__(self):
462 470 for clrev in self._db.execute(
463 471 '''
464 472 SELECT rev FROM changelog
465 473 INNER JOIN changedfiles ON changelog.node = changedfiles.node
466 474 WHERE changedfiles.filename = ? AND changedfiles.filenode != ?
467 475 ''',
468 476 (pycompat.fsdecode(self.path), gitutil.nullgit),
469 477 ):
470 478 yield clrev[0]
471 479
472 480 def linkrev(self, fr):
473 481 return fr
474 482
475 483 def rev(self, node):
476 484 row = self._db.execute(
477 485 '''
478 486 SELECT rev FROM changelog
479 487 INNER JOIN changedfiles ON changelog.node = changedfiles.node
480 488 WHERE changedfiles.filename = ? AND changedfiles.filenode = ?''',
481 489 (pycompat.fsdecode(self.path), gitutil.togitnode(node)),
482 490 ).fetchone()
483 491 if row is None:
484 492 raise error.LookupError(self.path, node, _(b'no such node'))
485 493 return int(row[0])
486 494
487 495 def node(self, rev):
488 496 maybe = self._db.execute(
489 497 '''SELECT filenode FROM changedfiles
490 498 INNER JOIN changelog ON changelog.node = changedfiles.node
491 499 WHERE changelog.rev = ? AND filename = ?
492 500 ''',
493 501 (rev, pycompat.fsdecode(self.path)),
494 502 ).fetchone()
495 503 if maybe is None:
496 504 raise IndexError('gitlog %r out of range %d' % (self.path, rev))
497 505 return nodemod.bin(maybe[0])
498 506
499 507 def parents(self, node):
500 508 gn = gitutil.togitnode(node)
501 509 gp = pycompat.fsdecode(self.path)
502 510 ps = []
503 511 for p in self._db.execute(
504 512 '''SELECT p1filenode, p2filenode FROM changedfiles
505 513 WHERE filenode = ? AND filename = ?
506 514 ''',
507 515 (gn, gp),
508 516 ).fetchone():
509 517 if p is None:
510 518 commit = self._db.execute(
511 519 "SELECT node FROM changedfiles "
512 520 "WHERE filenode = ? AND filename = ?",
513 521 (gn, gp),
514 522 ).fetchone()[0]
515 523 # This filelog is missing some data. Build the
516 524 # filelog, then recurse (which will always find data).
517 525 if pycompat.ispy3:
518 526 commit = commit.decode('ascii')
519 527 index.fill_in_filelog(self.gitrepo, self._db, commit, gp, gn)
520 528 return self.parents(node)
521 529 else:
522 530 ps.append(nodemod.bin(p))
523 531 return ps
524 532
525 533 def renamed(self, node):
526 534 # TODO: renames/copies
527 535 return False
General Comments 0
You need to be logged in to leave comments. Login now