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