##// END OF EJS Templates
Subdir support for annotate
mpm@selenic.com -
r122:82fd709d default
parent child Browse files
Show More
@@ -1,544 +1,547 b''
1 1 #!/usr/bin/env python
2 2 #
3 3 # mercurial - a minimal scalable distributed SCM
4 4 # v0.4f "jane dark"
5 5 #
6 6 # Copyright 2005 Matt Mackall <mpm@selenic.com>
7 7 #
8 8 # This software may be used and distributed according to the terms
9 9 # of the GNU General Public License, incorporated herein by reference.
10 10
11 11 # the psyco compiler makes commits a bit faster
12 12 # and makes changegroup merge about 20 times slower!
13 13 # try:
14 14 # import psyco
15 15 # psyco.full()
16 16 # except:
17 17 # pass
18 18
19 19 import sys, os, time
20 20 from mercurial import hg, mdiff, fancyopts
21 21
22 22 def help():
23 23 print """\
24 24 commands:
25 25
26 26 init create a new repository in this directory
27 27 branch <path> create a branch of <path> in this directory
28 28 merge <path> merge changes from <path> into local repository
29 29 checkout [changeset] checkout the latest or given changeset
30 30 status show new, missing, and changed files in working dir
31 31 add [files...] add the given files in the next commit
32 32 remove [files...] remove the given files in the next commit
33 33 addremove add all new files, delete all missing files
34 34 commit commit all changes to the repository
35 35 history show changeset history
36 36 log <file> show revision history of a single file
37 37 dump <file> [rev] dump the latest or given revision of a file
38 38 dumpmanifest [rev] dump the latest or given revision of the manifest
39 39 diff [files...] diff working directory (or selected files)
40 40 tags show current changeset tags
41 41 annotate [files...] show changeset number per file line
42 42 blame [files...] show commit user per file line
43 43 """
44 44
45 45 def filterfiles(list, files):
46 46 l = [ x for x in list if x in files ]
47 47
48 48 for f in files:
49 49 if f[-1] != os.sep: f += os.sep
50 50 l += [ x for x in list if x.startswith(f) ]
51 51 return l
52 52
53 53 def diff(files = None, node1 = None, node2 = None):
54 54 def date(c):
55 55 return time.asctime(time.gmtime(float(c[2].split(' ')[0])))
56 56
57 57 if node2:
58 58 change = repo.changelog.read(node2)
59 59 mmap2 = repo.manifest.read(change[0])
60 60 (c, a, d) = repo.diffrevs(node1, node2)
61 61 def read(f): return repo.file(f).read(mmap2[f])
62 62 date2 = date(change)
63 63 else:
64 64 date2 = time.asctime()
65 65 if not node1:
66 66 node1 = repo.current
67 67 (c, a, d) = repo.diffdir(repo.root, node1)
68 68 def read(f): return file(os.path.join(repo.root, f)).read()
69 69
70 70 change = repo.changelog.read(node1)
71 71 mmap = repo.manifest.read(change[0])
72 72 date1 = date(change)
73 73
74 74 if files:
75 75 c = filterfiles(c, files)
76 76 d = filterfiles(d, files)
77 77
78 78 for f in c:
79 79 to = repo.file(f).read(mmap[f])
80 80 tn = read(f)
81 81 sys.stdout.write(mdiff.unidiff(to, date1, tn, date2, f))
82 82 for f in d:
83 83 to = repo.file(f).read(mmap[f])
84 84 tn = ""
85 85 sys.stdout.write(mdiff.unidiff(to, date1, tn, date2, f))
86 86
87 87 options = {}
88 88 opts = [('v', 'verbose', None, 'verbose'),
89 89 ('d', 'debug', None, 'debug'),
90 90 ('q', 'quiet', None, 'quiet'),
91 91 ('y', 'noninteractive', None, 'run non-interactively'),
92 92 ]
93 93
94 94 args = fancyopts.fancyopts(sys.argv[1:], opts, options,
95 95 'hg [options] <command> [command options] [files]')
96 96
97 97 try:
98 98 cmd = args[0]
99 99 args = args[1:]
100 100 except:
101 101 cmd = ""
102 102
103 103 ui = hg.ui(options["verbose"], options["debug"], options["quiet"],
104 104 not options["noninteractive"])
105 105
106 106 if cmd == "init":
107 107 repo = hg.repository(ui, ".", create=1)
108 108 sys.exit(0)
109 109 elif cmd == "branch" or cmd == "clone":
110 110 os.system("cp -al %s/.hg .hg" % args[0])
111 111 sys.exit(0)
112 112 elif cmd == "help":
113 113 help()
114 114 sys.exit(0)
115 115 else:
116 116 try:
117 117 repo = hg.repository(ui=ui)
118 118 except IOError:
119 119 ui.warn("Unable to open repository\n")
120 120 sys.exit(0)
121 121
122 122 relpath = None
123 123 if os.getcwd() != repo.root:
124 124 relpath = os.getcwd()[len(repo.root) + 1: ]
125 125
126 126 if cmd == "checkout" or cmd == "co":
127 127 node = repo.changelog.tip()
128 128 if args:
129 129 node = repo.lookup(args[0])
130 130 repo.checkout(node)
131 131
132 132 elif cmd == "add":
133 133 repo.add(args)
134 134
135 135 elif cmd == "remove" or cmd == "rm" or cmd == "del" or cmd == "delete":
136 136 repo.remove(args)
137 137
138 138 elif cmd == "commit" or cmd == "checkin" or cmd == "ci":
139 139 if 1:
140 140 if len(args) > 0:
141 141 repo.commit(repo.current, args)
142 142 else:
143 143 repo.commit(repo.current)
144 144
145 145 elif cmd == "import" or cmd == "patch":
146 146 try:
147 147 import psyco
148 148 psyco.full()
149 149 except:
150 150 pass
151 151
152 152 ioptions = {}
153 153 opts = [('p', 'strip', 1, 'path strip'),
154 154 ('b', 'base', "", 'base path'),
155 155 ('q', 'quiet', "", 'silence diff')
156 156 ]
157 157
158 158 args = fancyopts.fancyopts(args, opts, ioptions,
159 159 'hg import [options] <patch names>')
160 160 d = ioptions["base"]
161 161 strip = ioptions["strip"]
162 162 quiet = ioptions["quiet"] and "> /dev/null" or ""
163 163
164 164 for patch in args:
165 165 ui.status("applying %s\n" % patch)
166 166 pf = os.path.join(d, patch)
167 167
168 168 text = ""
169 169 for l in file(pf):
170 170 if l[:4] == "--- ": break
171 171 text += l
172 172
173 173 f = os.popen("lsdiff --strip %d %s" % (strip, pf))
174 174 files = filter(None, map(lambda x: x.rstrip(), f.read().splitlines()))
175 175 f.close()
176 176
177 177 if files:
178 178 if os.system("patch -p%d < %s %s" % (strip, pf, quiet)):
179 179 raise "patch failed!"
180 180 repo.commit(repo.current, files, text)
181 181
182 182 elif cmd == "status":
183 183 (c, a, d) = repo.diffdir(repo.root, repo.current)
184 184 if relpath:
185 185 (c, a, d) = map(lambda x: filterfiles(x, [ relpath ]), (c, a, d))
186 186
187 187 for f in c: print "C", f
188 188 for f in a: print "?", f
189 189 for f in d: print "R", f
190 190
191 191 elif cmd == "diff":
192 192 revs = []
193 193
194 194 if args:
195 195 doptions = {}
196 196 opts = [('r', 'revision', [], 'revision')]
197 197 args = fancyopts.fancyopts(args, opts, doptions,
198 198 'hg diff [options] [files]')
199 199 revs = map(lambda x: repo.lookup(x), doptions['revision'])
200 200
201 201 if len(revs) > 2:
202 202 self.ui.warn("too many revisions to diff\n")
203 203 sys.exit(1)
204 204
205 205 if relpath:
206 206 if not args: args = [ relpath ]
207 207 else: args = [ os.path.join(relpath, x) for x in args ]
208 208
209 209 diff(args, *revs)
210 210
211 211 elif cmd == "annotate":
212 212 aoptions = {}
213 213 opts = [('r', 'revision', '', 'revision')]
214 214 args = fancyopts.fancyopts(args, opts, aoptions,
215 215 'hg annotate [-r id] [files]')
216
216 217 if args:
218 if relpath: args = [ os.path.join(relpath, x) for x in args ]
219
217 220 node = repo.current
218 221 if aoptions['revision']:
219 222 node = repo.changelog.lookup(aoptions['revision'])
220 223 change = repo.changelog.read(node)
221 224 mmap = repo.manifest.read(change[0])
222 225 for f in args:
223 226 for n, l in repo.file(f).annotate(mmap[f]):
224 227 sys.stdout.write("% 6s:%s"%(n, l))
225 228
226 229 elif cmd == "blame":
227 230 aoptions = {}
228 231 opts = [('r', 'revision', '', 'revision')]
229 232 args = fancyopts.fancyopts(args, opts, aoptions,
230 233 'hg blame [-r id] [files]')
231 234 if args:
232 235 bcache = {}
233 236 node = repo.current
234 237 if aoptions['revision']:
235 238 node = repo.changelog.lookup(aoptions['revision'])
236 239 change = repo.changelog.read(node)
237 240 mmap = repo.manifest.read(change[0])
238 241 for f in args:
239 242 for n, l in repo.file(f).annotate(mmap[f]):
240 243 try:
241 244 name = bcache[n]
242 245 except KeyError:
243 246 cl = repo.changelog.read(repo.changelog.node(n))
244 247 name = cl[1]
245 248 f = name.find('@')
246 249 if f >= 0:
247 250 name = name[:f]
248 251 bcache[n] = name
249 252 sys.stdout.write("% 10s:%s"%(name, l))
250 253
251 254 elif cmd == "export":
252 255 node = repo.lookup(args[0])
253 256 prev, other = repo.changelog.parents(node)
254 257 change = repo.changelog.read(node)
255 258 print "# HG changeset patch"
256 259 print "# User %s" % change[1]
257 260 print "# Node ID %s" % hg.hex(node)
258 261 print "# Parent %s" % hg.hex(prev)
259 262 print
260 263 if other != hg.nullid:
261 264 print "# Parent %s" % hg.hex(other)
262 265 print change[4]
263 266
264 267 diff(None, prev, node)
265 268
266 269 elif cmd == "debugchangegroup":
267 270 newer = repo.newer(map(repo.lookup, args))
268 271 for chunk in repo.changegroup(newer):
269 272 sys.stdout.write(chunk)
270 273
271 274 elif cmd == "debugaddchangegroup":
272 275 data = sys.stdin.read()
273 276 repo.addchangegroup(data)
274 277
275 278 elif cmd == "addremove":
276 279 (c, a, d) = repo.diffdir(repo.root, repo.current)
277 280 repo.add(a)
278 281 repo.remove(d)
279 282
280 283 elif cmd == "history":
281 284 for i in range(repo.changelog.count()):
282 285 n = repo.changelog.node(i)
283 286 changes = repo.changelog.read(n)
284 287 (p1, p2) = repo.changelog.parents(n)
285 288 (h, h1, h2) = map(hg.hex, (n, p1, p2))
286 289 (i1, i2) = map(repo.changelog.rev, (p1, p2))
287 290 print "rev: %4d:%s" % (i, h)
288 291 print "parents: %4d:%s" % (i1, h1)
289 292 if i2: print " %4d:%s" % (i2, h2)
290 293 print "manifest: %4d:%s" % (repo.manifest.rev(changes[0]),
291 294 hg.hex(changes[0]))
292 295 print "user:", changes[1]
293 296 print "date:", time.asctime(
294 297 time.localtime(float(changes[2].split(' ')[0])))
295 298 print "files:", " ".join(changes[3])
296 299 print "description:"
297 300 print changes[4]
298 301
299 302 elif cmd == "tip":
300 303 n = repo.changelog.tip()
301 304 t = repo.changelog.rev(n)
302 305 ui.status("%d:%s\n" % (t, hg.hex(n)))
303 306
304 307 elif cmd == "log":
305 308
306 309 if len(args) == 1:
307 310 if relpath:
308 311 args[0] = os.path.join(relpath, args[0])
309 312
310 313 r = repo.file(args[0])
311 314 for i in range(r.count()):
312 315 n = r.node(i)
313 316 (p1, p2) = r.parents(n)
314 317 (h, h1, h2) = map(hg.hex, (n, p1, p2))
315 318 (i1, i2) = map(r.rev, (p1, p2))
316 319 cr = r.linkrev(n)
317 320 cn = hg.hex(repo.changelog.node(cr))
318 321 print "rev: %4d:%s" % (i, h)
319 322 print "changeset: %4d:%s" % (cr, cn)
320 323 print "parents: %4d:%s" % (i1, h1)
321 324 if i2: print " %4d:%s" % (i2, h2)
322 325 changes = repo.changelog.read(repo.changelog.node(cr))
323 326 print "user: %s" % changes[1]
324 327 print "date: %s" % time.asctime(
325 328 time.localtime(float(changes[2].split(' ')[0])))
326 329 print "description:"
327 330 print changes[4]
328 331 print
329 332 elif len(args) > 1:
330 333 print "too many args"
331 334 else:
332 335 print "missing filename"
333 336
334 337 elif cmd == "dump":
335 338 if args:
336 339 r = repo.file(args[0])
337 340 n = r.tip()
338 341 if len(args) > 1: n = r.lookup(args[1])
339 342 sys.stdout.write(r.read(n))
340 343 else:
341 344 print "missing filename"
342 345
343 346 elif cmd == "dumpmanifest":
344 347 n = repo.manifest.tip()
345 348 if len(args) > 0:
346 349 n = repo.manifest.lookup(args[0])
347 350 m = repo.manifest.read(n)
348 351 files = m.keys()
349 352 files.sort()
350 353
351 354 for f in files:
352 355 print hg.hex(m[f]), f
353 356
354 357 elif cmd == "debugprompt":
355 358 print ui.prompt(args[0], args[1], args[2])
356 359
357 360 elif cmd == "debughash":
358 361 f = repo.file(args[0])
359 362 print f.encodepath(args[0])
360 363
361 364 elif cmd == "debugindex":
362 365 if ".hg" not in args[0]:
363 366 args[0] = ".hg/data/" + repo.file(args[0]).encodepath(args[0]) + "i"
364 367
365 368 r = hg.revlog(open, args[0], "")
366 369 print " rev offset length base linkrev"+\
367 370 " p1 p2 nodeid"
368 371 for i in range(r.count()):
369 372 e = r.index[i]
370 373 print "% 6d % 9d % 7d % 6d % 7d %s.. %s.. %s.." % (
371 374 i, e[0], e[1], e[2], e[3],
372 375 hg.hex(e[4][:5]), hg.hex(e[5][:5]), hg.hex(e[6][:5]))
373 376
374 377 elif cmd == "debugindexdot":
375 378 if ".hg" not in args[0]:
376 379 args[0] = ".hg/data/" + repo.file(args[0]).encodepath(args[0]) + "i"
377 380
378 381 r = hg.revlog(open, args[0], "")
379 382 print "digraph G {"
380 383 for i in range(r.count()):
381 384 e = r.index[i]
382 385 print "\t%d -> %d" % (r.rev(e[4]), i)
383 386 if e[5] != hg.nullid:
384 387 print "\t%d -> %d" % (r.rev(e[5]), i)
385 388 print "}"
386 389
387 390 elif cmd == "merge":
388 391 if args:
389 392 other = hg.repository(ui, args[0])
390 393 ui.status("requesting changegroup\n")
391 394 cg = repo.getchangegroup(other)
392 395 repo.addchangegroup(cg)
393 396 else:
394 397 print "missing source repository"
395 398
396 399 elif cmd == "tags":
397 400 repo.lookup(0) # prime the cache
398 401 i = repo.tags.items()
399 402 i.sort()
400 403 for k, n in i:
401 404 try:
402 405 r = repo.changelog.rev(n)
403 406 except KeyError:
404 407 r = "?"
405 408 print "%-30s %5d:%s" % (k, repo.changelog.rev(n), hg.hex(n))
406 409
407 410 elif cmd == "debugoldmerge":
408 411 if args:
409 412 other = hg.repository(ui, args[0])
410 413 repo.merge(other)
411 414 else:
412 415 print "missing source repository"
413 416
414 417 elif cmd == "verify":
415 418 filelinkrevs = {}
416 419 filenodes = {}
417 420 manifestchangeset = {}
418 421 changesets = revisions = files = 0
419 422 errors = 0
420 423
421 424 ui.status("checking changesets\n")
422 425 for i in range(repo.changelog.count()):
423 426 changesets += 1
424 427 n = repo.changelog.node(i)
425 428 for p in repo.changelog.parents(n):
426 429 if p not in repo.changelog.nodemap:
427 430 ui.warn("changeset %s has unknown parent %s\n" %
428 431 (hg.short(n), hg.short(p)))
429 432 errors += 1
430 433 try:
431 434 changes = repo.changelog.read(n)
432 435 except Error, inst:
433 436 ui.warn("unpacking changeset %s: %s\n" % (short(n), inst))
434 437 errors += 1
435 438
436 439 manifestchangeset[changes[0]] = n
437 440 for f in changes[3]:
438 441 revisions += 1
439 442 filelinkrevs.setdefault(f, []).append(i)
440 443
441 444 ui.status("checking manifests\n")
442 445 for i in range(repo.manifest.count()):
443 446 n = repo.manifest.node(i)
444 447 for p in repo.manifest.parents(n):
445 448 if p not in repo.manifest.nodemap:
446 449 ui.warn("manifest %s has unknown parent %s\n" %
447 450 (hg.short(n), hg.short(p)))
448 451 errors += 1
449 452 ca = repo.changelog.node(repo.manifest.linkrev(n))
450 453 cc = manifestchangeset[n]
451 454 if ca != cc:
452 455 ui.warn("manifest %s points to %s, not %s\n" %
453 456 (hg.hex(n), hg.hex(ca), hg.hex(cc)))
454 457 errors += 1
455 458
456 459 try:
457 460 delta = mdiff.patchtext(repo.manifest.delta(n))
458 461 except KeyboardInterrupt:
459 462 print "aborted"
460 463 sys.exit(0)
461 464 except Exception, inst:
462 465 ui.warn("unpacking manifest %s: %s\n" % (hg.short(n), inst))
463 466 errors += 1
464 467
465 468 ff = [ l.split('\0') for l in delta.splitlines() ]
466 469 for f, fn in ff:
467 470 filenodes.setdefault(f, {})[hg.bin(fn)] = 1
468 471
469 472 ui.status("crosschecking files in changesets and manifests\n")
470 473 for f in filenodes:
471 474 if f not in filelinkrevs:
472 475 ui.warn("file %s in manifest but not in changesets\n" % f)
473 476 errors += 1
474 477
475 478 for f in filelinkrevs:
476 479 if f not in filenodes:
477 480 ui.warn("file %s in changeset but not in manifest\n" % f)
478 481 errors += 1
479 482
480 483 ui.status("checking files\n")
481 484 ff = filenodes.keys()
482 485 ff.sort()
483 486 for f in ff:
484 487 if f == "/dev/null": continue
485 488 files += 1
486 489 fl = repo.file(f)
487 490 nodes = { hg.nullid: 1 }
488 491 for i in range(fl.count()):
489 492 n = fl.node(i)
490 493
491 494 if n not in filenodes[f]:
492 495 ui.warn("%s: %d:%s not in manifests\n" % (f, i, hg.short(n)))
493 496 print len(filenodes[f].keys()), fl.count(), f
494 497 errors += 1
495 498 else:
496 499 del filenodes[f][n]
497 500
498 501 flr = fl.linkrev(n)
499 502 if flr not in filelinkrevs[f]:
500 503 ui.warn("%s:%s points to unexpected changeset rev %d\n"
501 504 % (f, hg.short(n), fl.linkrev(n)))
502 505 errors += 1
503 506 else:
504 507 filelinkrevs[f].remove(flr)
505 508
506 509 # verify contents
507 510 try:
508 511 t = fl.read(n)
509 512 except Error, inst:
510 513 ui.warn("unpacking file %s %s: %s\n" % (f, short(n), inst))
511 514 errors += 1
512 515
513 516 # verify parents
514 517 (p1, p2) = fl.parents(n)
515 518 if p1 not in nodes:
516 519 ui.warn("file %s:%s unknown parent 1 %s" %
517 520 (f, hg.short(n), hg.short(p1)))
518 521 errors += 1
519 522 if p2 not in nodes:
520 523 ui.warn("file %s:%s unknown parent 2 %s" %
521 524 (f, hg.short(n), hg.short(p1)))
522 525 errors += 1
523 526 nodes[n] = 1
524 527
525 528 # cross-check
526 529 for flr in filelinkrevs[f]:
527 530 ui.warn("changeset rev %d not in %s\n" % (flr, f))
528 531 errors += 1
529 532
530 533 for node in filenodes[f]:
531 534 ui.warn("node %s in manifests not in %s\n" % (hg.hex(n), f))
532 535 errors += 1
533 536
534 537 ui.status("%d files, %d changesets, %d total revisions\n" %
535 538 (files, changesets, revisions))
536 539
537 540 if errors:
538 541 ui.warn("%d integrity errors encountered!\n" % errors)
539 542 sys.exit(1)
540 543
541 544 else:
542 545 print "unknown command\n"
543 546 help()
544 547 sys.exit(1)
General Comments 0
You need to be logged in to leave comments. Login now