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