##// END OF EJS Templates
[PATCH] file type fixes for the other 'OS'...
mpm@selenic.com -
r417:f2d1f5fd default
parent child Browse files
Show More
@@ -1,823 +1,823 b''
1 1 # commands.py - command processing 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 os, re, sys, signal
9 9 import fancyopts, ui, hg
10 10 from demandload import *
11 11 demandload(globals(), "mdiff time hgweb traceback random signal errno")
12 12
13 13 class UnknownCommand(Exception): pass
14 14
15 15 def filterfiles(filters, files):
16 16 l = [ x for x in files if x in filters ]
17 17
18 18 for t in filters:
19 19 if t and t[-1] != os.sep: t += os.sep
20 20 l += [ x for x in files if x.startswith(t) ]
21 21 return l
22 22
23 23 def relfilter(repo, files):
24 24 if os.getcwd() != repo.root:
25 25 p = os.getcwd()[len(repo.root) + 1: ]
26 26 return filterfiles([p], files)
27 27 return files
28 28
29 29 def relpath(repo, args):
30 30 if os.getcwd() != repo.root:
31 31 p = os.getcwd()[len(repo.root) + 1: ]
32 32 return [ os.path.normpath(os.path.join(p, x)) for x in args ]
33 33 return args
34 34
35 35 def dodiff(ui, repo, path, files = None, node1 = None, node2 = None):
36 36 def date(c):
37 37 return time.asctime(time.gmtime(float(c[2].split(' ')[0])))
38 38
39 39 if node2:
40 40 change = repo.changelog.read(node2)
41 41 mmap2 = repo.manifest.read(change[0])
42 42 (c, a, d) = repo.diffrevs(node1, node2)
43 43 def read(f): return repo.file(f).read(mmap2[f])
44 44 date2 = date(change)
45 45 else:
46 46 date2 = time.asctime()
47 47 (c, a, d, u) = repo.diffdir(path, node1)
48 48 if not node1:
49 49 node1 = repo.dirstate.parents()[0]
50 def read(f): return file(os.path.join(repo.root, f)).read()
50 def read(f): return repo.wfile(f).read()
51 51
52 52 if ui.quiet:
53 53 r = None
54 54 else:
55 55 hexfunc = ui.verbose and hg.hex or hg.short
56 56 r = [hexfunc(node) for node in [node1, node2] if node]
57 57
58 58 change = repo.changelog.read(node1)
59 59 mmap = repo.manifest.read(change[0])
60 60 date1 = date(change)
61 61
62 62 if files:
63 63 c, a, d = map(lambda x: filterfiles(files, x), (c, a, d))
64 64
65 65 for f in c:
66 66 to = None
67 67 if f in mmap:
68 68 to = repo.file(f).read(mmap[f])
69 69 tn = read(f)
70 70 sys.stdout.write(mdiff.unidiff(to, date1, tn, date2, f, r))
71 71 for f in a:
72 72 to = None
73 73 tn = read(f)
74 74 sys.stdout.write(mdiff.unidiff(to, date1, tn, date2, f, r))
75 75 for f in d:
76 76 to = repo.file(f).read(mmap[f])
77 77 tn = None
78 78 sys.stdout.write(mdiff.unidiff(to, date1, tn, date2, f, r))
79 79
80 80 def show_changeset(ui, repo, rev=0, changenode=None, filelog=None):
81 81 """show a single changeset or file revision"""
82 82 changelog = repo.changelog
83 83 if filelog:
84 84 log = filelog
85 85 filerev = rev
86 86 node = filenode = filelog.node(filerev)
87 87 changerev = filelog.linkrev(filenode)
88 88 changenode = changenode or changelog.node(changerev)
89 89 else:
90 90 log = changelog
91 91 changerev = rev
92 92 if changenode is None:
93 93 changenode = changelog.node(changerev)
94 94 elif not changerev:
95 95 rev = changerev = changelog.rev(changenode)
96 96 node = changenode
97 97
98 98 if ui.quiet:
99 99 ui.write("%d:%s\n" % (rev, hg.hex(node)))
100 100 return
101 101
102 102 changes = changelog.read(changenode)
103 103
104 104 parents = [(log.rev(parent), hg.hex(parent))
105 105 for parent in log.parents(node)
106 106 if ui.debugflag or parent != hg.nullid]
107 107 if not ui.debugflag and len(parents) == 1 and parents[0][0] == rev-1:
108 108 parents = []
109 109
110 110 if filelog:
111 111 ui.write("revision: %d:%s\n" % (filerev, hg.hex(filenode)))
112 112 for parent in parents:
113 113 ui.write("parent: %d:%s\n" % parent)
114 114 ui.status("changeset: %d:%s\n" % (changerev, hg.hex(changenode)))
115 115 else:
116 116 ui.write("changeset: %d:%s\n" % (changerev, hg.hex(changenode)))
117 117 for tag in repo.nodetags(changenode):
118 118 ui.status("tag: %s\n" % tag)
119 119 for parent in parents:
120 120 ui.write("parent: %d:%s\n" % parent)
121 121 ui.note("manifest: %d:%s\n" % (repo.manifest.rev(changes[0]),
122 122 hg.hex(changes[0])))
123 123 ui.status("user: %s\n" % changes[1])
124 124 ui.status("date: %s\n" % time.asctime(
125 125 time.localtime(float(changes[2].split(' ')[0]))))
126 126 ui.note("files: %s\n" % " ".join(changes[3]))
127 127 description = changes[4].strip()
128 128 if description:
129 129 if ui.verbose:
130 130 ui.status("description:\n")
131 131 ui.status(description)
132 132 ui.status("\n")
133 133 else:
134 134 ui.status("summary: %s\n" % description.splitlines()[0])
135 135 ui.status("\n")
136 136
137 137 def help(ui, cmd=None):
138 138 '''show help for a given command or all commands'''
139 139 if cmd:
140 140 try:
141 141 i = find(cmd)
142 142 ui.write("%s\n\n" % i[2])
143 143
144 144 if i[1]:
145 145 for s, l, d, c in i[1]:
146 146 opt=' '
147 147 if s: opt = opt + '-' + s + ' '
148 148 if l: opt = opt + '--' + l + ' '
149 149 if d: opt = opt + '(' + str(d) + ')'
150 150 ui.write(opt, "\n")
151 151 if c: ui.write(' %s\n' % c)
152 152 ui.write("\n")
153 153
154 154 ui.write(i[0].__doc__, "\n")
155 155 except UnknownCommand:
156 156 ui.warn("hg: unknown command %s\n" % cmd)
157 157 sys.exit(0)
158 158 else:
159 159 ui.status('hg commands:\n\n')
160 160
161 161 h = {}
162 162 for e in table.values():
163 163 f = e[0]
164 164 if f.__name__.startswith("debug"): continue
165 165 d = ""
166 166 if f.__doc__:
167 167 d = f.__doc__.splitlines(0)[0].rstrip()
168 168 h[f.__name__] = d
169 169
170 170 fns = h.keys()
171 171 fns.sort()
172 172 m = max(map(len, fns))
173 173 for f in fns:
174 174 ui.status(' %-*s %s\n' % (m, f, h[f]))
175 175
176 176 # Commands start here, listed alphabetically
177 177
178 178 def add(ui, repo, file, *files):
179 179 '''add the specified files on the next commit'''
180 180 repo.add(relpath(repo, (file,) + files))
181 181
182 182 def addremove(ui, repo, *files):
183 183 """add all new files, delete all missing files"""
184 184 if files:
185 185 files = relpath(repo, files)
186 186 d = []
187 187 u = []
188 188 for f in files:
189 189 p = repo.wjoin(f)
190 190 s = repo.dirstate.state(f)
191 191 isfile = os.path.isfile(p)
192 192 if s != 'r' and not isfile:
193 193 d.append(f)
194 194 elif s not in 'nmai' and isfile:
195 195 u.append(f)
196 196 else:
197 197 (c, a, d, u) = repo.diffdir(repo.root)
198 198 repo.add(u)
199 199 repo.remove(d)
200 200
201 201 def annotate(u, repo, file, *files, **ops):
202 202 """show changeset information per file line"""
203 203 def getnode(rev):
204 204 return hg.short(repo.changelog.node(rev))
205 205
206 206 def getname(rev):
207 207 try:
208 208 return bcache[rev]
209 209 except KeyError:
210 210 cl = repo.changelog.read(repo.changelog.node(rev))
211 211 name = cl[1]
212 212 f = name.find('@')
213 213 if f >= 0:
214 214 name = name[:f]
215 215 bcache[rev] = name
216 216 return name
217 217
218 218 bcache = {}
219 219 opmap = [['user', getname], ['number', str], ['changeset', getnode]]
220 220 if not ops['user'] and not ops['changeset']:
221 221 ops['number'] = 1
222 222
223 223 node = repo.dirstate.parents()[0]
224 224 if ops['revision']:
225 225 node = repo.changelog.lookup(ops['revision'])
226 226 change = repo.changelog.read(node)
227 227 mmap = repo.manifest.read(change[0])
228 228 maxuserlen = 0
229 229 maxchangelen = 0
230 230 for f in relpath(repo, (file,) + files):
231 231 lines = repo.file(f).annotate(mmap[f])
232 232 pieces = []
233 233
234 234 for o, f in opmap:
235 235 if ops[o]:
236 236 l = [ f(n) for n,t in lines ]
237 237 m = max(map(len, l))
238 238 pieces.append([ "%*s" % (m, x) for x in l])
239 239
240 240 for p,l in zip(zip(*pieces), lines):
241 241 u.write(" ".join(p) + ": " + l[1])
242 242
243 243 def cat(ui, repo, file, rev = []):
244 244 """output the latest or given revision of a file"""
245 245 r = repo.file(relpath(repo, [file])[0])
246 246 n = r.tip()
247 247 if rev: n = r.lookup(rev)
248 248 sys.stdout.write(r.read(n))
249 249
250 250 def commit(ui, repo, *files, **opts):
251 251 """commit the specified files or all outstanding changes"""
252 252 text = opts['text']
253 253 if not text and opts['logfile']:
254 254 try: text = open(opts['logfile']).read()
255 255 except IOError: pass
256 256
257 257 if opts['addremove']:
258 258 addremove(ui, repo, *files)
259 259 repo.commit(relpath(repo, files), text, opts['user'], opts['date'])
260 260
261 261 def copy(ui, repo, source, dest):
262 262 """mark a file as copied or renamed for the next commit"""
263 263 return repo.copy(*relpath(repo, (source, dest)))
264 264
265 265 def debugaddchangegroup(ui, repo):
266 266 data = sys.stdin.read()
267 267 repo.addchangegroup(data)
268 268
269 269 def debugchangegroup(ui, repo, roots):
270 270 newer = repo.newer(map(repo.lookup, roots))
271 271 for chunk in repo.changegroup(newer):
272 272 sys.stdout.write(chunk)
273 273
274 274 def debugindex(ui, file):
275 r = hg.revlog(open, file, "")
275 r = hg.revlog(hg.opener(""), file, "")
276 276 print " rev offset length base linkrev"+\
277 277 " p1 p2 nodeid"
278 278 for i in range(r.count()):
279 279 e = r.index[i]
280 280 print "% 6d % 9d % 7d % 6d % 7d %s.. %s.. %s.." % (
281 281 i, e[0], e[1], e[2], e[3],
282 282 hg.hex(e[4][:5]), hg.hex(e[5][:5]), hg.hex(e[6][:5]))
283 283
284 284 def debugindexdot(ui, file):
285 r = hg.revlog(open, file, "")
285 r = hg.revlog(hg.opener(""), file, "")
286 286 print "digraph G {"
287 287 for i in range(r.count()):
288 288 e = r.index[i]
289 289 print "\t%d -> %d" % (r.rev(e[4]), i)
290 290 if e[5] != hg.nullid:
291 291 print "\t%d -> %d" % (r.rev(e[5]), i)
292 292 print "}"
293 293
294 294 def diff(ui, repo, *files, **opts):
295 295 """diff working directory (or selected files)"""
296 296 revs = []
297 297 if opts['rev']:
298 298 revs = map(lambda x: repo.lookup(x), opts['rev'])
299 299
300 300 if len(revs) > 2:
301 301 self.ui.warn("too many revisions to diff\n")
302 302 sys.exit(1)
303 303
304 304 if files:
305 305 files = relpath(repo, files)
306 306 else:
307 307 files = relpath(repo, [""])
308 308
309 309 dodiff(ui, repo, os.getcwd(), files, *revs)
310 310
311 311 def export(ui, repo, changeset):
312 312 """dump the changeset header and diffs for a revision"""
313 313 node = repo.lookup(changeset)
314 314 prev, other = repo.changelog.parents(node)
315 315 change = repo.changelog.read(node)
316 316 print "# HG changeset patch"
317 317 print "# User %s" % change[1]
318 318 print "# Node ID %s" % hg.hex(node)
319 319 print "# Parent %s" % hg.hex(prev)
320 320 print
321 321 if other != hg.nullid:
322 322 print "# Parent %s" % hg.hex(other)
323 323 print change[4].rstrip()
324 324 print
325 325
326 326 dodiff(ui, repo, "", None, prev, node)
327 327
328 328 def forget(ui, repo, file, *files):
329 329 """don't add the specified files on the next commit"""
330 330 repo.forget(relpath(repo, (file,) + files))
331 331
332 332 def heads(ui, repo):
333 333 """show current repository heads"""
334 334 for n in repo.changelog.heads():
335 335 show_changeset(ui, repo, changenode=n)
336 336
337 337 def history(ui, repo):
338 338 """show the changelog history"""
339 339 for i in range(repo.changelog.count() - 1, -1, -1):
340 340 show_changeset(ui, repo, rev=i)
341 341
342 342 def identify(ui, repo):
343 343 """print information about the working copy"""
344 344 parents = [p for p in repo.dirstate.parents() if p != hg.nullid]
345 345 if not parents:
346 346 ui.write("unknown\n")
347 347 return
348 348
349 349 hexfunc = ui.verbose and hg.hex or hg.short
350 350 (c, a, d, u) = repo.diffdir(repo.root)
351 351 output = ["%s%s" % ('+'.join([hexfunc(parent) for parent in parents]),
352 352 (c or a or d) and "+" or "")]
353 353
354 354 if not ui.quiet:
355 355 # multiple tags for a single parent separated by '/'
356 356 parenttags = ['/'.join(tags)
357 357 for tags in map(repo.nodetags, parents) if tags]
358 358 # tags for multiple parents separated by ' + '
359 359 output.append(' + '.join(parenttags))
360 360
361 361 ui.write("%s\n" % ' '.join(output))
362 362
363 363 def init(ui, source=None, **opts):
364 364 """create a new repository or copy an existing one"""
365 365
366 366 if source:
367 367 paths = {}
368 368 for name, path in ui.configitems("paths"):
369 369 paths[name] = path
370 370
371 371 if source in paths: source = paths[source]
372 372
373 373 link = 0
374 374 if not source.startswith("http://"):
375 375 d1 = os.stat(os.getcwd()).st_dev
376 376 d2 = os.stat(source).st_dev
377 377 if d1 == d2: link = 1
378 378
379 379 if link:
380 380 ui.debug("copying by hardlink\n")
381 381 os.system("cp -al %s/.hg .hg" % source)
382 382 try:
383 383 os.remove(".hg/dirstate")
384 384 except: pass
385 385
386 386 repo = hg.repository(ui, ".")
387 387
388 388 else:
389 389 repo = hg.repository(ui, ".", create=1)
390 390 other = hg.repository(ui, source)
391 391 cg = repo.getchangegroup(other)
392 392 repo.addchangegroup(cg)
393 393
394 394 f = repo.opener("hgrc", "w")
395 395 f.write("[paths]\n")
396 396 f.write("default = %s\n" % source)
397 397
398 398 if opts['update']:
399 399 update(ui, repo)
400 400 else:
401 401 repo = hg.repository(ui, ".", create=1)
402 402
403 403 def log(ui, repo, f):
404 404 """show the revision history of a single file"""
405 405 f = relpath(repo, [f])[0]
406 406
407 407 r = repo.file(f)
408 408 for i in range(r.count() - 1, -1, -1):
409 409 show_changeset(ui, repo, filelog=r, rev=i)
410 410
411 411 def manifest(ui, repo, rev = []):
412 412 """output the latest or given revision of the project manifest"""
413 413 n = repo.manifest.tip()
414 414 if rev:
415 415 n = repo.manifest.lookup(rev)
416 416 m = repo.manifest.read(n)
417 417 mf = repo.manifest.readflags(n)
418 418 files = m.keys()
419 419 files.sort()
420 420
421 421 for f in files:
422 422 ui.write("%40s %3s %s\n" % (hg.hex(m[f]), mf[f] and "755" or "644", f))
423 423
424 424 def parents(ui, repo, node = None):
425 425 '''show the parents of the current working dir'''
426 426 if node:
427 427 p = repo.changelog.parents(repo.lookup(hg.bin(node)))
428 428 else:
429 429 p = repo.dirstate.parents()
430 430
431 431 for n in p:
432 432 if n != hg.nullid:
433 433 show_changeset(ui, repo, changenode=n)
434 434
435 435 def patch(ui, repo, patch1, *patches, **opts):
436 436 """import an ordered set of patches"""
437 437 try:
438 438 import psyco
439 439 psyco.full()
440 440 except:
441 441 pass
442 442
443 443 patches = (patch1,) + patches
444 444
445 445 d = opts["base"]
446 446 strip = opts["strip"]
447 447 quiet = opts["quiet"] and "> /dev/null" or ""
448 448
449 449 for patch in patches:
450 450 ui.status("applying %s\n" % patch)
451 451 pf = os.path.join(d, patch)
452 452
453 453 text = ""
454 454 for l in file(pf):
455 455 if l[:4] == "--- ": break
456 456 text += l
457 457
458 458 # make sure text isn't empty
459 459 if not text: text = "imported patch %s\n" % patch
460 460
461 461 f = os.popen("patch -p%d < %s" % (strip, pf))
462 462 files = []
463 463 for l in f.read().splitlines():
464 464 l.rstrip('\r\n');
465 465 if not quiet:
466 466 print l
467 467 if l[:14] == 'patching file ':
468 468 files.append(l[14:])
469 469 f.close()
470 470
471 471 if len(files) > 0:
472 472 addremove(ui, repo, *files)
473 473 repo.commit(files, text)
474 474
475 475 def pull(ui, repo, source="default", **opts):
476 476 """pull changes from the specified source"""
477 477 paths = {}
478 478 for name, path in ui.configitems("paths"):
479 479 paths[name] = path
480 480
481 481 if source in paths:
482 482 source = paths[source]
483 483
484 484 ui.status('pulling from %s\n' % (source))
485 485
486 486 other = hg.repository(ui, source)
487 487 cg = repo.getchangegroup(other)
488 488 r = repo.addchangegroup(cg)
489 489 if cg and not r:
490 490 if opts['update']:
491 491 return update(ui, repo)
492 492 else:
493 493 ui.status("(run 'hg update' to get a working copy)\n")
494 494
495 495 return r
496 496
497 497 def push(ui, repo, dest="default-push"):
498 498 """push changes to the specified destination"""
499 499 paths = {}
500 500 for name, path in ui.configitems("paths"):
501 501 paths[name] = path
502 502
503 503 if dest in paths: dest = paths[dest]
504 504
505 505 if not dest.startswith("ssh://"):
506 506 ui.warn("abort: can only push to ssh:// destinations currently\n")
507 507 return 1
508 508
509 509 m = re.match(r'ssh://(([^@]+)@)?([^:/]+)(:(\d+))?(/(.*))?', dest)
510 510 if not m:
511 511 ui.warn("abort: couldn't parse destination %s\n" % dest)
512 512 return 1
513 513
514 514 user, host, port, path = map(m.group, (2, 3, 5, 7))
515 515 host = user and ("%s@%s" % (user, host)) or host
516 516 port = port and (" -p %s") % port or ""
517 517 path = path or ""
518 518
519 519 sport = random.randrange(30000, 60000)
520 520 cmd = "ssh %s%s -R %d:localhost:%d 'cd %s; hg pull http://localhost:%d/'"
521 521 cmd = cmd % (host, port, sport+1, sport, path, sport+1)
522 522
523 523 child = os.fork()
524 524 if not child:
525 525 sys.stdout = file("/dev/null", "w")
526 526 sys.stderr = sys.stdout
527 527 hgweb.server(repo.root, "pull", "", "localhost", sport)
528 528 else:
529 529 r = os.system(cmd)
530 530 os.kill(child, signal.SIGTERM)
531 531 return r
532 532
533 533 def rawcommit(ui, repo, *flist, **rc):
534 534 "raw commit interface"
535 535
536 536 text = rc['text']
537 537 if not text and rc['logfile']:
538 538 try: text = open(rc['logfile']).read()
539 539 except IOError: pass
540 540 if not text and not rc['logfile']:
541 541 print "missing commit text"
542 542 return 1
543 543
544 544 files = relpath(repo, list(flist))
545 545 if rc['files']:
546 546 files += open(rc['files']).read().splitlines()
547 547
548 548 repo.rawcommit(files, text, rc['user'], rc['date'], *rc['parent'])
549 549
550 550 def recover(ui, repo):
551 551 """roll back an interrupted transaction"""
552 552 repo.recover()
553 553
554 554 def remove(ui, repo, file, *files):
555 555 """remove the specified files on the next commit"""
556 556 repo.remove(relpath(repo, (file,) + files))
557 557
558 558 def serve(ui, repo, **opts):
559 559 """export the repository via HTTP"""
560 560 hgweb.server(repo.root, opts["name"], opts["templates"],
561 561 opts["address"], opts["port"])
562 562
563 563 def status(ui, repo):
564 564 '''show changed files in the working directory
565 565
566 566 C = changed
567 567 A = added
568 568 R = removed
569 569 ? = not tracked'''
570 570
571 571 (c, a, d, u) = repo.diffdir(os.getcwd())
572 572 (c, a, d, u) = map(lambda x: relfilter(repo, x), (c, a, d, u))
573 573
574 574 for f in c: print "C", f
575 575 for f in a: print "A", f
576 576 for f in d: print "R", f
577 577 for f in u: print "?", f
578 578
579 579 def tag(ui, repo, name, rev = None, **opts):
580 580 """add a tag for the current tip or a given revision"""
581 581
582 582 if name == "tip":
583 583 ui.warn("abort: 'tip' is a reserved name!\n")
584 584 return -1
585 585
586 586 (c, a, d, u) = repo.diffdir(repo.root)
587 587 for x in (c, a, d, u):
588 588 if ".hgtags" in x:
589 589 ui.warn("abort: working copy of .hgtags is changed!\n")
590 590 ui.status("(please commit .hgtags manually)\n")
591 591 return -1
592 592
593 593 if rev:
594 594 r = hg.hex(repo.lookup(rev))
595 595 else:
596 596 r = hg.hex(repo.changelog.tip())
597 597
598 598 add = 0
599 599 if not os.path.exists(repo.wjoin(".hgtags")): add = 1
600 600 repo.wfile(".hgtags", "a").write("%s %s\n" % (r, name))
601 601 if add: repo.add([".hgtags"])
602 602
603 603 if not opts['text']:
604 604 opts['text'] = "Added tag %s for changeset %s" % (name, r)
605 605
606 606 repo.commit([".hgtags"], opts['text'], opts['user'], opts['date'])
607 607
608 608 def tags(ui, repo):
609 609 """list repository tags"""
610 610
611 611 l = repo.tagslist()
612 612 l.reverse()
613 613 for t,n in l:
614 614 try:
615 615 r = repo.changelog.rev(n)
616 616 except KeyError:
617 617 r = "?"
618 618 print "%-30s %5d:%s" % (t, repo.changelog.rev(n), hg.hex(n))
619 619
620 620 def tip(ui, repo):
621 621 """show the tip revision"""
622 622 n = repo.changelog.tip()
623 623 show_changeset(ui, repo, changenode=n)
624 624
625 625 def undo(ui, repo):
626 626 """undo the last transaction"""
627 627 repo.undo()
628 628
629 629 def update(ui, repo, node=None, merge=False, clean=False):
630 630 '''update or merge working directory
631 631
632 632 If there are no outstanding changes in the working directory and
633 633 there is a linear relationship between the current version and the
634 634 requested version, the result is the requested version.
635 635
636 636 Otherwise the result is a merge between the contents of the
637 637 current working directory and the requested version. Files that
638 638 changed between either parent are marked as changed for the next
639 639 commit and a commit must be performed before any further updates
640 640 are allowed.
641 641 '''
642 642 node = node and repo.lookup(node) or repo.changelog.tip()
643 643 return repo.update(node, allow=merge, force=clean)
644 644
645 645 def verify(ui, repo):
646 646 """verify the integrity of the repository"""
647 647 return repo.verify()
648 648
649 649 # Command options and aliases are listed here, alphabetically
650 650
651 651 table = {
652 652 "add": (add, [], "hg add [files]"),
653 653 "addremove": (addremove, [], "hg addremove [files]"),
654 654 "ann|annotate": (annotate,
655 655 [('r', 'revision', '', 'revision'),
656 656 ('u', 'user', None, 'show user'),
657 657 ('n', 'number', None, 'show revision number'),
658 658 ('c', 'changeset', None, 'show changeset')],
659 659 'hg annotate [-u] [-c] [-n] [-r id] [files]'),
660 660 "cat|dump": (cat, [], 'hg cat <file> [rev]'),
661 661 "commit|ci": (commit,
662 662 [('t', 'text', "", 'commit text'),
663 663 ('A', 'addremove', None, 'run add/remove during commit'),
664 664 ('l', 'logfile', "", 'commit text file'),
665 665 ('d', 'date', "", 'data'),
666 666 ('u', 'user', "", 'user')],
667 667 'hg commit [files]'),
668 668 "copy": (copy, [], 'hg copy <source> <dest>'),
669 669 "debugaddchangegroup": (debugaddchangegroup, [], 'debugaddchangegroup'),
670 670 "debugchangegroup": (debugchangegroup, [], 'debugchangegroup [roots]'),
671 671 "debugindex": (debugindex, [], 'debugindex <file>'),
672 672 "debugindexdot": (debugindexdot, [], 'debugindexdot <file>'),
673 673 "diff": (diff, [('r', 'rev', [], 'revision')],
674 674 'hg diff [-r A] [-r B] [files]'),
675 675 "export": (export, [], "hg export <changeset>"),
676 676 "forget": (forget, [], "hg forget [files]"),
677 677 "heads": (heads, [], 'hg heads'),
678 678 "history": (history, [], 'hg history'),
679 679 "help": (help, [], 'hg help [command]'),
680 680 "identify|id": (identify, [], 'hg identify'),
681 681 "init": (init, [('u', 'update', None, 'update after init')],
682 682 'hg init [options] [url]'),
683 683 "log": (log, [], 'hg log <file>'),
684 684 "manifest|dumpmanifest": (manifest, [], 'hg manifest [rev]'),
685 685 "parents": (parents, [], 'hg parents [node]'),
686 686 "patch|import": (patch,
687 687 [('p', 'strip', 1, 'path strip'),
688 688 ('b', 'base', "", 'base path'),
689 689 ('q', 'quiet', "", 'silence diff')],
690 690 "hg import [options] patches"),
691 691 "pull|merge": (pull,
692 692 [('u', 'update', None, 'update working directory')],
693 693 'hg pull [options] [source]'),
694 694 "push": (push, [], 'hg push <destination>'),
695 695 "rawcommit": (rawcommit,
696 696 [('p', 'parent', [], 'parent'),
697 697 ('d', 'date', "", 'data'),
698 698 ('u', 'user', "", 'user'),
699 699 ('F', 'files', "", 'file list'),
700 700 ('t', 'text', "", 'commit text'),
701 701 ('l', 'logfile', "", 'commit text file')],
702 702 'hg rawcommit [options] [files]'),
703 703 "recover": (recover, [], "hg recover"),
704 704 "remove": (remove, [], "hg remove [files]"),
705 705 "serve": (serve, [('p', 'port', 8000, 'listen port'),
706 706 ('a', 'address', '', 'interface address'),
707 707 ('n', 'name', os.getcwd(), 'repository name'),
708 708 ('t', 'templates', "", 'template map')],
709 709 "hg serve [options]"),
710 710 "status": (status, [], 'hg status'),
711 711 "tag": (tag, [('t', 'text', "", 'commit text'),
712 712 ('d', 'date', "", 'date'),
713 713 ('u', 'user', "", 'user')],
714 714 'hg tag [options] <name> [rev]'),
715 715 "tags": (tags, [], 'hg tags'),
716 716 "tip": (tip, [], 'hg tip'),
717 717 "undo": (undo, [], 'hg undo'),
718 718 "update|up|checkout|co|resolve": (update,
719 719 [('m', 'merge', None,
720 720 'allow merging of conflicts'),
721 721 ('C', 'clean', None,
722 722 'overwrite locally modified files')],
723 723 'hg update [options] [node]'),
724 724 "verify": (verify, [], 'hg verify'),
725 725 }
726 726
727 727 norepo = "init branch help debugindex debugindexdot"
728 728
729 729 def find(cmd):
730 730 i = None
731 731 for e in table.keys():
732 732 if re.match("(%s)$" % e, cmd):
733 733 return table[e]
734 734
735 735 raise UnknownCommand(cmd)
736 736
737 737 class SignalInterrupt(Exception): pass
738 738
739 739 def catchterm(*args):
740 740 raise SignalInterrupt
741 741
742 742 def run():
743 743 sys.exit(dispatch(sys.argv[1:]))
744 744
745 745 def dispatch(args):
746 746 options = {}
747 747 opts = [('v', 'verbose', None, 'verbose'),
748 748 ('d', 'debug', None, 'debug'),
749 749 ('q', 'quiet', None, 'quiet'),
750 750 ('p', 'profile', None, 'profile'),
751 751 ('y', 'noninteractive', None, 'run non-interactively'),
752 752 ]
753 753
754 754 args = fancyopts.fancyopts(args, opts, options,
755 755 'hg [options] <command> [options] [files]')
756 756
757 757 if not args:
758 758 cmd = "help"
759 759 else:
760 760 cmd, args = args[0], args[1:]
761 761
762 762 u = ui.ui(options["verbose"], options["debug"], options["quiet"],
763 763 not options["noninteractive"])
764 764
765 765 try:
766 766 i = find(cmd)
767 767 except UnknownCommand:
768 768 u.warn("hg: unknown command '%s'\n" % cmd)
769 769 help(u)
770 770 sys.exit(1)
771 771
772 772 signal.signal(signal.SIGTERM, catchterm)
773 773
774 774 cmdoptions = {}
775 775 try:
776 776 args = fancyopts.fancyopts(args, i[1], cmdoptions, i[2])
777 777 except fancyopts.getopt.GetoptError, inst:
778 778 u.warn("hg %s: %s\n" % (cmd, inst))
779 779 help(u, cmd)
780 780 sys.exit(-1)
781 781
782 782 if cmd not in norepo.split():
783 783 repo = hg.repository(ui = u)
784 784 d = lambda: i[0](u, repo, *args, **cmdoptions)
785 785 else:
786 786 d = lambda: i[0](u, *args, **cmdoptions)
787 787
788 788 try:
789 789 if options['profile']:
790 790 import hotshot, hotshot.stats
791 791 prof = hotshot.Profile("hg.prof")
792 792 r = prof.runcall(d)
793 793 prof.close()
794 794 stats = hotshot.stats.load("hg.prof")
795 795 stats.strip_dirs()
796 796 stats.sort_stats('time', 'calls')
797 797 stats.print_stats(40)
798 798 return r
799 799 else:
800 800 return d()
801 801 except SignalInterrupt:
802 802 u.warn("killed!\n")
803 803 except KeyboardInterrupt:
804 804 u.warn("interrupted!\n")
805 805 except IOError, inst:
806 806 if hasattr(inst, "code"):
807 807 u.warn("abort: %s\n" % inst)
808 808 elif hasattr(inst, "reason"):
809 809 u.warn("abort: error %d: %s\n" % (inst.reason[0], inst.reason[1]))
810 810 elif hasattr(inst, "args") and inst[0] == errno.EPIPE:
811 811 u.warn("broken pipe\n")
812 812 else:
813 813 raise
814 814 except TypeError, inst:
815 815 # was this an argument error?
816 816 tb = traceback.extract_tb(sys.exc_info()[2])
817 817 if len(tb) > 2: # no
818 818 raise
819 819 u.debug(inst, "\n")
820 820 u.warn("%s: invalid arguments\n" % i[0].__name__)
821 821 help(u, cmd)
822 822 sys.exit(-1)
823 823
@@ -1,1454 +1,1454 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, os
9 9 from revlog import *
10 10 from demandload import *
11 11 demandload(globals(), "re lock urllib urllib2 transaction time socket")
12 12 demandload(globals(), "tempfile httprangereader difflib")
13 13
14 14 def is_exec(f):
15 15 return (os.stat(f).st_mode & 0100 != 0)
16 16
17 17 def set_exec(f, mode):
18 18 s = os.stat(f).st_mode
19 19 if (s & 0100 != 0) == mode:
20 20 return
21 21 if mode:
22 22 # Turn on +x for every +r bit when making a file executable
23 23 # and obey umask.
24 24 umask = os.umask(0)
25 25 os.umask(umask)
26 26 os.chmod(f, s | (s & 0444) >> 2 & ~umask)
27 27 else:
28 28 os.chmod(f, s & 0666)
29 29
30 30 class filelog(revlog):
31 31 def __init__(self, opener, path):
32 32 revlog.__init__(self, opener,
33 33 os.path.join("data", path + ".i"),
34 34 os.path.join("data", path + ".d"))
35 35
36 36 def read(self, node):
37 37 t = self.revision(node)
38 38 if t[:2] != '\1\n':
39 39 return t
40 40 s = t.find('\1\n', 2)
41 41 return t[s+2:]
42 42
43 43 def readmeta(self, node):
44 44 t = self.revision(node)
45 45 if t[:2] != '\1\n':
46 46 return t
47 47 s = t.find('\1\n', 2)
48 48 mt = t[2:s]
49 49 for l in mt.splitlines():
50 50 k, v = l.split(": ", 1)
51 51 m[k] = v
52 52 return m
53 53
54 54 def add(self, text, meta, transaction, link, p1=None, p2=None):
55 55 if meta or text[:2] == '\1\n':
56 56 mt = ""
57 57 if meta:
58 58 mt = [ "%s: %s\n" % (k, v) for k,v in meta.items() ]
59 59 text = "\1\n" + "".join(mt) + "\1\n" + text
60 60 return self.addrevision(text, transaction, link, p1, p2)
61 61
62 62 def annotate(self, node):
63 63
64 64 def decorate(text, rev):
65 65 return [(rev, l) for l in text.splitlines(1)]
66 66
67 67 def strip(annotation):
68 68 return [e[1] for e in annotation]
69 69
70 70 def pair(parent, child):
71 71 new = []
72 72 sm = difflib.SequenceMatcher(None, strip(parent), strip(child))
73 73 for o, m, n, s, t in sm.get_opcodes():
74 74 if o == 'equal':
75 75 new += parent[m:n]
76 76 else:
77 77 new += child[s:t]
78 78 return new
79 79
80 80 # find all ancestors
81 81 needed = {node:1}
82 82 visit = [node]
83 83 while visit:
84 84 n = visit.pop(0)
85 85 for p in self.parents(n):
86 86 if p not in needed:
87 87 needed[p] = 1
88 88 visit.append(p)
89 89 else:
90 90 # count how many times we'll use this
91 91 needed[p] += 1
92 92
93 93 # sort by revision which is a topological order
94 94 visit = needed.keys()
95 95 visit = [ (self.rev(n), n) for n in visit ]
96 96 visit.sort()
97 97 visit = [ p[1] for p in visit ]
98 98 hist = {}
99 99
100 100 for n in visit:
101 101 curr = decorate(self.read(n), self.linkrev(n))
102 102 for p in self.parents(n):
103 103 if p != nullid:
104 104 curr = pair(hist[p], curr)
105 105 # trim the history of unneeded revs
106 106 needed[p] -= 1
107 107 if not needed[p]:
108 108 del hist[p]
109 109 hist[n] = curr
110 110
111 111 return hist[n]
112 112
113 113 class manifest(revlog):
114 114 def __init__(self, opener):
115 115 self.mapcache = None
116 116 self.listcache = None
117 117 self.addlist = None
118 118 revlog.__init__(self, opener, "00manifest.i", "00manifest.d")
119 119
120 120 def read(self, node):
121 121 if node == nullid: return {} # don't upset local cache
122 122 if self.mapcache and self.mapcache[0] == node:
123 123 return self.mapcache[1].copy()
124 124 text = self.revision(node)
125 125 map = {}
126 126 flag = {}
127 127 self.listcache = (text, text.splitlines(1))
128 128 for l in self.listcache[1]:
129 129 (f, n) = l.split('\0')
130 130 map[f] = bin(n[:40])
131 131 flag[f] = (n[40:-1] == "x")
132 132 self.mapcache = (node, map, flag)
133 133 return map
134 134
135 135 def readflags(self, node):
136 136 if node == nullid: return {} # don't upset local cache
137 137 if not self.mapcache or self.mapcache[0] != node:
138 138 self.read(node)
139 139 return self.mapcache[2]
140 140
141 141 def diff(self, a, b):
142 142 # this is sneaky, as we're not actually using a and b
143 143 if self.listcache and self.addlist and self.listcache[0] == a:
144 144 d = mdiff.diff(self.listcache[1], self.addlist, 1)
145 145 if mdiff.patch(a, d) != b:
146 146 sys.stderr.write("*** sortdiff failed, falling back ***\n")
147 147 return mdiff.textdiff(a, b)
148 148 return d
149 149 else:
150 150 return mdiff.textdiff(a, b)
151 151
152 152 def add(self, map, flags, transaction, link, p1=None, p2=None):
153 153 files = map.keys()
154 154 files.sort()
155 155
156 156 self.addlist = ["%s\000%s%s\n" %
157 157 (f, hex(map[f]), flags[f] and "x" or '')
158 158 for f in files]
159 159 text = "".join(self.addlist)
160 160
161 161 n = self.addrevision(text, transaction, link, p1, p2)
162 162 self.mapcache = (n, map, flags)
163 163 self.listcache = (text, self.addlist)
164 164 self.addlist = None
165 165
166 166 return n
167 167
168 168 class changelog(revlog):
169 169 def __init__(self, opener):
170 170 revlog.__init__(self, opener, "00changelog.i", "00changelog.d")
171 171
172 172 def extract(self, text):
173 173 if not text:
174 174 return (nullid, "", "0", [], "")
175 175 last = text.index("\n\n")
176 176 desc = text[last + 2:]
177 177 l = text[:last].splitlines()
178 178 manifest = bin(l[0])
179 179 user = l[1]
180 180 date = l[2]
181 181 files = l[3:]
182 182 return (manifest, user, date, files, desc)
183 183
184 184 def read(self, node):
185 185 return self.extract(self.revision(node))
186 186
187 187 def add(self, manifest, list, desc, transaction, p1=None, p2=None,
188 188 user=None, date=None):
189 189 user = (user or
190 190 os.environ.get("HGUSER") or
191 191 os.environ.get("EMAIL") or
192 192 os.environ.get("LOGNAME", "unknown") + '@' + socket.getfqdn())
193 193 date = date or "%d %d" % (time.time(), time.timezone)
194 194 list.sort()
195 195 l = [hex(manifest), user, date] + list + ["", desc]
196 196 text = "\n".join(l)
197 197 return self.addrevision(text, transaction, self.count(), p1, p2)
198 198
199 199 class dirstate:
200 200 def __init__(self, opener, ui, root):
201 201 self.opener = opener
202 202 self.root = root
203 203 self.dirty = 0
204 204 self.ui = ui
205 205 self.map = None
206 206 self.pl = None
207 207 self.copies = {}
208 208
209 209 def __del__(self):
210 210 if self.dirty:
211 211 self.write()
212 212
213 213 def __getitem__(self, key):
214 214 try:
215 215 return self.map[key]
216 216 except TypeError:
217 217 self.read()
218 218 return self[key]
219 219
220 220 def __contains__(self, key):
221 221 if not self.map: self.read()
222 222 return key in self.map
223 223
224 224 def parents(self):
225 225 if not self.pl:
226 226 self.read()
227 227 return self.pl
228 228
229 229 def setparents(self, p1, p2 = nullid):
230 230 self.dirty = 1
231 231 self.pl = p1, p2
232 232
233 233 def state(self, key):
234 234 try:
235 235 return self[key][0]
236 236 except KeyError:
237 237 return "?"
238 238
239 239 def read(self):
240 240 if self.map is not None: return self.map
241 241
242 242 self.map = {}
243 243 self.pl = [nullid, nullid]
244 244 try:
245 245 st = self.opener("dirstate").read()
246 246 if not st: return
247 247 except: return
248 248
249 249 self.pl = [st[:20], st[20: 40]]
250 250
251 251 pos = 40
252 252 while pos < len(st):
253 253 e = struct.unpack(">cllll", st[pos:pos+17])
254 254 l = e[4]
255 255 pos += 17
256 256 f = st[pos:pos + l]
257 257 if '\0' in f:
258 258 f, c = f.split('\0')
259 259 self.copies[f] = c
260 260 self.map[f] = e[:4]
261 261 pos += l
262 262
263 263 def copy(self, source, dest):
264 264 self.read()
265 265 self.dirty = 1
266 266 self.copies[dest] = source
267 267
268 268 def copied(self, file):
269 269 return self.copies.get(file, None)
270 270
271 271 def update(self, files, state):
272 272 ''' current states:
273 273 n normal
274 274 m needs merging
275 275 r marked for removal
276 276 a marked for addition'''
277 277
278 278 if not files: return
279 279 self.read()
280 280 self.dirty = 1
281 281 for f in files:
282 282 if state == "r":
283 283 self.map[f] = ('r', 0, 0, 0)
284 284 else:
285 285 s = os.stat(os.path.join(self.root, f))
286 286 self.map[f] = (state, s.st_mode, s.st_size, s.st_mtime)
287 287
288 288 def forget(self, files):
289 289 if not files: return
290 290 self.read()
291 291 self.dirty = 1
292 292 for f in files:
293 293 try:
294 294 del self.map[f]
295 295 except KeyError:
296 296 self.ui.warn("not in dirstate: %s!\n" % f)
297 297 pass
298 298
299 299 def clear(self):
300 300 self.map = {}
301 301 self.dirty = 1
302 302
303 303 def write(self):
304 304 st = self.opener("dirstate", "w")
305 305 st.write("".join(self.pl))
306 306 for f, e in self.map.items():
307 307 c = self.copied(f)
308 308 if c:
309 309 f = f + "\0" + c
310 310 e = struct.pack(">cllll", e[0], e[1], e[2], e[3], len(f))
311 311 st.write(e + f)
312 312 self.dirty = 0
313 313
314 314 def dup(self):
315 315 self.read()
316 316 return self.map.copy()
317 317
318 318 # used to avoid circular references so destructors work
319 319 def opener(base):
320 320 p = base
321 321 def o(path, mode="r"):
322 322 if p[:7] == "http://":
323 323 f = os.path.join(p, urllib.quote(path))
324 324 return httprangereader.httprangereader(f)
325 325
326 326 f = os.path.join(p, path)
327 327
328 328 mode += "b" # for that other OS
329 329
330 330 if mode[0] != "r":
331 331 try:
332 332 s = os.stat(f)
333 333 except OSError:
334 334 d = os.path.dirname(f)
335 335 if not os.path.isdir(d):
336 336 os.makedirs(d)
337 337 else:
338 338 if s.st_nlink > 1:
339 file(f + ".tmp", "w").write(file(f).read())
339 file(f + ".tmp", "wb").write(file(f, "rb").read())
340 340 os.rename(f+".tmp", f)
341 341
342 342 return file(f, mode)
343 343
344 344 return o
345 345
346 346 class localrepository:
347 347 def __init__(self, ui, path=None, create=0):
348 348 self.remote = 0
349 349 if path and path[:7] == "http://":
350 350 self.remote = 1
351 351 self.path = path
352 352 else:
353 353 if not path:
354 354 p = os.getcwd()
355 355 while not os.path.isdir(os.path.join(p, ".hg")):
356 356 p = os.path.dirname(p)
357 357 if p == "/": raise "No repo found"
358 358 path = p
359 359 self.path = os.path.join(path, ".hg")
360 360
361 361 if not create and not os.path.isdir(self.path):
362 362 raise "repository %s not found" % self.path
363 363
364 364 self.root = path
365 365 self.ui = ui
366 366
367 367 if create:
368 368 os.mkdir(self.path)
369 369 os.mkdir(self.join("data"))
370 370
371 371 self.opener = opener(self.path)
372 372 self.wopener = opener(self.root)
373 373 self.manifest = manifest(self.opener)
374 374 self.changelog = changelog(self.opener)
375 375 self.ignorelist = None
376 376 self.tagscache = None
377 377 self.nodetagscache = None
378 378
379 379 if not self.remote:
380 380 self.dirstate = dirstate(self.opener, ui, self.root)
381 381 try:
382 382 self.ui.readconfig(self.opener("hgrc"))
383 383 except IOError: pass
384 384
385 385 def ignore(self, f):
386 386 if self.ignorelist is None:
387 387 self.ignorelist = []
388 388 try:
389 l = self.wfile(".hgignore")
389 l = file(self.wjoin(".hgignore"))
390 390 for pat in l:
391 391 if pat != "\n":
392 392 self.ignorelist.append(re.compile(pat[:-1]))
393 393 except IOError: pass
394 394 for pat in self.ignorelist:
395 395 if pat.search(f): return True
396 396 return False
397 397
398 398 def tags(self):
399 399 '''return a mapping of tag to node'''
400 400 if not self.tagscache:
401 401 self.tagscache = {}
402 402 try:
403 403 # read each head of the tags file, ending with the tip
404 404 # and add each tag found to the map, with "newer" ones
405 405 # taking precedence
406 406 fl = self.file(".hgtags")
407 407 h = fl.heads()
408 408 h.reverse()
409 409 for r in h:
410 410 for l in fl.revision(r).splitlines():
411 411 if l:
412 412 n, k = l.split(" ", 1)
413 413 self.tagscache[k.strip()] = bin(n)
414 414 except KeyError: pass
415 415 self.tagscache['tip'] = self.changelog.tip()
416 416
417 417 return self.tagscache
418 418
419 419 def tagslist(self):
420 420 '''return a list of tags ordered by revision'''
421 421 l = []
422 422 for t,n in self.tags().items():
423 423 try:
424 424 r = self.changelog.rev(n)
425 425 except:
426 426 r = -2 # sort to the beginning of the list if unknown
427 427 l.append((r,t,n))
428 428 l.sort()
429 429 return [(t,n) for r,t,n in l]
430 430
431 431 def nodetags(self, node):
432 432 '''return the tags associated with a node'''
433 433 if not self.nodetagscache:
434 434 self.nodetagscache = {}
435 435 for t,n in self.tags().items():
436 436 self.nodetagscache.setdefault(n,[]).append(t)
437 437 return self.nodetagscache.get(node, [])
438 438
439 439 def lookup(self, key):
440 440 try:
441 441 return self.tags()[key]
442 442 except KeyError:
443 443 return self.changelog.lookup(key)
444 444
445 445 def join(self, f):
446 446 return os.path.join(self.path, f)
447 447
448 448 def wjoin(self, f):
449 449 return os.path.join(self.root, f)
450 450
451 451 def file(self, f):
452 452 if f[0] == '/': f = f[1:]
453 453 return filelog(self.opener, f)
454 454
455 455 def wfile(self, f, mode='r'):
456 456 return self.wopener(f, mode)
457 457
458 458 def transaction(self):
459 459 # save dirstate for undo
460 460 try:
461 461 ds = self.opener("dirstate").read()
462 462 except IOError:
463 463 ds = ""
464 464 self.opener("undo.dirstate", "w").write(ds)
465 465
466 466 return transaction.transaction(self.opener, self.join("journal"),
467 467 self.join("undo"))
468 468
469 469 def recover(self):
470 470 lock = self.lock()
471 471 if os.path.exists(self.join("recover")):
472 472 self.ui.status("attempting to rollback interrupted transaction\n")
473 473 return transaction.rollback(self.opener, self.join("recover"))
474 474 else:
475 475 self.ui.warn("no interrupted transaction available\n")
476 476
477 477 def undo(self):
478 478 lock = self.lock()
479 479 if os.path.exists(self.join("undo")):
480 480 self.ui.status("attempting to rollback last transaction\n")
481 481 transaction.rollback(self.opener, self.join("undo"))
482 482 self.dirstate = None
483 483 os.rename(self.join("undo.dirstate"), self.join("dirstate"))
484 484 self.dirstate = dirstate(self.opener, self.ui, self.root)
485 485 else:
486 486 self.ui.warn("no undo information available\n")
487 487
488 488 def lock(self, wait = 1):
489 489 try:
490 490 return lock.lock(self.join("lock"), 0)
491 491 except lock.LockHeld, inst:
492 492 if wait:
493 493 self.ui.warn("waiting for lock held by %s\n" % inst.args[0])
494 494 return lock.lock(self.join("lock"), wait)
495 495 raise inst
496 496
497 497 def rawcommit(self, files, text, user, date, p1=None, p2=None):
498 498 p1 = p1 or self.dirstate.parents()[0] or nullid
499 499 p2 = p2 or self.dirstate.parents()[1] or nullid
500 500 c1 = self.changelog.read(p1)
501 501 c2 = self.changelog.read(p2)
502 502 m1 = self.manifest.read(c1[0])
503 503 mf1 = self.manifest.readflags(c1[0])
504 504 m2 = self.manifest.read(c2[0])
505 505
506 506 tr = self.transaction()
507 507 mm = m1.copy()
508 508 mfm = mf1.copy()
509 509 linkrev = self.changelog.count()
510 510 self.dirstate.setparents(p1, p2)
511 511 for f in files:
512 512 try:
513 513 t = self.wfile(f).read()
514 514 tm = is_exec(self.wjoin(f))
515 515 r = self.file(f)
516 516 mfm[f] = tm
517 517 mm[f] = r.add(t, {}, tr, linkrev,
518 518 m1.get(f, nullid), m2.get(f, nullid))
519 519 self.dirstate.update([f], "n")
520 520 except IOError:
521 521 try:
522 522 del mm[f]
523 523 del mfm[f]
524 524 self.dirstate.forget([f])
525 525 except:
526 526 # deleted from p2?
527 527 pass
528 528
529 529 mnode = self.manifest.add(mm, mfm, tr, linkrev, c1[0], c2[0])
530 530 n = self.changelog.add(mnode, files, text, tr, p1, p2, user, date)
531 531 tr.close()
532 532
533 533 def commit(self, files = None, text = "", user = None, date = None):
534 534 commit = []
535 535 remove = []
536 536 if files:
537 537 for f in files:
538 538 s = self.dirstate.state(f)
539 539 if s in 'nmai':
540 540 commit.append(f)
541 541 elif s == 'r':
542 542 remove.append(f)
543 543 else:
544 544 self.ui.warn("%s not tracked!\n" % f)
545 545 else:
546 546 (c, a, d, u) = self.diffdir(self.root)
547 547 commit = c + a
548 548 remove = d
549 549
550 550 if not commit and not remove:
551 551 self.ui.status("nothing changed\n")
552 552 return
553 553
554 554 p1, p2 = self.dirstate.parents()
555 555 c1 = self.changelog.read(p1)
556 556 c2 = self.changelog.read(p2)
557 557 m1 = self.manifest.read(c1[0])
558 558 mf1 = self.manifest.readflags(c1[0])
559 559 m2 = self.manifest.read(c2[0])
560 560 lock = self.lock()
561 561 tr = self.transaction()
562 562
563 563 # check in files
564 564 new = {}
565 565 linkrev = self.changelog.count()
566 566 commit.sort()
567 567 for f in commit:
568 568 self.ui.note(f + "\n")
569 569 try:
570 570 fp = self.wjoin(f)
571 571 mf1[f] = is_exec(fp)
572 t = file(fp).read()
572 t = self.wopener(f).read()
573 573 except IOError:
574 574 self.warn("trouble committing %s!\n" % f)
575 575 raise
576 576
577 577 meta = {}
578 578 cp = self.dirstate.copied(f)
579 579 if cp:
580 580 meta["copy"] = cp
581 581 meta["copyrev"] = hex(m1.get(cp, m2.get(cp, nullid)))
582 582 self.ui.debug(" %s: copy %s:%s\n" % (f, cp, meta["copyrev"]))
583 583
584 584 r = self.file(f)
585 585 fp1 = m1.get(f, nullid)
586 586 fp2 = m2.get(f, nullid)
587 587 new[f] = r.add(t, meta, tr, linkrev, fp1, fp2)
588 588
589 589 # update manifest
590 590 m1.update(new)
591 591 for f in remove:
592 592 if f in m1:
593 593 del m1[f]
594 594 mn = self.manifest.add(m1, mf1, tr, linkrev, c1[0], c2[0])
595 595
596 596 # add changeset
597 597 new = new.keys()
598 598 new.sort()
599 599
600 600 if not text:
601 601 edittext = "\n" + "HG: manifest hash %s\n" % hex(mn)
602 602 edittext += "".join(["HG: changed %s\n" % f for f in new])
603 603 edittext += "".join(["HG: removed %s\n" % f for f in remove])
604 604 edittext = self.ui.edit(edittext)
605 605 if not edittext.rstrip():
606 606 return 1
607 607 text = edittext
608 608
609 609 n = self.changelog.add(mn, new, text, tr, p1, p2, user, date)
610 610 tr.close()
611 611
612 612 self.dirstate.setparents(n)
613 613 self.dirstate.update(new, "n")
614 614 self.dirstate.forget(remove)
615 615
616 616 def diffdir(self, path, changeset = None):
617 617 changed = []
618 618 added = []
619 619 unknown = []
620 620 mf = {}
621 621
622 622 if changeset:
623 623 change = self.changelog.read(changeset)
624 624 mf = self.manifest.read(change[0])
625 625 dc = dict.fromkeys(mf)
626 626 else:
627 627 changeset = self.dirstate.parents()[0]
628 628 change = self.changelog.read(changeset)
629 629 mf = self.manifest.read(change[0])
630 630 dc = self.dirstate.dup()
631 631
632 632 def fcmp(fn):
633 633 t1 = self.wfile(fn).read()
634 634 t2 = self.file(fn).revision(mf[fn])
635 635 return cmp(t1, t2)
636 636
637 637 for dir, subdirs, files in os.walk(path):
638 638 d = dir[len(self.root)+1:]
639 639 if ".hg" in subdirs: subdirs.remove(".hg")
640 640
641 641 for f in files:
642 642 fn = os.path.join(d, f)
643 643 try: s = os.stat(os.path.join(self.root, fn))
644 644 except: continue
645 645 if fn in dc:
646 646 c = dc[fn]
647 647 del dc[fn]
648 648 if not c:
649 649 if fcmp(fn):
650 650 changed.append(fn)
651 651 elif c[0] == 'm':
652 652 changed.append(fn)
653 653 elif c[0] == 'a':
654 654 added.append(fn)
655 655 elif c[0] == 'r':
656 656 unknown.append(fn)
657 657 elif c[2] != s.st_size or (c[1] ^ s.st_mode) & 0100:
658 658 changed.append(fn)
659 659 elif c[1] != s.st_mode or c[3] != s.st_mtime:
660 660 if fcmp(fn):
661 661 changed.append(fn)
662 662 else:
663 663 if self.ignore(fn): continue
664 664 unknown.append(fn)
665 665
666 666 deleted = dc.keys()
667 667 deleted.sort()
668 668
669 669 return (changed, added, deleted, unknown)
670 670
671 671 def diffrevs(self, node1, node2):
672 672 changed, added = [], []
673 673
674 674 change = self.changelog.read(node1)
675 675 mf1 = self.manifest.read(change[0])
676 676 change = self.changelog.read(node2)
677 677 mf2 = self.manifest.read(change[0])
678 678
679 679 for fn in mf2:
680 680 if mf1.has_key(fn):
681 681 if mf1[fn] != mf2[fn]:
682 682 changed.append(fn)
683 683 del mf1[fn]
684 684 else:
685 685 added.append(fn)
686 686
687 687 deleted = mf1.keys()
688 688 deleted.sort()
689 689
690 690 return (changed, added, deleted)
691 691
692 692 def add(self, list):
693 693 for f in list:
694 694 p = self.wjoin(f)
695 695 if not os.path.isfile(p):
696 696 self.ui.warn("%s does not exist!\n" % f)
697 697 elif self.dirstate.state(f) == 'n':
698 698 self.ui.warn("%s already tracked!\n" % f)
699 699 else:
700 700 self.dirstate.update([f], "a")
701 701
702 702 def forget(self, list):
703 703 for f in list:
704 704 if self.dirstate.state(f) not in 'ai':
705 705 self.ui.warn("%s not added!\n" % f)
706 706 else:
707 707 self.dirstate.forget([f])
708 708
709 709 def remove(self, list):
710 710 for f in list:
711 711 p = self.wjoin(f)
712 712 if os.path.isfile(p):
713 713 self.ui.warn("%s still exists!\n" % f)
714 714 elif self.dirstate.state(f) == 'a':
715 715 self.ui.warn("%s never committed!\n" % f)
716 716 self.dirstate.forget(f)
717 717 elif f not in self.dirstate:
718 718 self.ui.warn("%s not tracked!\n" % f)
719 719 else:
720 720 self.dirstate.update([f], "r")
721 721
722 722 def copy(self, source, dest):
723 723 p = self.wjoin(dest)
724 724 if not os.path.isfile(dest):
725 725 self.ui.warn("%s does not exist!\n" % dest)
726 726 else:
727 727 if self.dirstate.state(dest) == '?':
728 728 self.dirstate.update([dest], "a")
729 729 self.dirstate.copy(source, dest)
730 730
731 731 def heads(self):
732 732 return self.changelog.heads()
733 733
734 734 def branches(self, nodes):
735 735 if not nodes: nodes = [self.changelog.tip()]
736 736 b = []
737 737 for n in nodes:
738 738 t = n
739 739 while n:
740 740 p = self.changelog.parents(n)
741 741 if p[1] != nullid or p[0] == nullid:
742 742 b.append((t, n, p[0], p[1]))
743 743 break
744 744 n = p[0]
745 745 return b
746 746
747 747 def between(self, pairs):
748 748 r = []
749 749
750 750 for top, bottom in pairs:
751 751 n, l, i = top, [], 0
752 752 f = 1
753 753
754 754 while n != bottom:
755 755 p = self.changelog.parents(n)[0]
756 756 if i == f:
757 757 l.append(n)
758 758 f = f * 2
759 759 n = p
760 760 i += 1
761 761
762 762 r.append(l)
763 763
764 764 return r
765 765
766 766 def newer(self, nodes):
767 767 m = {}
768 768 nl = []
769 769 pm = {}
770 770 cl = self.changelog
771 771 t = l = cl.count()
772 772
773 773 # find the lowest numbered node
774 774 for n in nodes:
775 775 l = min(l, cl.rev(n))
776 776 m[n] = 1
777 777
778 778 for i in xrange(l, t):
779 779 n = cl.node(i)
780 780 if n in m: # explicitly listed
781 781 pm[n] = 1
782 782 nl.append(n)
783 783 continue
784 784 for p in cl.parents(n):
785 785 if p in pm: # parent listed
786 786 pm[n] = 1
787 787 nl.append(n)
788 788 break
789 789
790 790 return nl
791 791
792 792 def getchangegroup(self, remote):
793 793 m = self.changelog.nodemap
794 794 search = []
795 795 fetch = []
796 796 seen = {}
797 797 seenbranch = {}
798 798
799 799 # if we have an empty repo, fetch everything
800 800 if self.changelog.tip() == nullid:
801 801 self.ui.status("requesting all changes\n")
802 802 return remote.changegroup([nullid])
803 803
804 804 # otherwise, assume we're closer to the tip than the root
805 805 self.ui.status("searching for changes\n")
806 806 heads = remote.heads()
807 807 unknown = []
808 808 for h in heads:
809 809 if h not in m:
810 810 unknown.append(h)
811 811
812 812 if not unknown:
813 813 self.ui.status("nothing to do!\n")
814 814 return None
815 815
816 816 rep = {}
817 817 reqcnt = 0
818 818
819 819 unknown = remote.branches(unknown)
820 820 while unknown:
821 821 r = []
822 822 while unknown:
823 823 n = unknown.pop(0)
824 824 if n[0] in seen:
825 825 continue
826 826
827 827 self.ui.debug("examining %s:%s\n" % (short(n[0]), short(n[1])))
828 828 if n[0] == nullid:
829 829 break
830 830 if n in seenbranch:
831 831 self.ui.debug("branch already found\n")
832 832 continue
833 833 if n[1] and n[1] in m: # do we know the base?
834 834 self.ui.debug("found incomplete branch %s:%s\n"
835 835 % (short(n[0]), short(n[1])))
836 836 search.append(n) # schedule branch range for scanning
837 837 seenbranch[n] = 1
838 838 else:
839 839 if n[1] not in seen and n[1] not in fetch:
840 840 if n[2] in m and n[3] in m:
841 841 self.ui.debug("found new changeset %s\n" %
842 842 short(n[1]))
843 843 fetch.append(n[1]) # earliest unknown
844 844 continue
845 845
846 846 for a in n[2:4]:
847 847 if a not in rep:
848 848 r.append(a)
849 849 rep[a] = 1
850 850
851 851 seen[n[0]] = 1
852 852
853 853 if r:
854 854 reqcnt += 1
855 855 self.ui.debug("request %d: %s\n" %
856 856 (reqcnt, " ".join(map(short, r))))
857 857 for p in range(0, len(r), 10):
858 858 for b in remote.branches(r[p:p+10]):
859 859 self.ui.debug("received %s:%s\n" %
860 860 (short(b[0]), short(b[1])))
861 861 if b[0] not in m and b[0] not in seen:
862 862 unknown.append(b)
863 863
864 864 while search:
865 865 n = search.pop(0)
866 866 reqcnt += 1
867 867 l = remote.between([(n[0], n[1])])[0]
868 868 l.append(n[1])
869 869 p = n[0]
870 870 f = 1
871 871 for i in l:
872 872 self.ui.debug("narrowing %d:%d %s\n" % (f, len(l), short(i)))
873 873 if i in m:
874 874 if f <= 2:
875 875 self.ui.debug("found new branch changeset %s\n" %
876 876 short(p))
877 877 fetch.append(p)
878 878 else:
879 879 self.ui.debug("narrowed branch search to %s:%s\n"
880 880 % (short(p), short(i)))
881 881 search.append((p, i))
882 882 break
883 883 p, f = i, f * 2
884 884
885 885 for f in fetch:
886 886 if f in m:
887 887 raise "already have", short(f[:4])
888 888
889 889 self.ui.note("adding new changesets starting at " +
890 890 " ".join([short(f) for f in fetch]) + "\n")
891 891
892 892 self.ui.debug("%d total queries\n" % reqcnt)
893 893
894 894 return remote.changegroup(fetch)
895 895
896 896 def changegroup(self, basenodes):
897 897 nodes = self.newer(basenodes)
898 898
899 899 # construct the link map
900 900 linkmap = {}
901 901 for n in nodes:
902 902 linkmap[self.changelog.rev(n)] = n
903 903
904 904 # construct a list of all changed files
905 905 changed = {}
906 906 for n in nodes:
907 907 c = self.changelog.read(n)
908 908 for f in c[3]:
909 909 changed[f] = 1
910 910 changed = changed.keys()
911 911 changed.sort()
912 912
913 913 # the changegroup is changesets + manifests + all file revs
914 914 revs = [ self.changelog.rev(n) for n in nodes ]
915 915
916 916 for y in self.changelog.group(linkmap): yield y
917 917 for y in self.manifest.group(linkmap): yield y
918 918 for f in changed:
919 919 yield struct.pack(">l", len(f) + 4) + f
920 920 g = self.file(f).group(linkmap)
921 921 for y in g:
922 922 yield y
923 923
924 924 def addchangegroup(self, generator):
925 925
926 926 class genread:
927 927 def __init__(self, generator):
928 928 self.g = generator
929 929 self.buf = ""
930 930 def read(self, l):
931 931 while l > len(self.buf):
932 932 try:
933 933 self.buf += self.g.next()
934 934 except StopIteration:
935 935 break
936 936 d, self.buf = self.buf[:l], self.buf[l:]
937 937 return d
938 938
939 939 def getchunk():
940 940 d = source.read(4)
941 941 if not d: return ""
942 942 l = struct.unpack(">l", d)[0]
943 943 if l <= 4: return ""
944 944 return source.read(l - 4)
945 945
946 946 def getgroup():
947 947 while 1:
948 948 c = getchunk()
949 949 if not c: break
950 950 yield c
951 951
952 952 def csmap(x):
953 953 self.ui.debug("add changeset %s\n" % short(x))
954 954 return self.changelog.count()
955 955
956 956 def revmap(x):
957 957 return self.changelog.rev(x)
958 958
959 959 if not generator: return
960 960 changesets = files = revisions = 0
961 961
962 962 source = genread(generator)
963 963 lock = self.lock()
964 964 tr = self.transaction()
965 965
966 966 # pull off the changeset group
967 967 self.ui.status("adding changesets\n")
968 968 co = self.changelog.tip()
969 969 cn = self.changelog.addgroup(getgroup(), csmap, tr, 1) # unique
970 970 changesets = self.changelog.rev(cn) - self.changelog.rev(co)
971 971
972 972 # pull off the manifest group
973 973 self.ui.status("adding manifests\n")
974 974 mm = self.manifest.tip()
975 975 mo = self.manifest.addgroup(getgroup(), revmap, tr)
976 976
977 977 # process the files
978 978 self.ui.status("adding file revisions\n")
979 979 while 1:
980 980 f = getchunk()
981 981 if not f: break
982 982 self.ui.debug("adding %s revisions\n" % f)
983 983 fl = self.file(f)
984 984 o = fl.tip()
985 985 n = fl.addgroup(getgroup(), revmap, tr)
986 986 revisions += fl.rev(n) - fl.rev(o)
987 987 files += 1
988 988
989 989 self.ui.status(("modified %d files, added %d changesets" +
990 990 " and %d new revisions\n")
991 991 % (files, changesets, revisions))
992 992
993 993 tr.close()
994 994 return
995 995
996 996 def update(self, node, allow=False, force=False):
997 997 pl = self.dirstate.parents()
998 998 if not force and pl[1] != nullid:
999 999 self.ui.warn("aborting: outstanding uncommitted merges\n")
1000 1000 return
1001 1001
1002 1002 p1, p2 = pl[0], node
1003 1003 pa = self.changelog.ancestor(p1, p2)
1004 1004 m1n = self.changelog.read(p1)[0]
1005 1005 m2n = self.changelog.read(p2)[0]
1006 1006 man = self.manifest.ancestor(m1n, m2n)
1007 1007 m1 = self.manifest.read(m1n)
1008 1008 mf1 = self.manifest.readflags(m1n)
1009 1009 m2 = self.manifest.read(m2n)
1010 1010 mf2 = self.manifest.readflags(m2n)
1011 1011 ma = self.manifest.read(man)
1012 1012 mfa = self.manifest.readflags(man)
1013 1013
1014 1014 (c, a, d, u) = self.diffdir(self.root)
1015 1015
1016 1016 # is this a jump, or a merge? i.e. is there a linear path
1017 1017 # from p1 to p2?
1018 1018 linear_path = (pa == p1 or pa == p2)
1019 1019
1020 1020 # resolve the manifest to determine which files
1021 1021 # we care about merging
1022 1022 self.ui.note("resolving manifests\n")
1023 1023 self.ui.debug(" ancestor %s local %s remote %s\n" %
1024 1024 (short(man), short(m1n), short(m2n)))
1025 1025
1026 1026 merge = {}
1027 1027 get = {}
1028 1028 remove = []
1029 1029 mark = {}
1030 1030
1031 1031 # construct a working dir manifest
1032 1032 mw = m1.copy()
1033 1033 mfw = mf1.copy()
1034 1034 for f in a + c + u:
1035 1035 mw[f] = ""
1036 1036 mfw[f] = is_exec(self.wjoin(f))
1037 1037 for f in d:
1038 1038 if f in mw: del mw[f]
1039 1039
1040 1040 # If we're jumping between revisions (as opposed to merging),
1041 1041 # and if neither the working directory nor the target rev has
1042 1042 # the file, then we need to remove it from the dirstate, to
1043 1043 # prevent the dirstate from listing the file when it is no
1044 1044 # longer in the manifest.
1045 1045 if linear_path and f not in m2:
1046 1046 self.dirstate.forget((f,))
1047 1047
1048 1048 for f, n in mw.iteritems():
1049 1049 if f in m2:
1050 1050 s = 0
1051 1051
1052 1052 # is the wfile new since m1, and match m2?
1053 1053 if n not in m1:
1054 1054 t1 = self.wfile(f).read()
1055 1055 t2 = self.file(f).revision(m2[f])
1056 1056 if cmp(t1, t2) == 0:
1057 1057 mark[f] = 1
1058 1058 n = m2[f]
1059 1059 del t1, t2
1060 1060
1061 1061 # are files different?
1062 1062 if n != m2[f]:
1063 1063 a = ma.get(f, nullid)
1064 1064 # are both different from the ancestor?
1065 1065 if n != a and m2[f] != a:
1066 1066 self.ui.debug(" %s versions differ, resolve\n" % f)
1067 1067 # merge executable bits
1068 1068 # "if we changed or they changed, change in merge"
1069 1069 a, b, c = mfa.get(f, 0), mfw[f], mf2[f]
1070 1070 mode = ((a^b) | (a^c)) ^ a
1071 1071 merge[f] = (m1.get(f, nullid), m2[f], mode)
1072 1072 s = 1
1073 1073 # are we clobbering?
1074 1074 # is remote's version newer?
1075 1075 # or are we going back in time?
1076 1076 elif force or m2[f] != a or (p2 == pa and mw[f] == m1[f]):
1077 1077 self.ui.debug(" remote %s is newer, get\n" % f)
1078 1078 get[f] = m2[f]
1079 1079 s = 1
1080 1080 else:
1081 1081 mark[f] = 1
1082 1082
1083 1083 if not s and mfw[f] != mf2[f]:
1084 1084 if force:
1085 1085 self.ui.debug(" updating permissions for %s\n" % f)
1086 1086 set_exec(self.wjoin(f), mf2[f])
1087 1087 else:
1088 1088 a, b, c = mfa.get(f, 0), mfw[f], mf2[f]
1089 1089 mode = ((a^b) | (a^c)) ^ a
1090 1090 if mode != b:
1091 1091 self.ui.debug(" updating permissions for %s\n" % f)
1092 1092 set_exec(self.wjoin(f), mode)
1093 1093 mark[f] = 1
1094 1094 del m2[f]
1095 1095 elif f in ma:
1096 1096 if not force and n != ma[f]:
1097 1097 r = ""
1098 1098 if linear_path or allow:
1099 1099 r = self.ui.prompt(
1100 1100 (" local changed %s which remote deleted\n" % f) +
1101 1101 "(k)eep or (d)elete?", "[kd]", "k")
1102 1102 if r == "d":
1103 1103 remove.append(f)
1104 1104 else:
1105 1105 self.ui.debug("other deleted %s\n" % f)
1106 1106 remove.append(f) # other deleted it
1107 1107 else:
1108 1108 if n == m1.get(f, nullid): # same as parent
1109 1109 if p2 == pa: # going backwards?
1110 1110 self.ui.debug("remote deleted %s\n" % f)
1111 1111 remove.append(f)
1112 1112 else:
1113 1113 self.ui.debug("local created %s, keeping\n" % f)
1114 1114 else:
1115 1115 self.ui.debug("working dir created %s, keeping\n" % f)
1116 1116
1117 1117 for f, n in m2.iteritems():
1118 1118 if f[0] == "/": continue
1119 1119 if not force and f in ma and n != ma[f]:
1120 1120 r = ""
1121 1121 if linear_path or allow:
1122 1122 r = self.ui.prompt(
1123 1123 ("remote changed %s which local deleted\n" % f) +
1124 1124 "(k)eep or (d)elete?", "[kd]", "k")
1125 1125 if r == "d": remove.append(f)
1126 1126 else:
1127 1127 self.ui.debug("remote created %s\n" % f)
1128 1128 get[f] = n
1129 1129
1130 1130 del mw, m1, m2, ma
1131 1131
1132 1132 if force:
1133 1133 for f in merge:
1134 1134 get[f] = merge[f][1]
1135 1135 merge = {}
1136 1136
1137 1137 if linear_path:
1138 1138 # we don't need to do any magic, just jump to the new rev
1139 1139 mode = 'n'
1140 1140 p1, p2 = p2, nullid
1141 1141 else:
1142 1142 if not allow:
1143 1143 self.ui.status("this update spans a branch" +
1144 1144 " affecting the following files:\n")
1145 1145 fl = merge.keys() + get.keys()
1146 1146 fl.sort()
1147 1147 for f in fl:
1148 1148 cf = ""
1149 1149 if f in merge: cf = " (resolve)"
1150 1150 self.ui.status(" %s%s\n" % (f, cf))
1151 1151 self.ui.warn("aborting update spanning branches!\n")
1152 1152 self.ui.status("(use update -m to perform a branch merge)\n")
1153 1153 return 1
1154 1154 # we have to remember what files we needed to get/change
1155 1155 # because any file that's different from either one of its
1156 1156 # parents must be in the changeset
1157 1157 mode = 'm'
1158 1158 self.dirstate.update(mark.keys(), "m")
1159 1159
1160 1160 self.dirstate.setparents(p1, p2)
1161 1161
1162 1162 # get the files we don't need to change
1163 1163 files = get.keys()
1164 1164 files.sort()
1165 1165 for f in files:
1166 1166 if f[0] == "/": continue
1167 1167 self.ui.note("getting %s\n" % f)
1168 1168 t = self.file(f).read(get[f])
1169 1169 try:
1170 1170 self.wfile(f, "w").write(t)
1171 1171 except IOError:
1172 1172 os.makedirs(os.path.dirname(self.wjoin(f)))
1173 1173 self.wfile(f, "w").write(t)
1174 1174 set_exec(self.wjoin(f), mf2[f])
1175 1175 self.dirstate.update([f], mode)
1176 1176
1177 1177 # merge the tricky bits
1178 1178 files = merge.keys()
1179 1179 files.sort()
1180 1180 for f in files:
1181 1181 self.ui.status("merging %s\n" % f)
1182 1182 m, o, flag = merge[f]
1183 1183 self.merge3(f, m, o)
1184 1184 set_exec(self.wjoin(f), flag)
1185 1185 self.dirstate.update([f], 'm')
1186 1186
1187 1187 for f in remove:
1188 1188 self.ui.note("removing %s\n" % f)
1189 1189 os.unlink(f)
1190 1190 if mode == 'n':
1191 1191 self.dirstate.forget(remove)
1192 1192 else:
1193 1193 self.dirstate.update(remove, 'r')
1194 1194
1195 1195 def merge3(self, fn, my, other):
1196 1196 """perform a 3-way merge in the working directory"""
1197 1197
1198 1198 def temp(prefix, node):
1199 1199 pre = "%s~%s." % (os.path.basename(fn), prefix)
1200 1200 (fd, name) = tempfile.mkstemp("", pre)
1201 f = os.fdopen(fd, "w")
1201 f = os.fdopen(fd, "wb")
1202 1202 f.write(fl.revision(node))
1203 1203 f.close()
1204 1204 return name
1205 1205
1206 1206 fl = self.file(fn)
1207 1207 base = fl.ancestor(my, other)
1208 1208 a = self.wjoin(fn)
1209 1209 b = temp("base", base)
1210 1210 c = temp("other", other)
1211 1211
1212 1212 self.ui.note("resolving %s\n" % fn)
1213 1213 self.ui.debug("file %s: other %s ancestor %s\n" %
1214 1214 (fn, short(other), short(base)))
1215 1215
1216 1216 cmd = os.environ.get("HGMERGE", "hgmerge")
1217 1217 r = os.system("%s %s %s %s" % (cmd, a, b, c))
1218 1218 if r:
1219 1219 self.ui.warn("merging %s failed!\n" % fn)
1220 1220
1221 1221 os.unlink(b)
1222 1222 os.unlink(c)
1223 1223
1224 1224 def verify(self):
1225 1225 filelinkrevs = {}
1226 1226 filenodes = {}
1227 1227 changesets = revisions = files = 0
1228 1228 errors = 0
1229 1229
1230 1230 seen = {}
1231 1231 self.ui.status("checking changesets\n")
1232 1232 for i in range(self.changelog.count()):
1233 1233 changesets += 1
1234 1234 n = self.changelog.node(i)
1235 1235 if n in seen:
1236 1236 self.ui.warn("duplicate changeset at revision %d\n" % i)
1237 1237 errors += 1
1238 1238 seen[n] = 1
1239 1239
1240 1240 for p in self.changelog.parents(n):
1241 1241 if p not in self.changelog.nodemap:
1242 1242 self.ui.warn("changeset %s has unknown parent %s\n" %
1243 1243 (short(n), short(p)))
1244 1244 errors += 1
1245 1245 try:
1246 1246 changes = self.changelog.read(n)
1247 1247 except Exception, inst:
1248 1248 self.ui.warn("unpacking changeset %s: %s\n" % (short(n), inst))
1249 1249 errors += 1
1250 1250
1251 1251 for f in changes[3]:
1252 1252 filelinkrevs.setdefault(f, []).append(i)
1253 1253
1254 1254 seen = {}
1255 1255 self.ui.status("checking manifests\n")
1256 1256 for i in range(self.manifest.count()):
1257 1257 n = self.manifest.node(i)
1258 1258 if n in seen:
1259 1259 self.ui.warn("duplicate manifest at revision %d\n" % i)
1260 1260 errors += 1
1261 1261 seen[n] = 1
1262 1262
1263 1263 for p in self.manifest.parents(n):
1264 1264 if p not in self.manifest.nodemap:
1265 1265 self.ui.warn("manifest %s has unknown parent %s\n" %
1266 1266 (short(n), short(p)))
1267 1267 errors += 1
1268 1268
1269 1269 try:
1270 1270 delta = mdiff.patchtext(self.manifest.delta(n))
1271 1271 except KeyboardInterrupt:
1272 1272 print "aborted"
1273 1273 sys.exit(0)
1274 1274 except Exception, inst:
1275 1275 self.ui.warn("unpacking manifest %s: %s\n"
1276 1276 % (short(n), inst))
1277 1277 errors += 1
1278 1278
1279 1279 ff = [ l.split('\0') for l in delta.splitlines() ]
1280 1280 for f, fn in ff:
1281 1281 filenodes.setdefault(f, {})[bin(fn[:40])] = 1
1282 1282
1283 1283 self.ui.status("crosschecking files in changesets and manifests\n")
1284 1284 for f in filenodes:
1285 1285 if f not in filelinkrevs:
1286 1286 self.ui.warn("file %s in manifest but not in changesets\n" % f)
1287 1287 errors += 1
1288 1288
1289 1289 for f in filelinkrevs:
1290 1290 if f not in filenodes:
1291 1291 self.ui.warn("file %s in changeset but not in manifest\n" % f)
1292 1292 errors += 1
1293 1293
1294 1294 self.ui.status("checking files\n")
1295 1295 ff = filenodes.keys()
1296 1296 ff.sort()
1297 1297 for f in ff:
1298 1298 if f == "/dev/null": continue
1299 1299 files += 1
1300 1300 fl = self.file(f)
1301 1301 nodes = { nullid: 1 }
1302 1302 seen = {}
1303 1303 for i in range(fl.count()):
1304 1304 revisions += 1
1305 1305 n = fl.node(i)
1306 1306
1307 1307 if n in seen:
1308 1308 self.ui.warn("%s: duplicate revision %d\n" % (f, i))
1309 1309 errors += 1
1310 1310
1311 1311 if n not in filenodes[f]:
1312 1312 self.ui.warn("%s: %d:%s not in manifests\n"
1313 1313 % (f, i, short(n)))
1314 1314 print len(filenodes[f].keys()), fl.count(), f
1315 1315 errors += 1
1316 1316 else:
1317 1317 del filenodes[f][n]
1318 1318
1319 1319 flr = fl.linkrev(n)
1320 1320 if flr not in filelinkrevs[f]:
1321 1321 self.ui.warn("%s:%s points to unexpected changeset %d\n"
1322 1322 % (f, short(n), fl.linkrev(n)))
1323 1323 errors += 1
1324 1324 else:
1325 1325 filelinkrevs[f].remove(flr)
1326 1326
1327 1327 # verify contents
1328 1328 try:
1329 1329 t = fl.read(n)
1330 1330 except Exception, inst:
1331 1331 self.ui.warn("unpacking file %s %s: %s\n"
1332 1332 % (f, short(n), inst))
1333 1333 errors += 1
1334 1334
1335 1335 # verify parents
1336 1336 (p1, p2) = fl.parents(n)
1337 1337 if p1 not in nodes:
1338 1338 self.ui.warn("file %s:%s unknown parent 1 %s" %
1339 1339 (f, short(n), short(p1)))
1340 1340 errors += 1
1341 1341 if p2 not in nodes:
1342 1342 self.ui.warn("file %s:%s unknown parent 2 %s" %
1343 1343 (f, short(n), short(p1)))
1344 1344 errors += 1
1345 1345 nodes[n] = 1
1346 1346
1347 1347 # cross-check
1348 1348 for node in filenodes[f]:
1349 1349 self.ui.warn("node %s in manifests not in %s\n"
1350 1350 % (hex(n), f))
1351 1351 errors += 1
1352 1352
1353 1353 self.ui.status("%d files, %d changesets, %d total revisions\n" %
1354 1354 (files, changesets, revisions))
1355 1355
1356 1356 if errors:
1357 1357 self.ui.warn("%d integrity errors encountered!\n" % errors)
1358 1358 return 1
1359 1359
1360 1360 class remoterepository:
1361 1361 def __init__(self, ui, path):
1362 1362 self.url = path
1363 1363 self.ui = ui
1364 1364 no_list = [ "localhost", "127.0.0.1" ]
1365 1365 host = ui.config("http_proxy", "host")
1366 1366 user = ui.config("http_proxy", "user")
1367 1367 passwd = ui.config("http_proxy", "passwd")
1368 1368 no = ui.config("http_proxy", "no")
1369 1369 if no:
1370 1370 no_list = no_list + no.split(",")
1371 1371
1372 1372 no_proxy = 0
1373 1373 for h in no_list:
1374 1374 if (path.startswith("http://" + h + "/") or
1375 1375 path.startswith("http://" + h + ":") or
1376 1376 path == "http://" + h):
1377 1377 no_proxy = 1
1378 1378
1379 1379 # Note: urllib2 takes proxy values from the environment and those will
1380 1380 # take precedence
1381 1381
1382 1382 proxy_handler = urllib2.BaseHandler()
1383 1383 if host and not no_proxy:
1384 1384 proxy_handler = urllib2.ProxyHandler({"http" : "http://" + host})
1385 1385
1386 1386 authinfo = None
1387 1387 if user and passwd:
1388 1388 passmgr = urllib2.HTTPPasswordMgrWithDefaultRealm()
1389 1389 passmgr.add_password(None, host, user, passwd)
1390 1390 authinfo = urllib2.ProxyBasicAuthHandler(passmgr)
1391 1391
1392 1392 opener = urllib2.build_opener(proxy_handler, authinfo)
1393 1393 urllib2.install_opener(opener)
1394 1394
1395 1395 def do_cmd(self, cmd, **args):
1396 1396 self.ui.debug("sending %s command\n" % cmd)
1397 1397 q = {"cmd": cmd}
1398 1398 q.update(args)
1399 1399 qs = urllib.urlencode(q)
1400 1400 cu = "%s?%s" % (self.url, qs)
1401 1401 return urllib2.urlopen(cu)
1402 1402
1403 1403 def heads(self):
1404 1404 d = self.do_cmd("heads").read()
1405 1405 try:
1406 1406 return map(bin, d[:-1].split(" "))
1407 1407 except:
1408 1408 self.ui.warn("unexpected response:\n" + d[:400] + "\n...\n")
1409 1409 raise
1410 1410
1411 1411 def branches(self, nodes):
1412 1412 n = " ".join(map(hex, nodes))
1413 1413 d = self.do_cmd("branches", nodes=n).read()
1414 1414 try:
1415 1415 br = [ tuple(map(bin, b.split(" "))) for b in d.splitlines() ]
1416 1416 return br
1417 1417 except:
1418 1418 self.ui.warn("unexpected response:\n" + d[:400] + "\n...\n")
1419 1419 raise
1420 1420
1421 1421 def between(self, pairs):
1422 1422 n = "\n".join(["-".join(map(hex, p)) for p in pairs])
1423 1423 d = self.do_cmd("between", pairs=n).read()
1424 1424 try:
1425 1425 p = [ l and map(bin, l.split(" ")) or [] for l in d.splitlines() ]
1426 1426 return p
1427 1427 except:
1428 1428 self.ui.warn("unexpected response:\n" + d[:400] + "\n...\n")
1429 1429 raise
1430 1430
1431 1431 def changegroup(self, nodes):
1432 1432 n = " ".join(map(hex, nodes))
1433 1433 zd = zlib.decompressobj()
1434 1434 f = self.do_cmd("changegroup", roots=n)
1435 1435 bytes = 0
1436 1436 while 1:
1437 1437 d = f.read(4096)
1438 1438 bytes += len(d)
1439 1439 if not d:
1440 1440 yield zd.flush()
1441 1441 break
1442 1442 yield zd.decompress(d)
1443 1443 self.ui.note("%d bytes of data transfered\n" % bytes)
1444 1444
1445 1445 def repository(ui, path=None, create=0):
1446 1446 if path and path[:7] == "http://":
1447 1447 return remoterepository(ui, path)
1448 1448 if path and path[:5] == "hg://":
1449 1449 return remoterepository(ui, path.replace("hg://", "http://"))
1450 1450 if path and path[:11] == "old-http://":
1451 1451 return localrepository(ui, path.replace("old-http://", "http://"))
1452 1452 else:
1453 1453 return localrepository(ui, path, create)
1454 1454
General Comments 0
You need to be logged in to leave comments. Login now