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