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