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