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