##// END OF EJS Templates
make diffdir default to dirstate.parents()...
mpm@selenic.com -
r230:00ea3613 default
parent child Browse files
Show More
@@ -1,532 +1,532 b''
1 1 #!/usr/bin/env python
2 2 #
3 3 # mercurial - a minimal scalable distributed SCM
4 4 # v0.5b "katje"
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, ui, commands
21 21
22 22 def help():
23 23 ui.status("""\
24 24 commands:
25 25
26 26 add [files...] add the given files in the next commit
27 27 addremove add all new files, delete all missing files
28 28 annotate [files...] show changeset number per file line
29 29 branch <path> create a branch of <path> in this directory
30 30 checkout [changeset] checkout the latest or given changeset
31 31 commit commit all changes to the repository
32 32 diff [files...] diff working directory (or selected files)
33 33 dump <file> [rev] dump the latest or given revision of a file
34 34 dumpmanifest [rev] dump the latest or given revision of the manifest
35 35 export <rev> dump the changeset header and diffs for a revision
36 36 history show changeset history
37 37 init create a new repository in this directory
38 38 log <file> show revision history of a single file
39 39 merge <path> merge changes from <path> into local repository
40 40 recover rollback an interrupted transaction
41 41 remove [files...] remove the given files in the next commit
42 42 serve export the repository via HTTP
43 43 status show new, missing, and changed files in working dir
44 44 tags show current changeset tags
45 45 undo undo the last transaction
46 46 """)
47 47
48 48 def filterfiles(list, files):
49 49 l = [ x for x in list if x in files ]
50 50
51 51 for f in files:
52 52 if f[-1] != os.sep: f += os.sep
53 53 l += [ x for x in list if x.startswith(f) ]
54 54 return l
55 55
56 56 def diff(files = None, node1 = None, node2 = None):
57 57 def date(c):
58 58 return time.asctime(time.gmtime(float(c[2].split(' ')[0])))
59 59
60 60 if node2:
61 61 change = repo.changelog.read(node2)
62 62 mmap2 = repo.manifest.read(change[0])
63 63 (c, a, d) = repo.diffrevs(node1, node2)
64 64 def read(f): return repo.file(f).read(mmap2[f])
65 65 date2 = date(change)
66 66 else:
67 67 date2 = time.asctime()
68 68 if not node1:
69 69 node1 = repo.dirstate.parents()[0]
70 70 (c, a, d, u) = repo.diffdir(repo.root, node1)
71 71 def read(f): return file(os.path.join(repo.root, f)).read()
72 72
73 73 change = repo.changelog.read(node1)
74 74 mmap = repo.manifest.read(change[0])
75 75 date1 = date(change)
76 76
77 77 if files:
78 78 c, a, d = map(lambda x: filterfiles(x, files), (c, a, d))
79 79
80 80 for f in c:
81 81 to = repo.file(f).read(mmap[f])
82 82 tn = read(f)
83 83 sys.stdout.write(mdiff.unidiff(to, date1, tn, date2, f))
84 84 for f in a:
85 85 to = ""
86 86 tn = read(f)
87 87 sys.stdout.write(mdiff.unidiff(to, date1, tn, date2, f))
88 88 for f in d:
89 89 to = repo.file(f).read(mmap[f])
90 90 tn = ""
91 91 sys.stdout.write(mdiff.unidiff(to, date1, tn, date2, f))
92 92
93 93
94 94 try:
95 95 sys.exit(commands.dispatch(sys.argv[1:]))
96 96 except commands.UnknownCommand:
97 97 # fall through
98 98 pass
99 99
100 100 options = {}
101 101 opts = [('v', 'verbose', None, 'verbose'),
102 102 ('d', 'debug', None, 'debug'),
103 103 ('q', 'quiet', None, 'quiet'),
104 104 ('y', 'noninteractive', None, 'run non-interactively'),
105 105 ]
106 106
107 107 args = fancyopts.fancyopts(sys.argv[1:], opts, options,
108 108 'hg [options] <command> [command options] [files]')
109 109
110 110 try:
111 111 cmd = args[0]
112 112 args = args[1:]
113 113 except:
114 114 cmd = "help"
115 115
116 116 ui = ui.ui(options["verbose"], options["debug"], options["quiet"],
117 117 not options["noninteractive"])
118 118
119 119 try:
120 120 repo = hg.repository(ui=ui)
121 121 except IOError:
122 122 ui.warn("Unable to open repository\n")
123 123 sys.exit(0)
124 124
125 125 relpath = None
126 126 if os.getcwd() != repo.root:
127 127 relpath = os.getcwd()[len(repo.root) + 1: ]
128 128
129 129 elif cmd == "add":
130 130 repo.add(args)
131 131
132 132 elif cmd == "forget":
133 133 repo.forget(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(args)
142 142 else:
143 143 repo.commit()
144 144 elif cmd == "rawcommit":
145 145 "raw commit interface"
146 146 rc = {}
147 147 opts = [('p', 'parent', [], 'parent'),
148 148 ('d', 'date', "", 'data'),
149 149 ('u', 'user', "", 'user'),
150 150 ('F', 'files', "", 'file list'),
151 151 ('t', 'text', "", 'commit text'),
152 152 ('l', 'logfile', "", 'commit text file')
153 153 ]
154 154 args = fancyopts.fancyopts(args, opts, rc,
155 155 "hg rawcommit [options] files")
156 156 text = rc['text']
157 157 if not text and rc['logfile']:
158 158 try: text = open(rc['logfile']).read()
159 159 except IOError: pass
160 160 if not text and not rc['logfile']:
161 161 print "missing commit text"
162 162 sys.exit(0)
163 163 if rc['files']:
164 164 files = open(rc['files']).read().splitlines()
165 165 else:
166 166 files = args
167 167
168 168 repo.rawcommit(files, text, rc['user'], rc['date'], *rc['parent'])
169 169
170 170
171 171 elif cmd == "import" or cmd == "patch":
172 172 try:
173 173 import psyco
174 174 psyco.full()
175 175 except:
176 176 pass
177 177
178 178 ioptions = {}
179 179 opts = [('p', 'strip', 1, 'path strip'),
180 180 ('b', 'base', "", 'base path'),
181 181 ('q', 'quiet', "", 'silence diff')
182 182 ]
183 183
184 184 args = fancyopts.fancyopts(args, opts, ioptions,
185 185 'hg import [options] <patch names>')
186 186 d = ioptions["base"]
187 187 strip = ioptions["strip"]
188 188 quiet = ioptions["quiet"] and "> /dev/null" or ""
189 189
190 190 for patch in args:
191 191 ui.status("applying %s\n" % patch)
192 192 pf = os.path.join(d, patch)
193 193
194 194 text = ""
195 195 for l in file(pf):
196 196 if l[:4] == "--- ": break
197 197 text += l
198 198
199 199 f = os.popen("lsdiff --strip %d %s" % (strip, pf))
200 200 files = filter(None, map(lambda x: x.rstrip(), f.read().splitlines()))
201 201 f.close()
202 202
203 203 if files:
204 204 if os.system("patch -p%d < %s %s" % (strip, pf, quiet)):
205 205 raise "patch failed!"
206 206 repo.commit(files, text)
207 207
208 208 elif cmd == "diff":
209 209 revs = []
210 210
211 211 if args:
212 212 doptions = {}
213 213 opts = [('r', 'revision', [], 'revision')]
214 214 args = fancyopts.fancyopts(args, opts, doptions,
215 215 'hg diff [options] [files]')
216 216 revs = map(lambda x: repo.lookup(x), doptions['revision'])
217 217
218 218 if len(revs) > 2:
219 219 self.ui.warn("too many revisions to diff\n")
220 220 sys.exit(1)
221 221
222 222 if relpath:
223 223 if not args: args = [ relpath ]
224 224 else: args = [ os.path.join(relpath, x) for x in args ]
225 225
226 226 diff(args, *revs)
227 227
228 228 elif cmd == "export":
229 229 node = repo.lookup(args[0])
230 230 prev, other = repo.changelog.parents(node)
231 231 change = repo.changelog.read(node)
232 232 print "# HG changeset patch"
233 233 print "# User %s" % change[1]
234 234 print "# Node ID %s" % hg.hex(node)
235 235 print "# Parent %s" % hg.hex(prev)
236 236 print
237 237 if other != hg.nullid:
238 238 print "# Parent %s" % hg.hex(other)
239 239 print change[4]
240 240
241 241 diff(None, prev, node)
242 242
243 243 elif cmd == "debugchangegroup":
244 244 newer = repo.newer(map(repo.lookup, args))
245 245 for chunk in repo.changegroup(newer):
246 246 sys.stdout.write(chunk)
247 247
248 248 elif cmd == "debugaddchangegroup":
249 249 data = sys.stdin.read()
250 250 repo.addchangegroup(data)
251 251
252 252 elif cmd == "addremove":
253 (c, a, d, u) = repo.diffdir(repo.root, repo.current)
253 (c, a, d, u) = repo.diffdir(repo.root)
254 254 repo.add(a)
255 255 repo.remove(d)
256 256
257 257 elif cmd == "history":
258 258 for i in range(repo.changelog.count()):
259 259 n = repo.changelog.node(i)
260 260 changes = repo.changelog.read(n)
261 261 (p1, p2) = repo.changelog.parents(n)
262 262 (h, h1, h2) = map(hg.hex, (n, p1, p2))
263 263 (i1, i2) = map(repo.changelog.rev, (p1, p2))
264 264 print "rev: %4d:%s" % (i, h)
265 265 print "parents: %4d:%s" % (i1, h1)
266 266 if i2: print " %4d:%s" % (i2, h2)
267 267 print "manifest: %4d:%s" % (repo.manifest.rev(changes[0]),
268 268 hg.hex(changes[0]))
269 269 print "user:", changes[1]
270 270 print "date:", time.asctime(
271 271 time.localtime(float(changes[2].split(' ')[0])))
272 272 if ui.verbose: print "files:", " ".join(changes[3])
273 273 print "description:"
274 274 print changes[4]
275 275
276 276 elif cmd == "tip":
277 277 n = repo.changelog.tip()
278 278 t = repo.changelog.rev(n)
279 279 ui.status("%d:%s\n" % (t, hg.hex(n)))
280 280
281 281 elif cmd == "log":
282 282
283 283 if len(args) == 1:
284 284 if relpath:
285 285 args[0] = os.path.join(relpath, args[0])
286 286
287 287 r = repo.file(args[0])
288 288 for i in range(r.count()):
289 289 n = r.node(i)
290 290 (p1, p2) = r.parents(n)
291 291 (h, h1, h2) = map(hg.hex, (n, p1, p2))
292 292 (i1, i2) = map(r.rev, (p1, p2))
293 293 cr = r.linkrev(n)
294 294 cn = hg.hex(repo.changelog.node(cr))
295 295 print "rev: %4d:%s" % (i, h)
296 296 print "changeset: %4d:%s" % (cr, cn)
297 297 print "parents: %4d:%s" % (i1, h1)
298 298 if i2: print " %4d:%s" % (i2, h2)
299 299 changes = repo.changelog.read(repo.changelog.node(cr))
300 300 print "user: %s" % changes[1]
301 301 print "date: %s" % time.asctime(
302 302 time.localtime(float(changes[2].split(' ')[0])))
303 303 print "description:"
304 304 print changes[4]
305 305 print
306 306 elif len(args) > 1:
307 307 print "too many args"
308 308 else:
309 309 print "missing filename"
310 310
311 311 elif cmd == "dump":
312 312 if args:
313 313 r = repo.file(args[0])
314 314 n = r.tip()
315 315 if len(args) > 1: n = r.lookup(args[1])
316 316 sys.stdout.write(r.read(n))
317 317 else:
318 318 print "missing filename"
319 319
320 320 elif cmd == "dumpmanifest":
321 321 n = repo.manifest.tip()
322 322 if len(args) > 0:
323 323 n = repo.manifest.lookup(args[0])
324 324 m = repo.manifest.read(n)
325 325 files = m.keys()
326 326 files.sort()
327 327
328 328 for f in files:
329 329 print hg.hex(m[f]), f
330 330
331 331 elif cmd == "debugindex":
332 332 if ".hg" not in args[0]:
333 333 args[0] = ".hg/data/" + repo.file(args[0]).encodepath(args[0]) + "i"
334 334
335 335 r = hg.revlog(open, args[0], "")
336 336 print " rev offset length base linkrev"+\
337 337 " p1 p2 nodeid"
338 338 for i in range(r.count()):
339 339 e = r.index[i]
340 340 print "% 6d % 9d % 7d % 6d % 7d %s.. %s.. %s.." % (
341 341 i, e[0], e[1], e[2], e[3],
342 342 hg.hex(e[4][:5]), hg.hex(e[5][:5]), hg.hex(e[6][:5]))
343 343
344 344 elif cmd == "debugindexdot":
345 345 if ".hg" not in args[0]:
346 346 args[0] = ".hg/data/" + repo.file(args[0]).encodepath(args[0]) + "i"
347 347
348 348 r = hg.revlog(open, args[0], "")
349 349 print "digraph G {"
350 350 for i in range(r.count()):
351 351 e = r.index[i]
352 352 print "\t%d -> %d" % (r.rev(e[4]), i)
353 353 if e[5] != hg.nullid:
354 354 print "\t%d -> %d" % (r.rev(e[5]), i)
355 355 print "}"
356 356
357 357 elif cmd == "merge":
358 358 if args:
359 359 paths = {}
360 360 try:
361 361 pf = os.path.join(os.environ["HOME"], ".hgpaths")
362 362 for l in file(pf):
363 363 name, path = l.split()
364 364 paths[name] = path
365 365 except:
366 366 pass
367 367
368 368 if args[0] in paths: args[0] = paths[args[0]]
369 369
370 370 other = hg.repository(ui, args[0])
371 371 cg = repo.getchangegroup(other)
372 372 repo.addchangegroup(cg)
373 373 else:
374 374 print "missing source repository"
375 375
376 376 elif cmd == "tags":
377 377 repo.lookup(0) # prime the cache
378 378 i = repo.tags.items()
379 379 i.sort()
380 380 for k, n in i:
381 381 try:
382 382 r = repo.changelog.rev(n)
383 383 except KeyError:
384 384 r = "?"
385 385 print "%-30s %5d:%s" % (k, repo.changelog.rev(n), hg.hex(n))
386 386
387 387 elif cmd == "recover":
388 388 repo.recover()
389 389
390 390 elif cmd == "verify":
391 391 filelinkrevs = {}
392 392 filenodes = {}
393 393 manifestchangeset = {}
394 394 changesets = revisions = files = 0
395 395 errors = 0
396 396
397 397 ui.status("checking changesets\n")
398 398 for i in range(repo.changelog.count()):
399 399 changesets += 1
400 400 n = repo.changelog.node(i)
401 401 for p in repo.changelog.parents(n):
402 402 if p not in repo.changelog.nodemap:
403 403 ui.warn("changeset %s has unknown parent %s\n" %
404 404 (hg.short(n), hg.short(p)))
405 405 errors += 1
406 406 try:
407 407 changes = repo.changelog.read(n)
408 408 except Exception, inst:
409 409 ui.warn("unpacking changeset %s: %s\n" % (short(n), inst))
410 410 errors += 1
411 411
412 412 manifestchangeset[changes[0]] = n
413 413 for f in changes[3]:
414 414 filelinkrevs.setdefault(f, []).append(i)
415 415
416 416 ui.status("checking manifests\n")
417 417 for i in range(repo.manifest.count()):
418 418 n = repo.manifest.node(i)
419 419 for p in repo.manifest.parents(n):
420 420 if p not in repo.manifest.nodemap:
421 421 ui.warn("manifest %s has unknown parent %s\n" %
422 422 (hg.short(n), hg.short(p)))
423 423 errors += 1
424 424 ca = repo.changelog.node(repo.manifest.linkrev(n))
425 425 cc = manifestchangeset[n]
426 426 if ca != cc:
427 427 ui.warn("manifest %s points to %s, not %s\n" %
428 428 (hg.hex(n), hg.hex(ca), hg.hex(cc)))
429 429 errors += 1
430 430
431 431 try:
432 432 delta = mdiff.patchtext(repo.manifest.delta(n))
433 433 except KeyboardInterrupt:
434 434 print "aborted"
435 435 sys.exit(0)
436 436 except Exception, inst:
437 437 ui.warn("unpacking manifest %s: %s\n" % (hg.short(n), inst))
438 438 errors += 1
439 439
440 440 ff = [ l.split('\0') for l in delta.splitlines() ]
441 441 for f, fn in ff:
442 442 filenodes.setdefault(f, {})[hg.bin(fn)] = 1
443 443
444 444 ui.status("crosschecking files in changesets and manifests\n")
445 445 for f in filenodes:
446 446 if f not in filelinkrevs:
447 447 ui.warn("file %s in manifest but not in changesets\n" % f)
448 448 errors += 1
449 449
450 450 for f in filelinkrevs:
451 451 if f not in filenodes:
452 452 ui.warn("file %s in changeset but not in manifest\n" % f)
453 453 errors += 1
454 454
455 455 ui.status("checking files\n")
456 456 ff = filenodes.keys()
457 457 ff.sort()
458 458 for f in ff:
459 459 if f == "/dev/null": continue
460 460 files += 1
461 461 fl = repo.file(f)
462 462 nodes = { hg.nullid: 1 }
463 463 for i in range(fl.count()):
464 464 revisions += 1
465 465 n = fl.node(i)
466 466
467 467 if n not in filenodes[f]:
468 468 ui.warn("%s: %d:%s not in manifests\n" % (f, i, hg.short(n)))
469 469 print len(filenodes[f].keys()), fl.count(), f
470 470 errors += 1
471 471 else:
472 472 del filenodes[f][n]
473 473
474 474 flr = fl.linkrev(n)
475 475 if flr not in filelinkrevs[f]:
476 476 ui.warn("%s:%s points to unexpected changeset rev %d\n"
477 477 % (f, hg.short(n), fl.linkrev(n)))
478 478 errors += 1
479 479 else:
480 480 filelinkrevs[f].remove(flr)
481 481
482 482 # verify contents
483 483 try:
484 484 t = fl.read(n)
485 485 except Exception, inst:
486 486 ui.warn("unpacking file %s %s: %s\n" % (f, hg.short(n), inst))
487 487 errors += 1
488 488
489 489 # verify parents
490 490 (p1, p2) = fl.parents(n)
491 491 if p1 not in nodes:
492 492 ui.warn("file %s:%s unknown parent 1 %s" %
493 493 (f, hg.short(n), hg.short(p1)))
494 494 errors += 1
495 495 if p2 not in nodes:
496 496 ui.warn("file %s:%s unknown parent 2 %s" %
497 497 (f, hg.short(n), hg.short(p1)))
498 498 errors += 1
499 499 nodes[n] = 1
500 500
501 501 # cross-check
502 502 for node in filenodes[f]:
503 503 ui.warn("node %s in manifests not in %s\n" % (hg.hex(n), f))
504 504 errors += 1
505 505
506 506 ui.status("%d files, %d changesets, %d total revisions\n" %
507 507 (files, changesets, revisions))
508 508
509 509 if errors:
510 510 ui.warn("%d integrity errors encountered!\n" % errors)
511 511 sys.exit(1)
512 512
513 513 elif cmd == "serve":
514 514 from mercurial import hgweb
515 515
516 516 soptions = {}
517 517 opts = [('p', 'port', 8000, 'listen port'),
518 518 ('a', 'address', '', 'interface address'),
519 519 ('n', 'name', os.getcwd(), 'repository name'),
520 520 ('t', 'templates', "", 'template map')
521 521 ]
522 522
523 523 args = fancyopts.fancyopts(args, opts, soptions,
524 524 'hg serve [options]')
525 525
526 526 hgweb.server(repo.root, soptions["name"], soptions["templates"],
527 527 soptions["address"], soptions["port"])
528 528
529 529 else:
530 530 if cmd: ui.warn("unknown command\n\n")
531 531 help()
532 532 sys.exit(1)
@@ -1,252 +1,252 b''
1 1 import os, re, traceback, sys, signal, time
2 2 from mercurial import fancyopts, ui, hg
3 3
4 4 class UnknownCommand(Exception): pass
5 5
6 6 def filterfiles(list, files):
7 7 l = [ x for x in list if x in files ]
8 8
9 9 for f in files:
10 10 if f[-1] != os.sep: f += os.sep
11 11 l += [ x for x in list if x.startswith(f) ]
12 12 return l
13 13
14 14 def relfilter(repo, args):
15 15 if os.getcwd() != repo.root:
16 16 p = os.getcwd()[len(repo.root) + 1: ]
17 17 return filterfiles(p, args)
18 18 return args
19 19
20 20 def relpath(repo, args):
21 21 if os.getcwd() != repo.root:
22 22 p = os.getcwd()[len(repo.root) + 1: ]
23 23 return [ os.path.join(p, x) for x in args ]
24 24 return args
25 25
26 26 def help(ui, cmd=None):
27 27 '''show help'''
28 28 if cmd:
29 29 try:
30 30 i = find(cmd)
31 31 ui.write("%s\n\n" % i[2])
32 32 ui.write(i[0].__doc__, "\n")
33 33 except UnknownCommand:
34 34 ui.warn("unknown command %s", cmd)
35 35 sys.exit(0)
36 36
37 37 ui.status("""\
38 38 hg commands:
39 39
40 40 add [files...] add the given files in the next commit
41 41 addremove add all new files, delete all missing files
42 42 annotate [files...] show changeset number per file line
43 43 branch <path> create a branch of <path> in this directory
44 44 checkout [changeset] checkout the latest or given changeset
45 45 commit commit all changes to the repository
46 46 diff [files...] diff working directory (or selected files)
47 47 dump <file> [rev] dump the latest or given revision of a file
48 48 dumpmanifest [rev] dump the latest or given revision of the manifest
49 49 export <rev> dump the changeset header and diffs for a revision
50 50 history show changeset history
51 51 init create a new repository in this directory
52 52 log <file> show revision history of a single file
53 53 merge <path> merge changes from <path> into local repository
54 54 recover rollback an interrupted transaction
55 55 remove [files...] remove the given files in the next commit
56 56 serve export the repository via HTTP
57 57 status show new, missing, and changed files in working dir
58 58 tags show current changeset tags
59 59 undo undo the last transaction
60 60 """)
61 61
62 62 def init(ui):
63 63 """create a repository"""
64 64 hg.repository(ui, ".", create=1)
65 65
66 66 def branch(ui, path):
67 67 '''branch from a local repository'''
68 68 # this should eventually support remote repos
69 69 os.system("cp -al %s/.hg .hg" % path)
70 70
71 71 def checkout(ui, repo, changeset=None):
72 72 '''checkout a given changeset or the current tip'''
73 (c, a, d, u) = repo.diffdir(repo.root, repo.current)
73 (c, a, d, u) = repo.diffdir(repo.root)
74 74 if c or a or d:
75 75 ui.warn("aborting (outstanding changes in working directory)\n")
76 76 sys.exit(1)
77 77
78 78 node = repo.changelog.tip()
79 79 if changeset:
80 80 node = repo.lookup(changeset)
81 81 repo.checkout(node)
82 82
83 83 def annotate(u, repo, *args, **ops):
84 84 def getnode(rev):
85 85 return hg.short(repo.changelog.node(rev))
86 86
87 87 def getname(rev):
88 88 try:
89 89 return bcache[rev]
90 90 except KeyError:
91 91 cl = repo.changelog.read(repo.changelog.node(rev))
92 92 name = cl[1]
93 93 f = name.find('@')
94 94 if f >= 0:
95 95 name = name[:f]
96 96 bcache[rev] = name
97 97 return name
98 98
99 99 bcache = {}
100 100 opmap = [['user', getname], ['number', str], ['changeset', getnode]]
101 101 if not ops['user'] and not ops['changeset']:
102 102 ops['number'] = 1
103 103
104 104 args = relpath(repo, args)
105 105 node = repo.dirstate.parents()[0]
106 106 if ops['revision']:
107 107 node = repo.changelog.lookup(ops['revision'])
108 108 change = repo.changelog.read(node)
109 109 mmap = repo.manifest.read(change[0])
110 110 maxuserlen = 0
111 111 maxchangelen = 0
112 112 for f in args:
113 113 lines = repo.file(f).annotate(mmap[f])
114 114 pieces = []
115 115
116 116 for o, f in opmap:
117 117 if ops[o]:
118 118 l = [ f(n) for n,t in lines ]
119 119 m = max(map(len, l))
120 120 pieces.append([ "%*s" % (m, x) for x in l])
121 121
122 122 for p,l in zip(zip(*pieces), lines):
123 123 u.write(" ".join(p) + ": " + l[1])
124 124
125 125 def heads(ui, repo):
126 126 '''show current repository heads'''
127 127 for n in repo.changelog.heads():
128 128 i = repo.changelog.rev(n)
129 129 changes = repo.changelog.read(n)
130 130 (p1, p2) = repo.changelog.parents(n)
131 131 (h, h1, h2) = map(hg.hex, (n, p1, p2))
132 132 (i1, i2) = map(repo.changelog.rev, (p1, p2))
133 133 print "rev: %4d:%s" % (i, h)
134 134 print "parents: %4d:%s" % (i1, h1)
135 135 if i2: print " %4d:%s" % (i2, h2)
136 136 print "manifest: %4d:%s" % (repo.manifest.rev(changes[0]),
137 137 hg.hex(changes[0]))
138 138 print "user:", changes[1]
139 139 print "date:", time.asctime(
140 140 time.localtime(float(changes[2].split(' ')[0])))
141 141 if ui.verbose: print "files:", " ".join(changes[3])
142 142 print "description:"
143 143 print changes[4]
144 144
145 145 def parents(ui, repo, node = None):
146 146 '''show the parents of the current working dir'''
147 147 if node:
148 148 p = repo.changelog.parents(repo.lookup(hg.bin(node)))
149 149 else:
150 150 p = repo.dirstate.parents()
151 151
152 152 for n in p:
153 153 if n != hg.nullid:
154 154 ui.write("%d:%s\n" % (repo.changelog.rev(n), hg.hex(n)))
155 155
156 156 def status(ui, repo):
157 157 '''show changed files in the working directory
158 158
159 159 C = changed
160 160 A = added
161 161 R = removed
162 162 ? = not tracked'''
163 (c, a, d, u) = repo.diffdir(repo.root, repo.current)
163 (c, a, d, u) = repo.diffdir(repo.root)
164 164 (c, a, d, u) = map(lambda x: relfilter(repo, x), (c, a, d, u))
165 165
166 166 for f in c: print "C", f
167 167 for f in a: print "A", f
168 168 for f in d: print "R", f
169 169 for f in u: print "?", f
170 170
171 171 def undo(ui, repo):
172 172 repo.undo()
173 173
174 174 table = {
175 175 "init": (init, [], 'hg init'),
176 176 "branch|clone": (branch, [], 'hg branch [path]'),
177 177 "heads": (heads, [], 'hg heads'),
178 178 "help": (help, [], 'hg help [command]'),
179 179 "checkout|co": (checkout, [], 'hg checkout [changeset]'),
180 180 "ann|annotate": (annotate,
181 181 [('r', 'revision', '', 'revision'),
182 182 ('u', 'user', None, 'show user'),
183 183 ('n', 'number', None, 'show revision number'),
184 184 ('c', 'changeset', None, 'show changeset')],
185 185 'hg annotate [-u] [-c] [-n] [-r id] [files]'),
186 186 "parents": (parents, [], 'hg parents [node]'),
187 187 "status": (status, [], 'hg status'),
188 188 "undo": (undo, [], 'hg undo'),
189 189 }
190 190
191 191 norepo = "init branch help"
192 192
193 193 def find(cmd):
194 194 i = None
195 195 for e in table.keys():
196 196 if re.match(e + "$", cmd):
197 197 return table[e]
198 198
199 199 raise UnknownCommand(cmd)
200 200
201 201 class SignalInterrupt(Exception): pass
202 202
203 203 def catchterm(*args):
204 204 raise SignalInterrupt
205 205
206 206 def dispatch(args):
207 207 options = {}
208 208 opts = [('v', 'verbose', None, 'verbose'),
209 209 ('d', 'debug', None, 'debug'),
210 210 ('q', 'quiet', None, 'quiet'),
211 211 ('y', 'noninteractive', None, 'run non-interactively'),
212 212 ]
213 213
214 214 args = fancyopts.fancyopts(args, opts, options,
215 215 'hg [options] <command> [options] [files]')
216 216
217 217 if not args:
218 218 cmd = "help"
219 219 else:
220 220 cmd, args = args[0], args[1:]
221 221
222 222 u = ui.ui(options["verbose"], options["debug"], options["quiet"],
223 223 not options["noninteractive"])
224 224
225 225 # deal with unfound commands later
226 226 i = find(cmd)
227 227
228 228 signal.signal(signal.SIGTERM, catchterm)
229 229
230 230 cmdoptions = {}
231 231 args = fancyopts.fancyopts(args, i[1], cmdoptions, i[2])
232 232
233 233 if cmd not in norepo.split():
234 234 repo = hg.repository(ui = u)
235 235 d = lambda: i[0](u, repo, *args, **cmdoptions)
236 236 else:
237 237 d = lambda: i[0](u, *args, **cmdoptions)
238 238
239 239 try:
240 240 d()
241 241 except SignalInterrupt:
242 242 u.warn("killed!\n")
243 243 except KeyboardInterrupt:
244 244 u.warn("interrupted!\n")
245 245 except TypeError, inst:
246 246 # was this an argument error?
247 247 tb = traceback.extract_tb(sys.exc_info()[2])
248 248 if len(tb) > 2: # no
249 249 raise
250 250 u.warn("%s: invalid arguments\n" % i[0].__name__)
251 251 u.warn("syntax: %s\n" % i[2])
252 252 sys.exit(-1)
@@ -1,1138 +1,1139 b''
1 1 # hg.py - repository classes for mercurial
2 2 #
3 3 # Copyright 2005 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms
6 6 # of the GNU General Public License, incorporated herein by reference.
7 7
8 8 import sys, struct, sha, socket, os, time, re, urllib2, tempfile
9 9 import urllib
10 10 from mercurial import byterange, lock
11 11 from mercurial.transaction import *
12 12 from mercurial.revlog import *
13 13 from difflib import SequenceMatcher
14 14
15 15 class filelog(revlog):
16 16 def __init__(self, opener, path):
17 17 revlog.__init__(self, opener,
18 18 os.path.join("data", path + ".i"),
19 19 os.path.join("data", path + ".d"))
20 20
21 21 def read(self, node):
22 22 return self.revision(node)
23 23 def add(self, text, transaction, link, p1=None, p2=None):
24 24 return self.addrevision(text, transaction, link, p1, p2)
25 25
26 26 def annotate(self, node):
27 27
28 28 def decorate(text, rev):
29 29 return [(rev, l) for l in text.splitlines(1)]
30 30
31 31 def strip(annotation):
32 32 return [e[1] for e in annotation]
33 33
34 34 def pair(parent, child):
35 35 new = []
36 36 sm = SequenceMatcher(None, strip(parent), strip(child))
37 37 for o, m, n, s, t in sm.get_opcodes():
38 38 if o == 'equal':
39 39 new += parent[m:n]
40 40 else:
41 41 new += child[s:t]
42 42 return new
43 43
44 44 # find all ancestors
45 45 needed = {node:1}
46 46 visit = [node]
47 47 while visit:
48 48 n = visit.pop(0)
49 49 for p in self.parents(n):
50 50 if p not in needed:
51 51 needed[p] = 1
52 52 visit.append(p)
53 53 else:
54 54 # count how many times we'll use this
55 55 needed[p] += 1
56 56
57 57 # sort by revision which is a topological order
58 58 visit = needed.keys()
59 59 visit = [ (self.rev(n), n) for n in visit ]
60 60 visit.sort()
61 61 visit = [ p[1] for p in visit ]
62 62 hist = {}
63 63
64 64 for n in visit:
65 65 curr = decorate(self.read(n), self.linkrev(n))
66 66 for p in self.parents(n):
67 67 if p != nullid:
68 68 curr = pair(hist[p], curr)
69 69 # trim the history of unneeded revs
70 70 needed[p] -= 1
71 71 if not needed[p]:
72 72 del hist[p]
73 73 hist[n] = curr
74 74
75 75 return hist[n]
76 76
77 77 class manifest(revlog):
78 78 def __init__(self, opener):
79 79 self.mapcache = None
80 80 self.listcache = None
81 81 self.addlist = None
82 82 revlog.__init__(self, opener, "00manifest.i", "00manifest.d")
83 83
84 84 def read(self, node):
85 85 if self.mapcache and self.mapcache[0] == node:
86 86 return self.mapcache[1].copy()
87 87 text = self.revision(node)
88 88 map = {}
89 89 self.listcache = (text, text.splitlines(1))
90 90 for l in self.listcache[1]:
91 91 (f, n) = l.split('\0')
92 92 map[f] = bin(n[:40])
93 93 self.mapcache = (node, map)
94 94 return map
95 95
96 96 def diff(self, a, b):
97 97 # this is sneaky, as we're not actually using a and b
98 98 if self.listcache and self.addlist and self.listcache[0] == a:
99 99 d = mdiff.diff(self.listcache[1], self.addlist, 1)
100 100 if mdiff.patch(a, d) != b:
101 101 sys.stderr.write("*** sortdiff failed, falling back ***\n")
102 102 return mdiff.textdiff(a, b)
103 103 return d
104 104 else:
105 105 return mdiff.textdiff(a, b)
106 106
107 107 def add(self, map, transaction, link, p1=None, p2=None):
108 108 files = map.keys()
109 109 files.sort()
110 110
111 111 self.addlist = ["%s\000%s\n" % (f, hex(map[f])) for f in files]
112 112 text = "".join(self.addlist)
113 113
114 114 n = self.addrevision(text, transaction, link, p1, p2)
115 115 self.mapcache = (n, map)
116 116 self.listcache = (text, self.addlist)
117 117 self.addlist = None
118 118
119 119 return n
120 120
121 121 class changelog(revlog):
122 122 def __init__(self, opener):
123 123 revlog.__init__(self, opener, "00changelog.i", "00changelog.d")
124 124
125 125 def extract(self, text):
126 126 if not text:
127 127 return (nullid, "", "0", [], "")
128 128 last = text.index("\n\n")
129 129 desc = text[last + 2:]
130 130 l = text[:last].splitlines()
131 131 manifest = bin(l[0])
132 132 user = l[1]
133 133 date = l[2]
134 134 files = l[3:]
135 135 return (manifest, user, date, files, desc)
136 136
137 137 def read(self, node):
138 138 return self.extract(self.revision(node))
139 139
140 140 def add(self, manifest, list, desc, transaction, p1=None, p2=None,
141 141 user=None, date=None):
142 142 user = (user or
143 143 os.environ.get("HGUSER") or
144 144 os.environ.get("EMAIL") or
145 145 os.environ.get("LOGNAME", "unknown") + '@' + socket.getfqdn())
146 146 date = date or "%d %d" % (time.time(), time.timezone)
147 147 list.sort()
148 148 l = [hex(manifest), user, date] + list + ["", desc]
149 149 text = "\n".join(l)
150 150 return self.addrevision(text, transaction, self.count(), p1, p2)
151 151
152 152 class dirstate:
153 153 def __init__(self, opener, ui):
154 154 self.opener = opener
155 155 self.dirty = 0
156 156 self.ui = ui
157 157 self.map = None
158 158 self.pl = None
159 159
160 160 def __del__(self):
161 161 if self.dirty:
162 162 self.write()
163 163
164 164 def __getitem__(self, key):
165 165 try:
166 166 return self.map[key]
167 167 except TypeError:
168 168 self.read()
169 169 return self[key]
170 170
171 171 def __contains__(self, key):
172 172 if not self.map: self.read()
173 173 return key in self.map
174 174
175 175 def parents(self):
176 176 if not self.pl:
177 177 self.read()
178 178 return self.pl
179 179
180 180 def setparents(self, p1, p2 = nullid):
181 181 self.dirty = 1
182 182 self.pl = p1, p2
183 183
184 184 def state(self, key):
185 185 try:
186 186 return self[key][0]
187 187 except KeyError:
188 188 return "?"
189 189
190 190 def read(self):
191 191 if self.map is not None: return self.map
192 192
193 193 self.map = {}
194 194 self.pl = [nullid, nullid]
195 195 try:
196 196 st = self.opener("dirstate").read()
197 197 except: return
198 198
199 199 self.pl = [st[:20], st[20: 40]]
200 200
201 201 pos = 40
202 202 while pos < len(st):
203 203 e = struct.unpack(">cllll", st[pos:pos+17])
204 204 l = e[4]
205 205 pos += 17
206 206 f = st[pos:pos + l]
207 207 self.map[f] = e[:4]
208 208 pos += l
209 209
210 210 def update(self, files, state):
211 211 ''' current states:
212 212 n normal
213 213 i invalid
214 214 r marked for removal
215 215 a marked for addition'''
216 216
217 217 if not files: return
218 218 self.read()
219 219 self.dirty = 1
220 220 for f in files:
221 221 if state == "r":
222 222 self.map[f] = ('r', 0, 0, 0)
223 223 else:
224 224 try:
225 225 s = os.stat(f)
226 226 self.map[f] = (state, s.st_mode, s.st_size, s.st_mtime)
227 227 except OSError:
228 228 if state != "i": raise
229 229 self.map[f] = ('r', 0, 0, 0)
230 230
231 231 def forget(self, files):
232 232 if not files: return
233 233 self.read()
234 234 self.dirty = 1
235 235 for f in files:
236 236 try:
237 237 del self.map[f]
238 238 except KeyError:
239 239 self.ui.warn("not in dirstate: %s!\n" % f)
240 240 pass
241 241
242 242 def clear(self):
243 243 self.map = {}
244 244 self.dirty = 1
245 245
246 246 def write(self):
247 247 st = self.opener("dirstate", "w")
248 248 st.write("".join(self.pl))
249 249 for f, e in self.map.items():
250 250 e = struct.pack(">cllll", e[0], e[1], e[2], e[3], len(f))
251 251 st.write(e + f)
252 252 self.dirty = 0
253 253
254 254 def copy(self):
255 255 self.read()
256 256 return self.map.copy()
257 257
258 258 # used to avoid circular references so destructors work
259 259 def opener(base):
260 260 p = base
261 261 def o(path, mode="r"):
262 262 if p[:7] == "http://":
263 263 f = os.path.join(p, urllib.quote(path))
264 264 return httprangereader(f)
265 265
266 266 f = os.path.join(p, path)
267 267
268 268 if mode != "r":
269 269 try:
270 270 s = os.stat(f)
271 271 except OSError:
272 272 d = os.path.dirname(f)
273 273 if not os.path.isdir(d):
274 274 os.makedirs(d)
275 275 else:
276 276 if s.st_nlink > 1:
277 277 file(f + ".tmp", "w").write(file(f).read())
278 278 os.rename(f+".tmp", f)
279 279
280 280 return file(f, mode)
281 281
282 282 return o
283 283
284 284 class localrepository:
285 285 def __init__(self, ui, path=None, create=0):
286 286 self.remote = 0
287 287 if path and path[:7] == "http://":
288 288 self.remote = 1
289 289 self.path = path
290 290 else:
291 291 if not path:
292 292 p = os.getcwd()
293 293 while not os.path.isdir(os.path.join(p, ".hg")):
294 294 p = os.path.dirname(p)
295 295 if p == "/": raise "No repo found"
296 296 path = p
297 297 self.path = os.path.join(path, ".hg")
298 298
299 299 self.root = path
300 300 self.ui = ui
301 301
302 302 if create:
303 303 os.mkdir(self.path)
304 304 os.mkdir(self.join("data"))
305 305
306 306 self.opener = opener(self.path)
307 307 self.manifest = manifest(self.opener)
308 308 self.changelog = changelog(self.opener)
309 309 self.ignorelist = None
310 310 self.tags = None
311 311
312 312 if not self.remote:
313 313 self.dirstate = dirstate(self.opener, ui)
314 314
315 315 def ignore(self, f):
316 316 if self.ignorelist is None:
317 317 self.ignorelist = []
318 318 try:
319 319 l = open(os.path.join(self.root, ".hgignore"))
320 320 for pat in l:
321 321 if pat != "\n":
322 322 self.ignorelist.append(re.compile(pat[:-1]))
323 323 except IOError: pass
324 324 for pat in self.ignorelist:
325 325 if pat.search(f): return True
326 326 return False
327 327
328 328 def lookup(self, key):
329 329 if self.tags is None:
330 330 self.tags = {}
331 331 try:
332 332 fl = self.file(".hgtags")
333 333 for l in fl.revision(fl.tip()).splitlines():
334 334 if l:
335 335 n, k = l.split(" ")
336 336 self.tags[k] = bin(n)
337 337 except KeyError: pass
338 338 try:
339 339 return self.tags[key]
340 340 except KeyError:
341 341 return self.changelog.lookup(key)
342 342
343 343 def join(self, f):
344 344 return os.path.join(self.path, f)
345 345
346 346 def file(self, f):
347 347 if f[0] == '/': f = f[1:]
348 348 return filelog(self.opener, f)
349 349
350 350 def transaction(self):
351 351 return transaction(self.opener, self.join("journal"),
352 352 self.join("undo"))
353 353
354 354 def recover(self):
355 355 lock = self.lock()
356 356 if os.path.exists(self.join("recover")):
357 357 self.ui.status("attempting to rollback interrupted transaction\n")
358 358 return rollback(self.opener, self.join("recover"))
359 359 else:
360 360 self.ui.warn("no interrupted transaction available\n")
361 361
362 362 def undo(self):
363 363 lock = self.lock()
364 364 if os.path.exists(self.join("undo")):
365 365 f = self.changelog.read(self.changelog.tip())[3]
366 366 self.ui.status("attempting to rollback last transaction\n")
367 367 rollback(self.opener, self.join("undo"))
368 368 self.manifest = manifest(self.opener)
369 369 self.changelog = changelog(self.opener)
370 370
371 371 self.ui.status("discarding dirstate\n")
372 372 node = self.changelog.tip()
373 373 f.sort()
374 374
375 375 self.dirstate.setparents(node)
376 376 self.dirstate.update(f, 'i')
377 377
378 378 else:
379 379 self.ui.warn("no undo information available\n")
380 380
381 381 def lock(self, wait = 1):
382 382 try:
383 383 return lock.lock(self.join("lock"), 0)
384 384 except lock.LockHeld, inst:
385 385 if wait:
386 386 self.ui.warn("waiting for lock held by %s\n" % inst.args[0])
387 387 return lock.lock(self.join("lock"), wait)
388 388 raise inst
389 389
390 390 def rawcommit(self, files, text, user, date, p1=None, p2=None):
391 391 p1 = p1 or self.dirstate.parents()[0] or nullid
392 392 p2 = p2 or self.dirstate.parents()[1] or nullid
393 393 pchange = self.changelog.read(p1)
394 394 pmmap = self.manifest.read(pchange[0])
395 395 tr = self.transaction()
396 396 mmap = {}
397 397 linkrev = self.changelog.count()
398 398 for f in files:
399 399 try:
400 400 t = file(f).read()
401 401 except IOError:
402 402 self.ui.warn("Read file %s error, skipped\n" % f)
403 403 continue
404 404 r = self.file(f)
405 405 # FIXME - need to find both parents properly
406 406 prev = pmmap.get(f, nullid)
407 407 mmap[f] = r.add(t, tr, linkrev, prev)
408 408
409 409 mnode = self.manifest.add(mmap, tr, linkrev, pchange[0])
410 410 n = self.changelog.add(mnode, files, text, tr, p1, p2, user ,date, )
411 411 tr.close()
412 412 self.dirstate.setparents(p1, p2)
413 413 self.dirstate.clear()
414 414 self.dirstate.update(mmap.keys(), "n")
415 415
416 416 def commit(self, files = None, text = ""):
417 417 commit = []
418 418 remove = []
419 419 if files:
420 420 for f in files:
421 421 s = self.dirstate.state(f)
422 422 if s in 'cai':
423 423 commit.append(f)
424 424 elif s == 'r':
425 425 remove.append(f)
426 426 else:
427 427 self.warn("%s not tracked!\n")
428 428 else:
429 (c, a, d, u) = self.diffdir(self.root, parent)
429 (c, a, d, u) = self.diffdir(self.root)
430 430 commit = c + a
431 431 remove = d
432 432
433 433 if not commit and not remove:
434 434 self.ui.status("nothing changed\n")
435 435 return
436 436
437 437 p1, p2 = self.dirstate.parents()
438 438 c1 = self.changelog.read(p1)
439 439 c2 = self.changelog.read(p2)
440 440 m1 = self.manifest.read(c1[0])
441 441 m2 = self.manifest.read(c2[0])
442 442 lock = self.lock()
443 443 tr = self.transaction()
444 444
445 445 # check in files
446 446 new = {}
447 447 linkrev = self.changelog.count()
448 448 commit.sort()
449 449 for f in commit:
450 450 self.ui.note(f + "\n")
451 451 try:
452 452 t = file(f).read()
453 453 except IOError:
454 454 self.warn("trouble committing %s!\n" % f)
455 455 raise
456 456
457 457 r = self.file(f)
458 458 fp1 = m1.get(f, nullid)
459 459 fp2 = m2.get(f, nullid)
460 460 new[f] = r.add(t, tr, linkrev, fp1, fp2)
461 461
462 462 # update manifest
463 463 m1.update(new)
464 464 for f in remove: del m1[f]
465 465 mn = self.manifest.add(m1, tr, linkrev, c1[0], c2[0])
466 466
467 467 # add changeset
468 468 new = new.keys()
469 469 new.sort()
470 470
471 471 edittext = text + "\n" + "HG: manifest hash %s\n" % hex(mn)
472 472 edittext += "".join(["HG: changed %s\n" % f for f in new])
473 473 edittext += "".join(["HG: removed %s\n" % f for f in remove])
474 474 edittext = self.ui.edit(edittext)
475 475
476 476 n = self.changelog.add(mn, new, edittext, tr, p1, p2)
477 477 tr.close()
478 478
479 479 self.dirstate.setparents(n)
480 480 self.dirstate.update(new, "n")
481 481 self.dirstate.forget(remove)
482 482
483 483 def checkout(self, node):
484 484 # checkout is really dumb at the moment
485 485 # it ought to basically merge
486 486 change = self.changelog.read(node)
487 487 l = self.manifest.read(change[0]).items()
488 488 l.sort()
489 489
490 490 for f,n in l:
491 491 if f[0] == "/": continue
492 492 self.ui.note(f, "\n")
493 493 t = self.file(f).revision(n)
494 494 try:
495 495 file(f, "w").write(t)
496 496 except IOError:
497 497 os.makedirs(os.path.dirname(f))
498 498 file(f, "w").write(t)
499 499
500 500 self.dirstate.setparents(node)
501 501 self.dirstate.clear()
502 502 self.dirstate.update([f for f,n in l], "n")
503 503
504 def diffdir(self, path, changeset):
504 def diffdir(self, path, changeset = None):
505 505 changed = []
506 506 added = []
507 507 unknown = []
508 508 mf = {}
509 509
510 510 if changeset:
511 511 change = self.changelog.read(changeset)
512 512 mf = self.manifest.read(change[0])
513
514 if changeset == self.current:
513 dc = dict.fromkeys(mf)
514 else:
515 changeset = self.dirstate.parents()[0]
516 change = self.changelog.read(changeset)
517 mf = self.manifest.read(change[0])
515 518 dc = self.dirstate.copy()
516 else:
517 dc = dict.fromkeys(mf)
518 519
519 520 def fcmp(fn):
520 521 t1 = file(os.path.join(self.root, fn)).read()
521 522 t2 = self.file(fn).revision(mf[fn])
522 523 return cmp(t1, t2)
523 524
524 525 for dir, subdirs, files in os.walk(self.root):
525 526 d = dir[len(self.root)+1:]
526 527 if ".hg" in subdirs: subdirs.remove(".hg")
527 528
528 529 for f in files:
529 530 fn = os.path.join(d, f)
530 531 try: s = os.stat(os.path.join(self.root, fn))
531 532 except: continue
532 533 if fn in dc:
533 534 c = dc[fn]
534 535 del dc[fn]
535 536 if not c:
536 537 if fcmp(fn):
537 538 changed.append(fn)
538 if c[0] == 'i':
539 elif c[0] == 'i':
539 540 if fn not in mf:
540 541 added.append(fn)
541 542 elif fcmp(fn):
542 543 changed.append(fn)
543 544 elif c[0] == 'a':
544 545 added.append(fn)
545 546 elif c[0] == 'r':
546 547 unknown.append(fn)
547 548 elif c[2] != s.st_size:
548 549 changed.append(fn)
549 550 elif c[1] != s.st_mode or c[3] != s.st_mtime:
550 551 if fcmp(fn):
551 552 changed.append(fn)
552 553 else:
553 554 if self.ignore(fn): continue
554 555 unknown.append(fn)
555 556
556 557 deleted = dc.keys()
557 558 deleted.sort()
558 559
559 560 return (changed, added, deleted, unknown)
560 561
561 562 def diffrevs(self, node1, node2):
562 563 changed, added = [], []
563 564
564 565 change = self.changelog.read(node1)
565 566 mf1 = self.manifest.read(change[0])
566 567 change = self.changelog.read(node2)
567 568 mf2 = self.manifest.read(change[0])
568 569
569 570 for fn in mf2:
570 571 if mf1.has_key(fn):
571 572 if mf1[fn] != mf2[fn]:
572 573 changed.append(fn)
573 574 del mf1[fn]
574 575 else:
575 576 added.append(fn)
576 577
577 578 deleted = mf1.keys()
578 579 deleted.sort()
579 580
580 581 return (changed, added, deleted)
581 582
582 583 def add(self, list):
583 584 for f in list:
584 585 p = os.path.join(self.root, f)
585 586 if not os.path.isfile(p):
586 587 self.ui.warn("%s does not exist!\n" % f)
587 588 elif self.dirstate.state(f) == 'n':
588 589 self.ui.warn("%s already tracked!\n" % f)
589 590 else:
590 591 self.dirstate.update([f], "a")
591 592
592 593 def forget(self, list):
593 594 for f in list:
594 595 if self.dirstate.state(f) not in 'ai':
595 596 self.ui.warn("%s not added!\n" % f)
596 597 else:
597 598 self.dirstate.forget([f])
598 599
599 600 def remove(self, list):
600 601 for f in list:
601 602 p = os.path.join(self.root, f)
602 603 if os.path.isfile(p):
603 604 self.ui.warn("%s still exists!\n" % f)
604 605 elif f not in self.dirstate:
605 606 self.ui.warn("%s not tracked!\n" % f)
606 607 else:
607 608 self.dirstate.update([f], "r")
608 609
609 610 def heads(self):
610 611 return self.changelog.heads()
611 612
612 613 def branches(self, nodes):
613 614 if not nodes: nodes = [self.changelog.tip()]
614 615 b = []
615 616 for n in nodes:
616 617 t = n
617 618 while n:
618 619 p = self.changelog.parents(n)
619 620 if p[1] != nullid or p[0] == nullid:
620 621 b.append((t, n, p[0], p[1]))
621 622 break
622 623 n = p[0]
623 624 return b
624 625
625 626 def between(self, pairs):
626 627 r = []
627 628
628 629 for top, bottom in pairs:
629 630 n, l, i = top, [], 0
630 631 f = 1
631 632
632 633 while n != bottom:
633 634 p = self.changelog.parents(n)[0]
634 635 if i == f:
635 636 l.append(n)
636 637 f = f * 2
637 638 n = p
638 639 i += 1
639 640
640 641 r.append(l)
641 642
642 643 return r
643 644
644 645 def newer(self, nodes):
645 646 m = {}
646 647 nl = []
647 648 pm = {}
648 649 cl = self.changelog
649 650 t = l = cl.count()
650 651
651 652 # find the lowest numbered node
652 653 for n in nodes:
653 654 l = min(l, cl.rev(n))
654 655 m[n] = 1
655 656
656 657 for i in xrange(l, t):
657 658 n = cl.node(i)
658 659 if n in m: # explicitly listed
659 660 pm[n] = 1
660 661 nl.append(n)
661 662 continue
662 663 for p in cl.parents(n):
663 664 if p in pm: # parent listed
664 665 pm[n] = 1
665 666 nl.append(n)
666 667 break
667 668
668 669 return nl
669 670
670 671 def getchangegroup(self, remote):
671 672 m = self.changelog.nodemap
672 673 search = []
673 674 fetch = []
674 675 seen = {}
675 676 seenbranch = {}
676 677
677 678 # if we have an empty repo, fetch everything
678 679 if self.changelog.tip() == nullid:
679 680 self.ui.status("requesting all changes\n")
680 681 return remote.changegroup([nullid])
681 682
682 683 # otherwise, assume we're closer to the tip than the root
683 684 self.ui.status("searching for changes\n")
684 685 heads = remote.heads()
685 686 unknown = []
686 687 for h in heads:
687 688 if h not in m:
688 689 unknown.append(h)
689 690
690 691 if not unknown:
691 692 self.ui.status("nothing to do!\n")
692 693 return None
693 694
694 695 unknown = remote.branches(unknown)
695 696 while unknown:
696 697 n = unknown.pop(0)
697 698 seen[n[0]] = 1
698 699
699 700 self.ui.debug("examining %s:%s\n" % (short(n[0]), short(n[1])))
700 701 if n == nullid: break
701 702 if n in seenbranch:
702 703 self.ui.debug("branch already found\n")
703 704 continue
704 705 if n[1] and n[1] in m: # do we know the base?
705 706 self.ui.debug("found incomplete branch %s:%s\n"
706 707 % (short(n[0]), short(n[1])))
707 708 search.append(n) # schedule branch range for scanning
708 709 seenbranch[n] = 1
709 710 else:
710 711 if n[2] in m and n[3] in m:
711 712 if n[1] not in fetch:
712 713 self.ui.debug("found new changeset %s\n" %
713 714 short(n[1]))
714 715 fetch.append(n[1]) # earliest unknown
715 716 continue
716 717
717 718 r = []
718 719 for a in n[2:4]:
719 720 if a not in seen: r.append(a)
720 721
721 722 if r:
722 723 self.ui.debug("requesting %s\n" %
723 724 " ".join(map(short, r)))
724 725 for b in remote.branches(r):
725 726 self.ui.debug("received %s:%s\n" %
726 727 (short(b[0]), short(b[1])))
727 728 if b[0] not in m and b[0] not in seen:
728 729 unknown.append(b)
729 730
730 731 while search:
731 732 n = search.pop(0)
732 733 l = remote.between([(n[0], n[1])])[0]
733 734 p = n[0]
734 735 f = 1
735 736 for i in l + [n[1]]:
736 737 if i in m:
737 738 if f <= 2:
738 739 self.ui.debug("found new branch changeset %s\n" %
739 740 short(p))
740 741 fetch.append(p)
741 742 else:
742 743 self.ui.debug("narrowed branch search to %s:%s\n"
743 744 % (short(p), short(i)))
744 745 search.append((p, i))
745 746 break
746 747 p, f = i, f * 2
747 748
748 749 for f in fetch:
749 750 if f in m:
750 751 raise "already have", short(f[:4])
751 752
752 753 self.ui.note("adding new changesets starting at " +
753 754 " ".join([short(f) for f in fetch]) + "\n")
754 755
755 756 return remote.changegroup(fetch)
756 757
757 758 def changegroup(self, basenodes):
758 759 nodes = self.newer(basenodes)
759 760
760 761 # construct the link map
761 762 linkmap = {}
762 763 for n in nodes:
763 764 linkmap[self.changelog.rev(n)] = n
764 765
765 766 # construct a list of all changed files
766 767 changed = {}
767 768 for n in nodes:
768 769 c = self.changelog.read(n)
769 770 for f in c[3]:
770 771 changed[f] = 1
771 772 changed = changed.keys()
772 773 changed.sort()
773 774
774 775 # the changegroup is changesets + manifests + all file revs
775 776 revs = [ self.changelog.rev(n) for n in nodes ]
776 777
777 778 for y in self.changelog.group(linkmap): yield y
778 779 for y in self.manifest.group(linkmap): yield y
779 780 for f in changed:
780 781 yield struct.pack(">l", len(f) + 4) + f
781 782 g = self.file(f).group(linkmap)
782 783 for y in g:
783 784 yield y
784 785
785 786 def addchangegroup(self, generator):
786 787
787 788 class genread:
788 789 def __init__(self, generator):
789 790 self.g = generator
790 791 self.buf = ""
791 792 def read(self, l):
792 793 while l > len(self.buf):
793 794 try:
794 795 self.buf += self.g.next()
795 796 except StopIteration:
796 797 break
797 798 d, self.buf = self.buf[:l], self.buf[l:]
798 799 return d
799 800
800 801 def getchunk():
801 802 d = source.read(4)
802 803 if not d: return ""
803 804 l = struct.unpack(">l", d)[0]
804 805 if l <= 4: return ""
805 806 return source.read(l - 4)
806 807
807 808 def getgroup():
808 809 while 1:
809 810 c = getchunk()
810 811 if not c: break
811 812 yield c
812 813
813 814 def csmap(x):
814 815 self.ui.debug("add changeset %s\n" % short(x))
815 816 return self.changelog.count()
816 817
817 818 def revmap(x):
818 819 return self.changelog.rev(x)
819 820
820 821 if not generator: return
821 822 changesets = files = revisions = 0
822 823
823 824 source = genread(generator)
824 825 lock = self.lock()
825 826 tr = self.transaction()
826 827
827 828 # pull off the changeset group
828 829 self.ui.status("adding changesets\n")
829 830 co = self.changelog.tip()
830 831 cn = self.changelog.addgroup(getgroup(), csmap, tr, 1) # unique
831 832 changesets = self.changelog.rev(cn) - self.changelog.rev(co)
832 833
833 834 # pull off the manifest group
834 835 self.ui.status("adding manifests\n")
835 836 mm = self.manifest.tip()
836 837 mo = self.manifest.addgroup(getgroup(), revmap, tr)
837 838
838 839 # process the files
839 840 self.ui.status("adding file revisions\n")
840 841 while 1:
841 842 f = getchunk()
842 843 if not f: break
843 844 self.ui.debug("adding %s revisions\n" % f)
844 845 fl = self.file(f)
845 846 o = fl.tip()
846 847 n = fl.addgroup(getgroup(), revmap, tr)
847 848 revisions += fl.rev(n) - fl.rev(o)
848 849 files += 1
849 850
850 851 self.ui.status(("modified %d files, added %d changesets" +
851 852 " and %d new revisions\n")
852 853 % (files, changesets, revisions))
853 854
854 855 tr.close()
855 856 return
856 857
857 858 def merge(self, generator):
858 859 changesets = files = revisions = 0
859 860
860 861 self.lock()
861 862 class genread:
862 863 def __init__(self, generator):
863 864 self.g = generator
864 865 self.buf = ""
865 866 def read(self, l):
866 867 while l > len(self.buf):
867 868 try:
868 869 self.buf += self.g.next()
869 870 except StopIteration:
870 871 break
871 872 d, self.buf = self.buf[:l], self.buf[l:]
872 873 return d
873 874
874 875 if not generator: return
875 876 source = genread(generator)
876 877
877 878 def getchunk():
878 879 d = source.read(4)
879 880 if not d: return ""
880 881 l = struct.unpack(">l", d)[0]
881 882 if l <= 4: return ""
882 883 return source.read(l - 4)
883 884
884 885 def getgroup():
885 886 while 1:
886 887 c = getchunk()
887 888 if not c: break
888 889 yield c
889 890
890 891 tr = self.transaction()
891 892 simple = True
892 893 need = {}
893 894
894 895 self.ui.status("adding changesets\n")
895 896 # pull off the changeset group
896 897 def report(x):
897 898 self.ui.debug("add changeset %s\n" % short(x))
898 899 return self.changelog.count()
899 900
900 901 co = self.changelog.tip()
901 902 cn = self.changelog.addgroup(getgroup(), report, tr)
902 903
903 904 changesets = self.changelog.rev(cn) - self.changelog.rev(co)
904 905
905 906 self.ui.status("adding manifests\n")
906 907 # pull off the manifest group
907 908 mm = self.manifest.tip()
908 909 mo = self.manifest.addgroup(getgroup(),
909 910 lambda x: self.changelog.rev(x), tr)
910 911
911 912 # do we need a resolve?
912 913 if self.changelog.ancestor(co, cn) != co:
913 914 simple = False
914 915 resolverev = self.changelog.count()
915 916
916 917 # resolve the manifest to determine which files
917 918 # we care about merging
918 919 self.ui.status("resolving manifests\n")
919 920 ma = self.manifest.ancestor(mm, mo)
920 921 omap = self.manifest.read(mo) # other
921 922 amap = self.manifest.read(ma) # ancestor
922 923 mmap = self.manifest.read(mm) # mine
923 924 nmap = {}
924 925
925 926 self.ui.debug(" ancestor %s local %s remote %s\n" %
926 927 (short(ma), short(mm), short(mo)))
927 928
928 929 for f, mid in mmap.iteritems():
929 930 if f in omap:
930 931 if mid != omap[f]:
931 932 self.ui.debug(" %s versions differ, do resolve\n" % f)
932 933 need[f] = mid # use merged version or local version
933 934 else:
934 935 nmap[f] = mid # keep ours
935 936 del omap[f]
936 937 elif f in amap:
937 938 if mid != amap[f]:
938 939 r = self.ui.prompt(
939 940 (" local changed %s which remote deleted\n" % f) +
940 941 "(k)eep or (d)elete?", "[kd]", "k")
941 942 if r == "k": nmap[f] = mid
942 943 else:
943 944 self.ui.debug("other deleted %s\n" % f)
944 945 pass # other deleted it
945 946 else:
946 947 self.ui.debug("local created %s\n" %f)
947 948 nmap[f] = mid # we created it
948 949
949 950 del mmap
950 951
951 952 for f, oid in omap.iteritems():
952 953 if f in amap:
953 954 if oid != amap[f]:
954 955 r = self.ui.prompt(
955 956 ("remote changed %s which local deleted\n" % f) +
956 957 "(k)eep or (d)elete?", "[kd]", "k")
957 958 if r == "k": nmap[f] = oid
958 959 else:
959 960 pass # probably safe
960 961 else:
961 962 self.ui.debug("remote created %s, do resolve\n" % f)
962 963 need[f] = oid
963 964
964 965 del omap
965 966 del amap
966 967
967 968 new = need.keys()
968 969 new.sort()
969 970
970 971 # process the files
971 972 self.ui.status("adding files\n")
972 973 while 1:
973 974 f = getchunk()
974 975 if not f: break
975 976 self.ui.debug("adding %s revisions\n" % f)
976 977 fl = self.file(f)
977 978 o = fl.tip()
978 979 n = fl.addgroup(getgroup(), lambda x: self.changelog.rev(x), tr)
979 980 revisions += fl.rev(n) - fl.rev(o)
980 981 files += 1
981 982 if f in need:
982 983 del need[f]
983 984 # manifest resolve determined we need to merge the tips
984 985 nmap[f] = self.merge3(fl, f, o, n, tr, resolverev)
985 986
986 987 if need:
987 988 # we need to do trivial merges on local files
988 989 for f in new:
989 990 if f not in need: continue
990 991 fl = self.file(f)
991 992 nmap[f] = self.merge3(fl, f, need[f], fl.tip(), tr, resolverev)
992 993 revisions += 1
993 994
994 995 # For simple merges, we don't need to resolve manifests or changesets
995 996 if simple:
996 997 self.ui.debug("simple merge, skipping resolve\n")
997 998 self.ui.status(("modified %d files, added %d changesets" +
998 999 " and %d new revisions\n")
999 1000 % (files, changesets, revisions))
1000 1001 tr.close()
1001 1002 return
1002 1003
1003 1004 node = self.manifest.add(nmap, tr, resolverev, mm, mo)
1004 1005 revisions += 1
1005 1006
1006 1007 # Now all files and manifests are merged, we add the changed files
1007 1008 # and manifest id to the changelog
1008 1009 self.ui.status("committing merge changeset\n")
1009 1010 if co == cn: cn = -1
1010 1011
1011 1012 edittext = "\nHG: merge resolve\n" + \
1012 1013 "HG: manifest hash %s\n" % hex(node) + \
1013 1014 "".join(["HG: changed %s\n" % f for f in new])
1014 1015 edittext = self.ui.edit(edittext)
1015 1016 n = self.changelog.add(node, new, edittext, tr, co, cn)
1016 1017 revisions += 1
1017 1018
1018 1019 self.ui.status("added %d changesets, %d files, and %d new revisions\n"
1019 1020 % (changesets, files, revisions))
1020 1021
1021 1022 tr.close()
1022 1023
1023 1024 def merge3(self, fl, fn, my, other, transaction, link):
1024 1025 """perform a 3-way merge and append the result"""
1025 1026
1026 1027 def temp(prefix, node):
1027 1028 pre = "%s~%s." % (os.path.basename(fn), prefix)
1028 1029 (fd, name) = tempfile.mkstemp("", pre)
1029 1030 f = os.fdopen(fd, "w")
1030 1031 f.write(fl.revision(node))
1031 1032 f.close()
1032 1033 return name
1033 1034
1034 1035 base = fl.ancestor(my, other)
1035 1036 self.ui.note("resolving %s\n" % fn)
1036 1037 self.ui.debug("local %s remote %s ancestor %s\n" %
1037 1038 (short(my), short(other), short(base)))
1038 1039
1039 1040 if my == base:
1040 1041 text = fl.revision(other)
1041 1042 else:
1042 1043 a = temp("local", my)
1043 1044 b = temp("remote", other)
1044 1045 c = temp("parent", base)
1045 1046
1046 1047 cmd = os.environ["HGMERGE"]
1047 1048 self.ui.debug("invoking merge with %s\n" % cmd)
1048 1049 r = os.system("%s %s %s %s %s" % (cmd, a, b, c, fn))
1049 1050 if r:
1050 1051 raise "Merge failed!"
1051 1052
1052 1053 text = open(a).read()
1053 1054 os.unlink(a)
1054 1055 os.unlink(b)
1055 1056 os.unlink(c)
1056 1057
1057 1058 return fl.add(text, transaction, link, my, other)
1058 1059
1059 1060 class remoterepository:
1060 1061 def __init__(self, ui, path):
1061 1062 self.url = path
1062 1063 self.ui = ui
1063 1064
1064 1065 def do_cmd(self, cmd, **args):
1065 1066 self.ui.debug("sending %s command\n" % cmd)
1066 1067 q = {"cmd": cmd}
1067 1068 q.update(args)
1068 1069 qs = urllib.urlencode(q)
1069 1070 cu = "%s?%s" % (self.url, qs)
1070 1071 return urllib.urlopen(cu)
1071 1072
1072 1073 def heads(self):
1073 1074 d = self.do_cmd("heads").read()
1074 1075 try:
1075 1076 return map(bin, d[:-1].split(" "))
1076 1077 except:
1077 1078 self.ui.warn("unexpected response:\n" + d[:400] + "\n...\n")
1078 1079 raise
1079 1080
1080 1081 def branches(self, nodes):
1081 1082 n = " ".join(map(hex, nodes))
1082 1083 d = self.do_cmd("branches", nodes=n).read()
1083 1084 try:
1084 1085 br = [ tuple(map(bin, b.split(" "))) for b in d.splitlines() ]
1085 1086 return br
1086 1087 except:
1087 1088 self.ui.warn("unexpected response:\n" + d[:400] + "\n...\n")
1088 1089 raise
1089 1090
1090 1091 def between(self, pairs):
1091 1092 n = "\n".join(["-".join(map(hex, p)) for p in pairs])
1092 1093 d = self.do_cmd("between", pairs=n).read()
1093 1094 try:
1094 1095 p = [ l and map(bin, l.split(" ")) or [] for l in d.splitlines() ]
1095 1096 return p
1096 1097 except:
1097 1098 self.ui.warn("unexpected response:\n" + d[:400] + "\n...\n")
1098 1099 raise
1099 1100
1100 1101 def changegroup(self, nodes):
1101 1102 n = " ".join(map(hex, nodes))
1102 1103 zd = zlib.decompressobj()
1103 1104 f = self.do_cmd("changegroup", roots=n)
1104 1105 bytes = 0
1105 1106 while 1:
1106 1107 d = f.read(4096)
1107 1108 bytes += len(d)
1108 1109 if not d:
1109 1110 yield zd.flush()
1110 1111 break
1111 1112 yield zd.decompress(d)
1112 1113 self.ui.note("%d bytes of data transfered\n" % bytes)
1113 1114
1114 1115 def repository(ui, path=None, create=0):
1115 1116 if path and path[:7] == "http://":
1116 1117 return remoterepository(ui, path)
1117 1118 if path and path[:5] == "hg://":
1118 1119 return remoterepository(ui, path.replace("hg://", "http://"))
1119 1120 if path and path[:11] == "old-http://":
1120 1121 return localrepository(ui, path.replace("old-http://", "http://"))
1121 1122 else:
1122 1123 return localrepository(ui, path, create)
1123 1124
1124 1125 class httprangereader:
1125 1126 def __init__(self, url):
1126 1127 self.url = url
1127 1128 self.pos = 0
1128 1129 def seek(self, pos):
1129 1130 self.pos = pos
1130 1131 def read(self, bytes=None):
1131 1132 opener = urllib2.build_opener(byterange.HTTPRangeHandler())
1132 1133 urllib2.install_opener(opener)
1133 1134 req = urllib2.Request(self.url)
1134 1135 end = ''
1135 1136 if bytes: end = self.pos + bytes
1136 1137 req.add_header('Range', 'bytes=%d-%s' % (self.pos, end))
1137 1138 f = urllib2.urlopen(req)
1138 1139 return f.read()
General Comments 0
You need to be logged in to leave comments. Login now