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