##// END OF EJS Templates
Add searching for named branches...
mason@suse.com -
r898:3616c0d7 default
parent child Browse files
Show More
@@ -1,1506 +1,1533
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 from demandload import demandload
9 9 demandload(globals(), "os re sys signal shutil")
10 10 demandload(globals(), "fancyopts ui hg util")
11 11 demandload(globals(), "fnmatch hgweb mdiff random signal time traceback")
12 12 demandload(globals(), "errno socket version struct atexit")
13 13
14 14 class UnknownCommand(Exception):
15 15 """Exception raised if command is not in the command table."""
16 16
17 17 def filterfiles(filters, files):
18 18 l = [x for x in files if x in filters]
19 19
20 20 for t in filters:
21 21 if t and t[-1] != "/":
22 22 t += "/"
23 23 l += [x for x in files if x.startswith(t)]
24 24 return l
25 25
26 26 def relfilter(repo, files):
27 27 cwd = repo.getcwd()
28 28 if cwd:
29 29 return filterfiles([util.pconvert(cwd)], files)
30 30 return files
31 31
32 32 def relpath(repo, args):
33 33 cwd = repo.getcwd()
34 34 if cwd:
35 35 return [util.normpath(os.path.join(cwd, x)) for x in args]
36 36 return args
37 37
38 38 def matchpats(repo, cwd, pats = [], opts = {}, head = ''):
39 39 return util.matcher(repo, cwd, pats or ['.'], opts.get('include'),
40 40 opts.get('exclude'), head)
41 41
42 42 def makewalk(repo, pats, opts, head = ''):
43 43 cwd = repo.getcwd()
44 44 files, matchfn = matchpats(repo, cwd, pats, opts, head)
45 45 def walk():
46 46 for src, fn in repo.walk(files = files, match = matchfn):
47 47 yield src, fn, util.pathto(cwd, fn)
48 48 return files, matchfn, walk()
49 49
50 50 def walk(repo, pats, opts, head = ''):
51 51 files, matchfn, results = makewalk(repo, pats, opts, head)
52 52 for r in results: yield r
53 53
54 54 revrangesep = ':'
55 55
56 56 def revrange(ui, repo, revs, revlog=None):
57 57 if revlog is None:
58 58 revlog = repo.changelog
59 59 revcount = revlog.count()
60 60 def fix(val, defval):
61 61 if not val:
62 62 return defval
63 63 try:
64 64 num = int(val)
65 65 if str(num) != val:
66 66 raise ValueError
67 67 if num < 0:
68 68 num += revcount
69 69 if not (0 <= num < revcount):
70 70 raise ValueError
71 71 except ValueError:
72 72 try:
73 73 num = repo.changelog.rev(repo.lookup(val))
74 74 except KeyError:
75 75 try:
76 76 num = revlog.rev(revlog.lookup(val))
77 77 except KeyError:
78 78 raise util.Abort('invalid revision identifier %s', val)
79 79 return num
80 80 for spec in revs:
81 81 if spec.find(revrangesep) >= 0:
82 82 start, end = spec.split(revrangesep, 1)
83 83 start = fix(start, 0)
84 84 end = fix(end, revcount - 1)
85 85 if end > start:
86 86 end += 1
87 87 step = 1
88 88 else:
89 89 end -= 1
90 90 step = -1
91 91 for rev in xrange(start, end, step):
92 92 yield str(rev)
93 93 else:
94 94 yield spec
95 95
96 96 def make_filename(repo, r, pat, node=None,
97 97 total=None, seqno=None, revwidth=None):
98 98 node_expander = {
99 99 'H': lambda: hg.hex(node),
100 100 'R': lambda: str(r.rev(node)),
101 101 'h': lambda: hg.short(node),
102 102 }
103 103 expander = {
104 104 '%': lambda: '%',
105 105 'b': lambda: os.path.basename(repo.root),
106 106 }
107 107
108 108 try:
109 109 if node:
110 110 expander.update(node_expander)
111 111 if node and revwidth is not None:
112 112 expander['r'] = lambda: str(r.rev(node)).zfill(revwidth)
113 113 if total is not None:
114 114 expander['N'] = lambda: str(total)
115 115 if seqno is not None:
116 116 expander['n'] = lambda: str(seqno)
117 117 if total is not None and seqno is not None:
118 118 expander['n'] = lambda:str(seqno).zfill(len(str(total)))
119 119
120 120 newname = []
121 121 patlen = len(pat)
122 122 i = 0
123 123 while i < patlen:
124 124 c = pat[i]
125 125 if c == '%':
126 126 i += 1
127 127 c = pat[i]
128 128 c = expander[c]()
129 129 newname.append(c)
130 130 i += 1
131 131 return ''.join(newname)
132 132 except KeyError, inst:
133 133 raise util.Abort("invalid format spec '%%%s' in output file name",
134 134 inst.args[0])
135 135
136 136 def make_file(repo, r, pat, node=None,
137 137 total=None, seqno=None, revwidth=None, mode='wb'):
138 138 if not pat or pat == '-':
139 139 if 'w' in mode: return sys.stdout
140 140 else: return sys.stdin
141 141 if hasattr(pat, 'write') and 'w' in mode:
142 142 return pat
143 143 if hasattr(pat, 'read') and 'r' in mode:
144 144 return pat
145 145 return open(make_filename(repo, r, pat, node, total, seqno, revwidth),
146 146 mode)
147 147
148 148 def dodiff(fp, ui, repo, files=None, node1=None, node2=None, match=util.always):
149 149 def date(c):
150 150 return time.asctime(time.gmtime(float(c[2].split(' ')[0])))
151 151
152 152 (c, a, d, u) = repo.changes(node1, node2, files, match = match)
153 153 if files:
154 154 c, a, d = map(lambda x: filterfiles(files, x), (c, a, d))
155 155
156 156 if not c and not a and not d:
157 157 return
158 158
159 159 if node2:
160 160 change = repo.changelog.read(node2)
161 161 mmap2 = repo.manifest.read(change[0])
162 162 date2 = date(change)
163 163 def read(f):
164 164 return repo.file(f).read(mmap2[f])
165 165 else:
166 166 date2 = time.asctime()
167 167 if not node1:
168 168 node1 = repo.dirstate.parents()[0]
169 169 def read(f):
170 170 return repo.wfile(f).read()
171 171
172 172 if ui.quiet:
173 173 r = None
174 174 else:
175 175 hexfunc = ui.verbose and hg.hex or hg.short
176 176 r = [hexfunc(node) for node in [node1, node2] if node]
177 177
178 178 change = repo.changelog.read(node1)
179 179 mmap = repo.manifest.read(change[0])
180 180 date1 = date(change)
181 181
182 182 for f in c:
183 183 to = None
184 184 if f in mmap:
185 185 to = repo.file(f).read(mmap[f])
186 186 tn = read(f)
187 187 fp.write(mdiff.unidiff(to, date1, tn, date2, f, r))
188 188 for f in a:
189 189 to = None
190 190 tn = read(f)
191 191 fp.write(mdiff.unidiff(to, date1, tn, date2, f, r))
192 192 for f in d:
193 193 to = repo.file(f).read(mmap[f])
194 194 tn = None
195 195 fp.write(mdiff.unidiff(to, date1, tn, date2, f, r))
196 196
197 def show_changeset(ui, repo, rev=0, changenode=None, filelog=None):
197 def show_changeset(ui, repo, rev=0, changenode=None, filelog=None, brinfo=None):
198 198 """show a single changeset or file revision"""
199 199 changelog = repo.changelog
200 200 if filelog:
201 201 log = filelog
202 202 filerev = rev
203 203 node = filenode = filelog.node(filerev)
204 204 changerev = filelog.linkrev(filenode)
205 205 changenode = changenode or changelog.node(changerev)
206 206 else:
207 207 log = changelog
208 208 changerev = rev
209 209 if changenode is None:
210 210 changenode = changelog.node(changerev)
211 211 elif not changerev:
212 212 rev = changerev = changelog.rev(changenode)
213 213 node = changenode
214 214
215 215 if ui.quiet:
216 216 ui.write("%d:%s\n" % (rev, hg.hex(node)))
217 217 return
218 218
219 219 changes = changelog.read(changenode)
220 220
221 221 parents = [(log.rev(p), ui.verbose and hg.hex(p) or hg.short(p))
222 222 for p in log.parents(node)
223 223 if ui.debugflag or p != hg.nullid]
224 224 if not ui.debugflag and len(parents) == 1 and parents[0][0] == rev-1:
225 225 parents = []
226 226
227 227 if ui.verbose:
228 228 ui.write("changeset: %d:%s\n" % (changerev, hg.hex(changenode)))
229 229 else:
230 230 ui.write("changeset: %d:%s\n" % (changerev, hg.short(changenode)))
231 231
232 232 for tag in repo.nodetags(changenode):
233 233 ui.status("tag: %s\n" % tag)
234 234 for parent in parents:
235 235 ui.write("parent: %d:%s\n" % parent)
236 236 if filelog:
237 237 ui.debug("file rev: %d:%s\n" % (filerev, hg.hex(filenode)))
238 238
239 if brinfo and changenode in brinfo:
240 br = brinfo[changenode]
241 ui.write("branch: %s\n" % " ".join(br))
242
239 243 ui.debug("manifest: %d:%s\n" % (repo.manifest.rev(changes[0]),
240 244 hg.hex(changes[0])))
241 245 ui.status("user: %s\n" % changes[1])
242 246 ui.status("date: %s\n" % time.asctime(
243 247 time.localtime(float(changes[2].split(' ')[0]))))
244 248
245 249 if ui.debugflag:
246 250 files = repo.changes(changelog.parents(changenode)[0], changenode)
247 251 for key, value in zip(["files:", "files+:", "files-:"], files):
248 252 if value:
249 253 ui.note("%-12s %s\n" % (key, " ".join(value)))
250 254 else:
251 255 ui.note("files: %s\n" % " ".join(changes[3]))
252 256
253 257 description = changes[4].strip()
254 258 if description:
255 259 if ui.verbose:
256 260 ui.status("description:\n")
257 261 ui.status(description)
258 262 ui.status("\n\n")
259 263 else:
260 264 ui.status("summary: %s\n" % description.splitlines()[0])
261 265 ui.status("\n")
262 266
263 267 def show_version(ui):
264 268 """output version and copyright information"""
265 269 ui.write("Mercurial Distributed SCM (version %s)\n"
266 270 % version.get_version())
267 271 ui.status(
268 272 "\nCopyright (C) 2005 Matt Mackall <mpm@selenic.com>\n"
269 273 "This is free software; see the source for copying conditions. "
270 274 "There is NO\nwarranty; "
271 275 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
272 276 )
273 277
274 278 def help_(ui, cmd=None):
275 279 """show help for a given command or all commands"""
276 280 if cmd and cmd != 'shortlist':
277 281 key, i = find(cmd)
278 282 # synopsis
279 283 ui.write("%s\n\n" % i[2])
280 284
281 285 # description
282 286 doc = i[0].__doc__
283 287 if ui.quiet:
284 288 doc = doc.splitlines(0)[0]
285 289 ui.write("%s\n" % doc.rstrip())
286 290
287 291 # aliases
288 292 if not ui.quiet:
289 293 aliases = ', '.join(key.split('|')[1:])
290 294 if aliases:
291 295 ui.write("\naliases: %s\n" % aliases)
292 296
293 297 # options
294 298 if not ui.quiet and i[1]:
295 299 ui.write("\noptions:\n\n")
296 300 for s, l, d, c in i[1]:
297 301 opt = ' '
298 302 if s:
299 303 opt = opt + '-' + s + ' '
300 304 if l:
301 305 opt = opt + '--' + l + ' '
302 306 if d:
303 307 opt = opt + '(' + str(d) + ')'
304 308 ui.write(opt, "\n")
305 309 if c:
306 310 ui.write(' %s\n' % c)
307 311
308 312 else:
309 313 # program name
310 314 if ui.verbose:
311 315 show_version(ui)
312 316 else:
313 317 ui.status("Mercurial Distributed SCM\n")
314 318 ui.status('\n')
315 319
316 320 # list of commands
317 321 if cmd == "shortlist":
318 322 ui.status('basic commands (use "hg help" '
319 323 'for the full list or option "-v" for details):\n\n')
320 324 elif ui.verbose:
321 325 ui.status('list of commands:\n\n')
322 326 else:
323 327 ui.status('list of commands (use "hg help -v" '
324 328 'to show aliases and global options):\n\n')
325 329
326 330 h = {}
327 331 cmds = {}
328 332 for c, e in table.items():
329 333 f = c.split("|")[0]
330 334 if cmd == "shortlist" and not f.startswith("^"):
331 335 continue
332 336 f = f.lstrip("^")
333 337 if not ui.debugflag and f.startswith("debug"):
334 338 continue
335 339 d = ""
336 340 if e[0].__doc__:
337 341 d = e[0].__doc__.splitlines(0)[0].rstrip()
338 342 h[f] = d
339 343 cmds[f]=c.lstrip("^")
340 344
341 345 fns = h.keys()
342 346 fns.sort()
343 347 m = max(map(len, fns))
344 348 for f in fns:
345 349 if ui.verbose:
346 350 commands = cmds[f].replace("|",", ")
347 351 ui.write(" %s:\n %s\n"%(commands,h[f]))
348 352 else:
349 353 ui.write(' %-*s %s\n' % (m, f, h[f]))
350 354
351 355 # global options
352 356 if ui.verbose:
353 357 ui.write("\nglobal options:\n\n")
354 358 for s, l, d, c in globalopts:
355 359 opt = ' '
356 360 if s:
357 361 opt = opt + '-' + s + ' '
358 362 if l:
359 363 opt = opt + '--' + l + ' '
360 364 if d:
361 365 opt = opt + '(' + str(d) + ')'
362 366 ui.write(opt, "\n")
363 367 if c:
364 368 ui.write(' %s\n' % c)
365 369
366 370 # Commands start here, listed alphabetically
367 371
368 372 def add(ui, repo, *pats, **opts):
369 373 '''add the specified files on the next commit'''
370 374 names = []
371 375 q = dict(zip(pats, pats))
372 376 for src, abs, rel in walk(repo, pats, opts):
373 377 if rel in q or abs in q:
374 378 names.append(abs)
375 379 elif repo.dirstate.state(abs) == '?':
376 380 ui.status('adding %s\n' % rel)
377 381 names.append(abs)
378 382 repo.add(names)
379 383
380 384 def addremove(ui, repo, *pats, **opts):
381 385 """add all new files, delete all missing files"""
382 386 q = dict(zip(pats, pats))
383 387 add, remove = [], []
384 388 for src, abs, rel in walk(repo, pats, opts):
385 389 if src == 'f' and repo.dirstate.state(abs) == '?':
386 390 add.append(abs)
387 391 if rel not in q: ui.status('adding ', rel, '\n')
388 392 if repo.dirstate.state(abs) != 'r' and not os.path.exists(rel):
389 393 remove.append(abs)
390 394 if rel not in q: ui.status('removing ', rel, '\n')
391 395 repo.add(add)
392 396 repo.remove(remove)
393 397
394 398 def annotate(ui, repo, *pats, **opts):
395 399 """show changeset information per file line"""
396 400 def getnode(rev):
397 401 return hg.short(repo.changelog.node(rev))
398 402
399 403 def getname(rev):
400 404 try:
401 405 return bcache[rev]
402 406 except KeyError:
403 407 cl = repo.changelog.read(repo.changelog.node(rev))
404 408 name = cl[1]
405 409 f = name.find('@')
406 410 if f >= 0:
407 411 name = name[:f]
408 412 f = name.find('<')
409 413 if f >= 0:
410 414 name = name[f+1:]
411 415 bcache[rev] = name
412 416 return name
413 417
414 418 if not pats:
415 419 raise util.Abort('at least one file name or pattern required')
416 420
417 421 bcache = {}
418 422 opmap = [['user', getname], ['number', str], ['changeset', getnode]]
419 423 if not opts['user'] and not opts['changeset']:
420 424 opts['number'] = 1
421 425
422 426 if opts['rev']:
423 427 node = repo.changelog.lookup(opts['rev'])
424 428 else:
425 429 node = repo.dirstate.parents()[0]
426 430 change = repo.changelog.read(node)
427 431 mmap = repo.manifest.read(change[0])
428 432 for src, abs, rel in walk(repo, pats, opts):
429 433 if abs not in mmap:
430 434 ui.warn("warning: %s is not in the repository!\n" % rel)
431 435 continue
432 436
433 437 lines = repo.file(abs).annotate(mmap[abs])
434 438 pieces = []
435 439
436 440 for o, f in opmap:
437 441 if opts[o]:
438 442 l = [f(n) for n, dummy in lines]
439 443 if l:
440 444 m = max(map(len, l))
441 445 pieces.append(["%*s" % (m, x) for x in l])
442 446
443 447 if pieces:
444 448 for p, l in zip(zip(*pieces), lines):
445 449 ui.write("%s: %s" % (" ".join(p), l[1]))
446 450
447 451 def cat(ui, repo, file1, rev=None, **opts):
448 452 """output the latest or given revision of a file"""
449 453 r = repo.file(relpath(repo, [file1])[0])
450 454 if rev:
451 455 n = r.lookup(rev)
452 456 else:
453 457 n = r.tip()
454 458 fp = make_file(repo, r, opts['output'], node=n)
455 459 fp.write(r.read(n))
456 460
457 461 def clone(ui, source, dest=None, **opts):
458 462 """make a copy of an existing repository"""
459 463 if dest is None:
460 464 dest = os.path.basename(os.path.normpath(source))
461 465
462 466 if os.path.exists(dest):
463 467 ui.warn("abort: destination '%s' already exists\n" % dest)
464 468 return 1
465 469
466 470 dest = os.path.realpath(dest)
467 471
468 472 class Dircleanup:
469 473 def __init__(self, dir_):
470 474 self.rmtree = shutil.rmtree
471 475 self.dir_ = dir_
472 476 os.mkdir(dir_)
473 477 def close(self):
474 478 self.dir_ = None
475 479 def __del__(self):
476 480 if self.dir_:
477 481 self.rmtree(self.dir_, True)
478 482
479 483 d = Dircleanup(dest)
480 484 abspath = source
481 485 source = ui.expandpath(source)
482 486 other = hg.repository(ui, source)
483 487
484 488 if other.dev() != -1:
485 489 abspath = os.path.abspath(source)
486 490 copyfile = (os.stat(dest).st_dev == other.dev()
487 491 and getattr(os, 'link', None) or shutil.copy2)
488 492 if copyfile is not shutil.copy2:
489 493 ui.note("cloning by hardlink\n")
490 494 util.copytree(os.path.join(source, ".hg"), os.path.join(dest, ".hg"),
491 495 copyfile)
492 496 try:
493 497 os.unlink(os.path.join(dest, ".hg", "dirstate"))
494 498 except OSError:
495 499 pass
496 500
497 501 repo = hg.repository(ui, dest)
498 502
499 503 else:
500 504 repo = hg.repository(ui, dest, create=1)
501 505 repo.pull(other)
502 506
503 507 f = repo.opener("hgrc", "w")
504 508 f.write("[paths]\n")
505 509 f.write("default = %s\n" % abspath)
506 510
507 511 if not opts['noupdate']:
508 512 update(ui, repo)
509 513
510 514 d.close()
511 515
512 516 def commit(ui, repo, *pats, **opts):
513 517 """commit the specified files or all outstanding changes"""
514 518 if opts['text']:
515 519 ui.warn("Warning: -t and --text is deprecated,"
516 520 " please use -m or --message instead.\n")
517 521 message = opts['message'] or opts['text']
518 522 logfile = opts['logfile']
519 523 if not message and logfile:
520 524 try:
521 525 message = open(logfile).read()
522 526 except IOError, why:
523 527 ui.warn("Can't read commit message %s: %s\n" % (logfile, why))
524 528
525 529 if opts['addremove']:
526 530 addremove(ui, repo, *pats, **opts)
527 531 cwd = repo.getcwd()
528 532 if not pats and cwd:
529 533 opts['include'] = [os.path.join(cwd, i) for i in opts['include']]
530 534 opts['exclude'] = [os.path.join(cwd, x) for x in opts['exclude']]
531 535 fns, match = matchpats(repo, (pats and repo.getcwd()) or '', pats, opts)
532 536 if pats:
533 537 c, a, d, u = repo.changes(files = fns, match = match)
534 538 files = c + a + [fn for fn in d if repo.dirstate.state(fn) == 'r']
535 539 else:
536 540 files = []
537 541 repo.commit(files, message, opts['user'], opts['date'], match)
538 542
539 543 def copy(ui, repo, source, dest):
540 544 """mark a file as copied or renamed for the next commit"""
541 545 return repo.copy(*relpath(repo, (source, dest)))
542 546
543 547 def debugcheckstate(ui, repo):
544 548 """validate the correctness of the current dirstate"""
545 549 parent1, parent2 = repo.dirstate.parents()
546 550 repo.dirstate.read()
547 551 dc = repo.dirstate.map
548 552 keys = dc.keys()
549 553 keys.sort()
550 554 m1n = repo.changelog.read(parent1)[0]
551 555 m2n = repo.changelog.read(parent2)[0]
552 556 m1 = repo.manifest.read(m1n)
553 557 m2 = repo.manifest.read(m2n)
554 558 errors = 0
555 559 for f in dc:
556 560 state = repo.dirstate.state(f)
557 561 if state in "nr" and f not in m1:
558 562 ui.warn("%s in state %s, but not in manifest1\n" % (f, state))
559 563 errors += 1
560 564 if state in "a" and f in m1:
561 565 ui.warn("%s in state %s, but also in manifest1\n" % (f, state))
562 566 errors += 1
563 567 if state in "m" and f not in m1 and f not in m2:
564 568 ui.warn("%s in state %s, but not in either manifest\n" %
565 569 (f, state))
566 570 errors += 1
567 571 for f in m1:
568 572 state = repo.dirstate.state(f)
569 573 if state not in "nrm":
570 574 ui.warn("%s in manifest1, but listed as state %s" % (f, state))
571 575 errors += 1
572 576 if errors:
573 577 raise util.Abort(".hg/dirstate inconsistent with current parent's manifest")
574 578
575 579 def debugstate(ui, repo):
576 580 """show the contents of the current dirstate"""
577 581 repo.dirstate.read()
578 582 dc = repo.dirstate.map
579 583 keys = dc.keys()
580 584 keys.sort()
581 585 for file_ in keys:
582 586 ui.write("%c %3o %10d %s %s\n"
583 587 % (dc[file_][0], dc[file_][1] & 0777, dc[file_][2],
584 588 time.strftime("%x %X",
585 589 time.localtime(dc[file_][3])), file_))
586 590
587 591 def debugindex(ui, file_):
588 592 """dump the contents of an index file"""
589 593 r = hg.revlog(hg.opener(""), file_, "")
590 594 ui.write(" rev offset length base linkrev" +
591 595 " p1 p2 nodeid\n")
592 596 for i in range(r.count()):
593 597 e = r.index[i]
594 598 ui.write("% 6d % 9d % 7d % 6d % 7d %s.. %s.. %s..\n" % (
595 599 i, e[0], e[1], e[2], e[3],
596 600 hg.hex(e[4][:5]), hg.hex(e[5][:5]), hg.hex(e[6][:5])))
597 601
598 602 def debugindexdot(ui, file_):
599 603 """dump an index DAG as a .dot file"""
600 604 r = hg.revlog(hg.opener(""), file_, "")
601 605 ui.write("digraph G {\n")
602 606 for i in range(r.count()):
603 607 e = r.index[i]
604 608 ui.write("\t%d -> %d\n" % (r.rev(e[4]), i))
605 609 if e[5] != hg.nullid:
606 610 ui.write("\t%d -> %d\n" % (r.rev(e[5]), i))
607 611 ui.write("}\n")
608 612
609 613 def debugwalk(ui, repo, *pats, **opts):
610 614 items = list(walk(repo, pats, opts))
611 615 if not items: return
612 616 fmt = '%%s %%-%ds %%s' % max([len(abs) for (src, abs, rel) in items])
613 617 for i in items: print fmt % i
614 618
615 619 def diff(ui, repo, *pats, **opts):
616 620 """diff working directory (or selected files)"""
617 621 revs = []
618 622 if opts['rev']:
619 623 revs = map(lambda x: repo.lookup(x), opts['rev'])
620 624
621 625 if len(revs) > 2:
622 626 raise util.Abort("too many revisions to diff")
623 627
624 628 files = []
625 629 match = util.always
626 630 if pats:
627 631 roots, match, results = makewalk(repo, pats, opts)
628 632 for src, abs, rel in results:
629 633 files.append(abs)
630 634 dodiff(sys.stdout, ui, repo, files, *revs, **{'match': match})
631 635
632 636 def doexport(ui, repo, changeset, seqno, total, revwidth, opts):
633 637 node = repo.lookup(changeset)
634 638 prev, other = repo.changelog.parents(node)
635 639 change = repo.changelog.read(node)
636 640
637 641 fp = make_file(repo, repo.changelog, opts['output'],
638 642 node=node, total=total, seqno=seqno,
639 643 revwidth=revwidth)
640 644 if fp != sys.stdout:
641 645 ui.note("%s\n" % fp.name)
642 646
643 647 fp.write("# HG changeset patch\n")
644 648 fp.write("# User %s\n" % change[1])
645 649 fp.write("# Node ID %s\n" % hg.hex(node))
646 650 fp.write("# Parent %s\n" % hg.hex(prev))
647 651 if other != hg.nullid:
648 652 fp.write("# Parent %s\n" % hg.hex(other))
649 653 fp.write(change[4].rstrip())
650 654 fp.write("\n\n")
651 655
652 656 dodiff(fp, ui, repo, None, prev, node)
653 657 if fp != sys.stdout: fp.close()
654 658
655 659 def export(ui, repo, *changesets, **opts):
656 660 """dump the header and diffs for one or more changesets"""
657 661 if not changesets:
658 662 raise util.Abort("export requires at least one changeset")
659 663 seqno = 0
660 664 revs = list(revrange(ui, repo, changesets))
661 665 total = len(revs)
662 666 revwidth = max(len(revs[0]), len(revs[-1]))
663 667 ui.note(len(revs) > 1 and "Exporting patches:\n" or "Exporting patch:\n")
664 668 for cset in revs:
665 669 seqno += 1
666 670 doexport(ui, repo, cset, seqno, total, revwidth, opts)
667 671
668 672 def forget(ui, repo, *pats, **opts):
669 673 """don't add the specified files on the next commit"""
670 674 q = dict(zip(pats, pats))
671 675 forget = []
672 676 for src, abs, rel in walk(repo, pats, opts):
673 677 if repo.dirstate.state(abs) == 'a':
674 678 forget.append(abs)
675 679 if rel not in q: ui.status('forgetting ', rel, '\n')
676 680 repo.forget(forget)
677 681
678 def heads(ui, repo):
682 def heads(ui, repo, **opts):
679 683 """show current repository heads"""
684 heads = repo.changelog.heads()
685 br = None
686 if opts['branches']:
687 br = repo.branchlookup(heads)
680 688 for n in repo.changelog.heads():
681 show_changeset(ui, repo, changenode=n)
689 show_changeset(ui, repo, changenode=n, brinfo=br)
682 690
683 691 def identify(ui, repo):
684 692 """print information about the working copy"""
685 693 parents = [p for p in repo.dirstate.parents() if p != hg.nullid]
686 694 if not parents:
687 695 ui.write("unknown\n")
688 696 return
689 697
690 698 hexfunc = ui.verbose and hg.hex or hg.short
691 699 (c, a, d, u) = repo.changes()
692 700 output = ["%s%s" % ('+'.join([hexfunc(parent) for parent in parents]),
693 701 (c or a or d) and "+" or "")]
694 702
695 703 if not ui.quiet:
696 704 # multiple tags for a single parent separated by '/'
697 705 parenttags = ['/'.join(tags)
698 706 for tags in map(repo.nodetags, parents) if tags]
699 707 # tags for multiple parents separated by ' + '
700 708 if parenttags:
701 709 output.append(' + '.join(parenttags))
702 710
703 711 ui.write("%s\n" % ' '.join(output))
704 712
705 713 def import_(ui, repo, patch1, *patches, **opts):
706 714 """import an ordered set of patches"""
707 715 patches = (patch1,) + patches
708 716
709 717 d = opts["base"]
710 718 strip = opts["strip"]
711 719
712 720 for patch in patches:
713 721 ui.status("applying %s\n" % patch)
714 722 pf = os.path.join(d, patch)
715 723
716 724 message = []
717 725 user = None
718 726 hgpatch = False
719 727 for line in file(pf):
720 728 line = line.rstrip()
721 729 if line.startswith("--- ") or line.startswith("diff -r"):
722 730 break
723 731 elif hgpatch:
724 732 # parse values when importing the result of an hg export
725 733 if line.startswith("# User "):
726 734 user = line[7:]
727 735 ui.debug('User: %s\n' % user)
728 736 elif not line.startswith("# ") and line:
729 737 message.append(line)
730 738 hgpatch = False
731 739 elif line == '# HG changeset patch':
732 740 hgpatch = True
733 741 message = [] # We may have collected garbage
734 742 else:
735 743 message.append(line)
736 744
737 745 # make sure message isn't empty
738 746 if not message:
739 747 message = "imported patch %s\n" % patch
740 748 else:
741 749 message = "%s\n" % '\n'.join(message)
742 750 ui.debug('message:\n%s\n' % message)
743 751
744 752 f = os.popen("patch -p%d < '%s'" % (strip, pf))
745 753 files = []
746 754 for l in f.read().splitlines():
747 755 l.rstrip('\r\n');
748 756 ui.status("%s\n" % l)
749 757 if l.startswith('patching file '):
750 758 pf = l[14:]
751 759 if pf not in files:
752 760 files.append(pf)
753 761 patcherr = f.close()
754 762 if patcherr:
755 763 raise util.Abort("patch failed")
756 764
757 765 if len(files) > 0:
758 766 addremove(ui, repo, *files)
759 767 repo.commit(files, message, user)
760 768
761 769 def init(ui, source=None):
762 770 """create a new repository in the current directory"""
763 771
764 772 if source:
765 773 raise util.Abort("no longer supported: use \"hg clone\" instead")
766 774 hg.repository(ui, ".", create=1)
767 775
768 776 def locate(ui, repo, *pats, **opts):
769 777 """locate files matching specific patterns"""
770 778 end = '\n'
771 779 if opts['print0']: end = '\0'
772 780
773 781 for src, abs, rel in walk(repo, pats, opts, '(?:.*/|)'):
774 782 if repo.dirstate.state(abs) == '?': continue
775 783 if opts['fullpath']:
776 784 ui.write(os.path.join(repo.root, abs), end)
777 785 else:
778 786 ui.write(rel, end)
779 787
780 788 def log(ui, repo, f=None, **opts):
781 789 """show the revision history of the repository or a single file"""
782 790 if f:
783 791 files = relpath(repo, [f])
784 792 filelog = repo.file(files[0])
785 793 log = filelog
786 794 lookup = filelog.lookup
787 795 else:
788 796 files = None
789 797 filelog = None
790 798 log = repo.changelog
791 799 lookup = repo.lookup
792 800 revlist = []
793 801 revs = [log.rev(lookup(rev)) for rev in opts['rev']]
794 802 while revs:
795 803 if len(revs) == 1:
796 804 revlist.append(revs.pop(0))
797 805 else:
798 806 a = revs.pop(0)
799 807 b = revs.pop(0)
800 808 off = a > b and -1 or 1
801 809 revlist.extend(range(a, b + off, off))
802 810
803 811 for i in revlist or range(log.count() - 1, -1, -1):
804 812 show_changeset(ui, repo, filelog=filelog, rev=i)
805 813 if opts['patch']:
806 814 if filelog:
807 815 filenode = filelog.node(i)
808 816 i = filelog.linkrev(filenode)
809 817 changenode = repo.changelog.node(i)
810 818 prev, other = repo.changelog.parents(changenode)
811 819 dodiff(sys.stdout, ui, repo, files, prev, changenode)
812 820 ui.write("\n\n")
813 821
814 822 def manifest(ui, repo, rev=None):
815 823 """output the latest or given revision of the project manifest"""
816 824 if rev:
817 825 try:
818 826 # assume all revision numbers are for changesets
819 827 n = repo.lookup(rev)
820 828 change = repo.changelog.read(n)
821 829 n = change[0]
822 830 except hg.RepoError:
823 831 n = repo.manifest.lookup(rev)
824 832 else:
825 833 n = repo.manifest.tip()
826 834 m = repo.manifest.read(n)
827 835 mf = repo.manifest.readflags(n)
828 836 files = m.keys()
829 837 files.sort()
830 838
831 839 for f in files:
832 840 ui.write("%40s %3s %s\n" % (hg.hex(m[f]), mf[f] and "755" or "644", f))
833 841
834 842 def parents(ui, repo, rev=None):
835 843 """show the parents of the working dir or revision"""
836 844 if rev:
837 845 p = repo.changelog.parents(repo.lookup(rev))
838 846 else:
839 847 p = repo.dirstate.parents()
840 848
841 849 for n in p:
842 850 if n != hg.nullid:
843 851 show_changeset(ui, repo, changenode=n)
844 852
845 853 def paths(ui, repo, search = None):
846 854 """show path or list of available paths"""
847 855 if search:
848 856 for name, path in ui.configitems("paths"):
849 857 if name == search:
850 858 ui.write("%s\n" % path)
851 859 return
852 860 ui.warn("not found!\n")
853 861 return 1
854 862 else:
855 863 for name, path in ui.configitems("paths"):
856 864 ui.write("%s = %s\n" % (name, path))
857 865
858 866 def pull(ui, repo, source="default", **opts):
859 867 """pull changes from the specified source"""
860 868 source = ui.expandpath(source)
861 869 ui.status('pulling from %s\n' % (source))
862 870
863 871 other = hg.repository(ui, source)
864 872 r = repo.pull(other)
865 873 if not r:
866 874 if opts['update']:
867 875 return update(ui, repo)
868 876 else:
869 877 ui.status("(run 'hg update' to get a working copy)\n")
870 878
871 879 return r
872 880
873 881 def push(ui, repo, dest="default-push", force=False):
874 882 """push changes to the specified destination"""
875 883 dest = ui.expandpath(dest)
876 884 ui.status('pushing to %s\n' % (dest))
877 885
878 886 other = hg.repository(ui, dest)
879 887 r = repo.push(other, force)
880 888 return r
881 889
882 890 def rawcommit(ui, repo, *flist, **rc):
883 891 "raw commit interface"
884 892 if rc['text']:
885 893 ui.warn("Warning: -t and --text is deprecated,"
886 894 " please use -m or --message instead.\n")
887 895 message = rc['message'] or rc['text']
888 896 if not message and rc['logfile']:
889 897 try:
890 898 message = open(rc['logfile']).read()
891 899 except IOError:
892 900 pass
893 901 if not message and not rc['logfile']:
894 902 ui.warn("abort: missing commit message\n")
895 903 return 1
896 904
897 905 files = relpath(repo, list(flist))
898 906 if rc['files']:
899 907 files += open(rc['files']).read().splitlines()
900 908
901 909 rc['parent'] = map(repo.lookup, rc['parent'])
902 910
903 911 repo.rawcommit(files, message, rc['user'], rc['date'], *rc['parent'])
904 912
905 913 def recover(ui, repo):
906 914 """roll back an interrupted transaction"""
907 915 repo.recover()
908 916
909 917 def remove(ui, repo, file1, *files):
910 918 """remove the specified files on the next commit"""
911 919 repo.remove(relpath(repo, (file1,) + files))
912 920
913 921 def revert(ui, repo, *names, **opts):
914 922 """revert modified files or dirs back to their unmodified states"""
915 923 node = opts['rev'] and repo.lookup(opts['rev']) or \
916 924 repo.dirstate.parents()[0]
917 925 root = os.path.realpath(repo.root)
918 926
919 927 def trimpath(p):
920 928 p = os.path.realpath(p)
921 929 if p.startswith(root):
922 930 rest = p[len(root):]
923 931 if not rest:
924 932 return rest
925 933 if p.startswith(os.sep):
926 934 return rest[1:]
927 935 return p
928 936
929 937 relnames = map(trimpath, names or [os.getcwd()])
930 938 chosen = {}
931 939
932 940 def choose(name):
933 941 def body(name):
934 942 for r in relnames:
935 943 if not name.startswith(r):
936 944 continue
937 945 rest = name[len(r):]
938 946 if not rest:
939 947 return r, True
940 948 depth = rest.count(os.sep)
941 949 if not r:
942 950 if depth == 0 or not opts['nonrecursive']:
943 951 return r, True
944 952 elif rest[0] == os.sep:
945 953 if depth == 1 or not opts['nonrecursive']:
946 954 return r, True
947 955 return None, False
948 956 relname, ret = body(name)
949 957 if ret:
950 958 chosen[relname] = 1
951 959 return ret
952 960
953 961 r = repo.update(node, False, True, choose, False)
954 962 for n in relnames:
955 963 if n not in chosen:
956 964 ui.warn('error: no matches for %s\n' % n)
957 965 r = 1
958 966 sys.stdout.flush()
959 967 return r
960 968
961 969 def root(ui, repo):
962 970 """print the root (top) of the current working dir"""
963 971 ui.write(repo.root + "\n")
964 972
965 973 def serve(ui, repo, **opts):
966 974 """export the repository via HTTP"""
967 975
968 976 if opts["stdio"]:
969 977 fin, fout = sys.stdin, sys.stdout
970 978 sys.stdout = sys.stderr
971 979
972 980 def getarg():
973 981 argline = fin.readline()[:-1]
974 982 arg, l = argline.split()
975 983 val = fin.read(int(l))
976 984 return arg, val
977 985 def respond(v):
978 986 fout.write("%d\n" % len(v))
979 987 fout.write(v)
980 988 fout.flush()
981 989
982 990 lock = None
983 991
984 992 while 1:
985 993 cmd = fin.readline()[:-1]
986 994 if cmd == '':
987 995 return
988 996 if cmd == "heads":
989 997 h = repo.heads()
990 998 respond(" ".join(map(hg.hex, h)) + "\n")
991 999 if cmd == "lock":
992 1000 lock = repo.lock()
993 1001 respond("")
994 1002 if cmd == "unlock":
995 1003 if lock:
996 1004 lock.release()
997 1005 lock = None
998 1006 respond("")
999 1007 elif cmd == "branches":
1000 1008 arg, nodes = getarg()
1001 1009 nodes = map(hg.bin, nodes.split(" "))
1002 1010 r = []
1003 1011 for b in repo.branches(nodes):
1004 1012 r.append(" ".join(map(hg.hex, b)) + "\n")
1005 1013 respond("".join(r))
1006 1014 elif cmd == "between":
1007 1015 arg, pairs = getarg()
1008 1016 pairs = [map(hg.bin, p.split("-")) for p in pairs.split(" ")]
1009 1017 r = []
1010 1018 for b in repo.between(pairs):
1011 1019 r.append(" ".join(map(hg.hex, b)) + "\n")
1012 1020 respond("".join(r))
1013 1021 elif cmd == "changegroup":
1014 1022 nodes = []
1015 1023 arg, roots = getarg()
1016 1024 nodes = map(hg.bin, roots.split(" "))
1017 1025
1018 1026 cg = repo.changegroup(nodes)
1019 1027 while 1:
1020 1028 d = cg.read(4096)
1021 1029 if not d:
1022 1030 break
1023 1031 fout.write(d)
1024 1032
1025 1033 fout.flush()
1026 1034
1027 1035 elif cmd == "addchangegroup":
1028 1036 if not lock:
1029 1037 respond("not locked")
1030 1038 continue
1031 1039 respond("")
1032 1040
1033 1041 r = repo.addchangegroup(fin)
1034 1042 respond("")
1035 1043
1036 1044 def openlog(opt, default):
1037 1045 if opts[opt] and opts[opt] != '-':
1038 1046 return open(opts[opt], 'w')
1039 1047 else:
1040 1048 return default
1041 1049
1042 1050 httpd = hgweb.create_server(repo.root, opts["name"], opts["templates"],
1043 1051 opts["address"], opts["port"], opts["ipv6"],
1044 1052 openlog('accesslog', sys.stdout),
1045 1053 openlog('errorlog', sys.stderr))
1046 1054 if ui.verbose:
1047 1055 addr, port = httpd.socket.getsockname()
1048 1056 if addr == '0.0.0.0':
1049 1057 addr = socket.gethostname()
1050 1058 else:
1051 1059 try:
1052 1060 addr = socket.gethostbyaddr(addr)[0]
1053 1061 except socket.error:
1054 1062 pass
1055 1063 if port != 80:
1056 1064 ui.status('listening at http://%s:%d/\n' % (addr, port))
1057 1065 else:
1058 1066 ui.status('listening at http://%s/\n' % addr)
1059 1067 httpd.serve_forever()
1060 1068
1061 1069 def status(ui, repo, *pats, **opts):
1062 1070 '''show changed files in the working directory
1063 1071
1064 1072 M = modified
1065 1073 A = added
1066 1074 R = removed
1067 1075 ? = not tracked
1068 1076 '''
1069 1077
1070 1078 cwd = repo.getcwd()
1071 1079 files, matchfn = matchpats(repo, cwd, pats, opts)
1072 1080 (c, a, d, u) = [[util.pathto(cwd, x) for x in n]
1073 1081 for n in repo.changes(files=files, match=matchfn)]
1074 1082
1075 1083 changetypes = [('modified', 'M', c),
1076 1084 ('added', 'A', a),
1077 1085 ('removed', 'R', d),
1078 1086 ('unknown', '?', u)]
1079 1087
1080 1088 for opt, char, changes in ([ct for ct in changetypes if opts[ct[0]]]
1081 1089 or changetypes):
1082 1090 for f in changes:
1083 1091 ui.write("%s %s\n" % (char, f))
1084 1092
1085 1093 def tag(ui, repo, name, rev=None, **opts):
1086 1094 """add a tag for the current tip or a given revision"""
1087 1095 if opts['text']:
1088 1096 ui.warn("Warning: -t and --text is deprecated,"
1089 1097 " please use -m or --message instead.\n")
1090 1098 if name == "tip":
1091 1099 ui.warn("abort: 'tip' is a reserved name!\n")
1092 1100 return -1
1093 1101 if rev:
1094 1102 r = hg.hex(repo.lookup(rev))
1095 1103 else:
1096 1104 r = hg.hex(repo.changelog.tip())
1097 1105
1098 1106 if name.find(revrangesep) >= 0:
1099 1107 ui.warn("abort: '%s' cannot be used in a tag name\n" % revrangesep)
1100 1108 return -1
1101 1109
1102 1110 if opts['local']:
1103 1111 repo.opener("localtags", "a").write("%s %s\n" % (r, name))
1104 1112 return
1105 1113
1106 1114 (c, a, d, u) = repo.changes()
1107 1115 for x in (c, a, d, u):
1108 1116 if ".hgtags" in x:
1109 1117 ui.warn("abort: working copy of .hgtags is changed!\n")
1110 1118 ui.status("(please commit .hgtags manually)\n")
1111 1119 return -1
1112 1120
1113 1121 repo.wfile(".hgtags", "ab").write("%s %s\n" % (r, name))
1114 1122 if repo.dirstate.state(".hgtags") == '?':
1115 1123 repo.add([".hgtags"])
1116 1124
1117 1125 message = (opts['message'] or opts['text'] or
1118 1126 "Added tag %s for changeset %s" % (name, r))
1119 1127 repo.commit([".hgtags"], message, opts['user'], opts['date'])
1120 1128
1121 1129 def tags(ui, repo):
1122 1130 """list repository tags"""
1123 1131
1124 1132 l = repo.tagslist()
1125 1133 l.reverse()
1126 1134 for t, n in l:
1127 1135 try:
1128 1136 r = "%5d:%s" % (repo.changelog.rev(n), hg.hex(n))
1129 1137 except KeyError:
1130 1138 r = " ?:?"
1131 1139 ui.write("%-30s %s\n" % (t, r))
1132 1140
1133 1141 def tip(ui, repo):
1134 1142 """show the tip revision"""
1135 1143 n = repo.changelog.tip()
1136 1144 show_changeset(ui, repo, changenode=n)
1137 1145
1138 1146 def undo(ui, repo):
1139 1147 """undo the last commit or pull
1140 1148
1141 1149 Roll back the last pull or commit transaction on the
1142 1150 repository, restoring the project to its earlier state.
1143 1151
1144 1152 This command should be used with care. There is only one level of
1145 1153 undo and there is no redo.
1146 1154
1147 1155 This command is not intended for use on public repositories. Once
1148 1156 a change is visible for pull by other users, undoing it locally is
1149 1157 ineffective.
1150 1158 """
1151 1159 repo.undo()
1152 1160
1153 def update(ui, repo, node=None, merge=False, clean=False):
1161 def update(ui, repo, node=None, merge=False, clean=False, branch=None):
1154 1162 '''update or merge working directory
1155 1163
1156 1164 If there are no outstanding changes in the working directory and
1157 1165 there is a linear relationship between the current version and the
1158 1166 requested version, the result is the requested version.
1159 1167
1160 1168 Otherwise the result is a merge between the contents of the
1161 1169 current working directory and the requested version. Files that
1162 1170 changed between either parent are marked as changed for the next
1163 1171 commit and a commit must be performed before any further updates
1164 1172 are allowed.
1165 1173 '''
1166 node = node and repo.lookup(node) or repo.changelog.tip()
1174 if branch:
1175 br = repo.branchlookup(branch=branch)
1176 found = []
1177 for x in br:
1178 if branch in br[x]:
1179 found.append(x)
1180 if len(found) > 1:
1181 ui.warn("Found multiple heads for %s\n" % branch)
1182 for x in found:
1183 show_changeset(ui, repo, changenode=x, brinfo=br)
1184 return 1
1185 if len(found) == 1:
1186 node = found[0]
1187 ui.warn("Using head %s for branch %s\n" % (hg.short(node), branch))
1188 else:
1189 ui.warn("branch %s not found\n" % (branch))
1190 return 1
1191 else:
1192 node = node and repo.lookup(node) or repo.changelog.tip()
1167 1193 return repo.update(node, allow=merge, force=clean)
1168 1194
1169 1195 def verify(ui, repo):
1170 1196 """verify the integrity of the repository"""
1171 1197 return repo.verify()
1172 1198
1173 1199 # Command options and aliases are listed here, alphabetically
1174 1200
1175 1201 table = {
1176 1202 "^add":
1177 1203 (add,
1178 1204 [('I', 'include', [], 'include path in search'),
1179 1205 ('X', 'exclude', [], 'exclude path from search')],
1180 1206 "hg add [FILE]..."),
1181 1207 "addremove":
1182 1208 (addremove,
1183 1209 [('I', 'include', [], 'include path in search'),
1184 1210 ('X', 'exclude', [], 'exclude path from search')],
1185 1211 "hg addremove [OPTION]... [FILE]..."),
1186 1212 "^annotate":
1187 1213 (annotate,
1188 1214 [('r', 'rev', '', 'revision'),
1189 1215 ('u', 'user', None, 'show user'),
1190 1216 ('n', 'number', None, 'show revision number'),
1191 1217 ('c', 'changeset', None, 'show changeset'),
1192 1218 ('I', 'include', [], 'include path in search'),
1193 1219 ('X', 'exclude', [], 'exclude path from search')],
1194 1220 'hg annotate [-r REV] [-u] [-n] [-c] FILE...'),
1195 1221 "cat":
1196 1222 (cat,
1197 1223 [('o', 'output', "", 'output to file')],
1198 1224 'hg cat [-o OUTFILE] FILE [REV]'),
1199 1225 "^clone":
1200 1226 (clone,
1201 1227 [('U', 'noupdate', None, 'skip update after cloning')],
1202 1228 'hg clone [-U] SOURCE [DEST]'),
1203 1229 "^commit|ci":
1204 1230 (commit,
1205 1231 [('A', 'addremove', None, 'run add/remove during commit'),
1206 1232 ('I', 'include', [], 'include path in search'),
1207 1233 ('X', 'exclude', [], 'exclude path from search'),
1208 1234 ('m', 'message', "", 'commit message'),
1209 1235 ('t', 'text', "", 'commit message (deprecated: use -m)'),
1210 1236 ('l', 'logfile', "", 'commit message file'),
1211 1237 ('d', 'date', "", 'date code'),
1212 1238 ('u', 'user', "", 'user')],
1213 1239 'hg commit [OPTION]... [FILE]...'),
1214 1240 "copy": (copy, [], 'hg copy SOURCE DEST'),
1215 1241 "debugcheckstate": (debugcheckstate, [], 'debugcheckstate'),
1216 1242 "debugstate": (debugstate, [], 'debugstate'),
1217 1243 "debugindex": (debugindex, [], 'debugindex FILE'),
1218 1244 "debugindexdot": (debugindexdot, [], 'debugindexdot FILE'),
1219 1245 "debugwalk":
1220 1246 (debugwalk,
1221 1247 [('I', 'include', [], 'include path in search'),
1222 1248 ('X', 'exclude', [], 'exclude path from search')],
1223 1249 'debugwalk [OPTIONS]... [FILE]...'),
1224 1250 "^diff":
1225 1251 (diff,
1226 1252 [('r', 'rev', [], 'revision'),
1227 1253 ('I', 'include', [], 'include path in search'),
1228 1254 ('X', 'exclude', [], 'exclude path from search')],
1229 1255 'hg diff [-r REV1 [-r REV2]] [FILE]...'),
1230 1256 "^export":
1231 1257 (export,
1232 1258 [('o', 'output', "", 'output to file')],
1233 1259 "hg export [-o OUTFILE] REV..."),
1234 1260 "forget":
1235 1261 (forget,
1236 1262 [('I', 'include', [], 'include path in search'),
1237 1263 ('X', 'exclude', [], 'exclude path from search')],
1238 1264 "hg forget FILE..."),
1239 "heads": (heads, [], 'hg heads'),
1265 "heads": (heads, [('b', 'branches', None, 'find branch info')], 'hg heads'),
1240 1266 "help": (help_, [], 'hg help [COMMAND]'),
1241 1267 "identify|id": (identify, [], 'hg identify'),
1242 1268 "import|patch":
1243 1269 (import_,
1244 1270 [('p', 'strip', 1, 'path strip'),
1245 1271 ('b', 'base', "", 'base path')],
1246 1272 "hg import [-p NUM] [-b BASE] PATCH..."),
1247 1273 "^init": (init, [], 'hg init'),
1248 1274 "locate":
1249 1275 (locate,
1250 1276 [('r', 'rev', '', 'revision'),
1251 1277 ('0', 'print0', None, 'end records with NUL'),
1252 1278 ('f', 'fullpath', None, 'print complete paths'),
1253 1279 ('I', 'include', [], 'include path in search'),
1254 1280 ('X', 'exclude', [], 'exclude path from search')],
1255 1281 'hg locate [-r REV] [-f] [-0] [PATTERN]...'),
1256 1282 "^log|history":
1257 1283 (log,
1258 1284 [('r', 'rev', [], 'revision'),
1259 1285 ('p', 'patch', None, 'show patch')],
1260 1286 'hg log [-r REV1 [-r REV2]] [-p] [FILE]'),
1261 1287 "manifest": (manifest, [], 'hg manifest [REV]'),
1262 1288 "parents": (parents, [], 'hg parents [REV]'),
1263 1289 "paths": (paths, [], 'hg paths [name]'),
1264 1290 "^pull":
1265 1291 (pull,
1266 1292 [('u', 'update', None, 'update working directory')],
1267 1293 'hg pull [-u] [SOURCE]'),
1268 1294 "^push":
1269 1295 (push,
1270 1296 [('f', 'force', None, 'force push')],
1271 1297 'hg push [DEST]'),
1272 1298 "rawcommit":
1273 1299 (rawcommit,
1274 1300 [('p', 'parent', [], 'parent'),
1275 1301 ('d', 'date', "", 'date code'),
1276 1302 ('u', 'user', "", 'user'),
1277 1303 ('F', 'files', "", 'file list'),
1278 1304 ('m', 'message', "", 'commit message'),
1279 1305 ('t', 'text', "", 'commit message (deprecated: use -m)'),
1280 1306 ('l', 'logfile', "", 'commit message file')],
1281 1307 'hg rawcommit [OPTION]... [FILE]...'),
1282 1308 "recover": (recover, [], "hg recover"),
1283 1309 "^remove|rm": (remove, [], "hg remove FILE..."),
1284 1310 "^revert":
1285 1311 (revert,
1286 1312 [("n", "nonrecursive", None, "don't recurse into subdirs"),
1287 1313 ("r", "rev", "", "revision")],
1288 1314 "hg revert [-n] [-r REV] [NAME]..."),
1289 1315 "root": (root, [], "hg root"),
1290 1316 "^serve":
1291 1317 (serve,
1292 1318 [('A', 'accesslog', '', 'access log file'),
1293 1319 ('E', 'errorlog', '', 'error log file'),
1294 1320 ('p', 'port', 8000, 'listen port'),
1295 1321 ('a', 'address', '', 'interface address'),
1296 1322 ('n', 'name', os.getcwd(), 'repository name'),
1297 1323 ('', 'stdio', None, 'for remote clients'),
1298 1324 ('t', 'templates', "", 'template map'),
1299 1325 ('6', 'ipv6', None, 'use IPv6 in addition to IPv4')],
1300 1326 "hg serve [OPTION]..."),
1301 1327 "^status":
1302 1328 (status,
1303 1329 [('m', 'modified', None, 'show only modified files'),
1304 1330 ('a', 'added', None, 'show only added files'),
1305 1331 ('r', 'removed', None, 'show only removed files'),
1306 1332 ('u', 'unknown', None, 'show only unknown (not tracked) files'),
1307 1333 ('I', 'include', [], 'include path in search'),
1308 1334 ('X', 'exclude', [], 'exclude path from search')],
1309 1335 "hg status [FILE]..."),
1310 1336 "tag":
1311 1337 (tag,
1312 1338 [('l', 'local', None, 'make the tag local'),
1313 1339 ('m', 'message', "", 'commit message'),
1314 1340 ('t', 'text', "", 'commit message (deprecated: use -m)'),
1315 1341 ('d', 'date', "", 'date code'),
1316 1342 ('u', 'user', "", 'user')],
1317 1343 'hg tag [OPTION]... NAME [REV]'),
1318 1344 "tags": (tags, [], 'hg tags'),
1319 1345 "tip": (tip, [], 'hg tip'),
1320 1346 "undo": (undo, [], 'hg undo'),
1321 1347 "^update|up|checkout|co":
1322 1348 (update,
1323 [('m', 'merge', None, 'allow merging of conflicts'),
1349 [('b', 'branch', "", 'checkout the head of a specific branch'),
1350 ('m', 'merge', None, 'allow merging of conflicts'),
1324 1351 ('C', 'clean', None, 'overwrite locally modified files')],
1325 1352 'hg update [-m] [-C] [REV]'),
1326 1353 "verify": (verify, [], 'hg verify'),
1327 1354 "version": (show_version, [], 'hg version'),
1328 1355 }
1329 1356
1330 1357 globalopts = [('v', 'verbose', None, 'verbose mode'),
1331 1358 ('', 'debug', None, 'debug mode'),
1332 1359 ('q', 'quiet', None, 'quiet mode'),
1333 1360 ('', 'profile', None, 'profile'),
1334 1361 ('R', 'repository', "", 'repository root directory'),
1335 1362 ('', 'traceback', None, 'print traceback on exception'),
1336 1363 ('y', 'noninteractive', None, 'run non-interactively'),
1337 1364 ('', 'version', None, 'output version information and exit'),
1338 1365 ('', 'time', None, 'time how long the command takes'),
1339 1366 ]
1340 1367
1341 1368 norepo = "clone init version help debugindex debugindexdot"
1342 1369
1343 1370 def find(cmd):
1344 1371 for e in table.keys():
1345 1372 if re.match("(%s)$" % e, cmd):
1346 1373 return e, table[e]
1347 1374
1348 1375 raise UnknownCommand(cmd)
1349 1376
1350 1377 class SignalInterrupt(Exception):
1351 1378 """Exception raised on SIGTERM and SIGHUP."""
1352 1379
1353 1380 def catchterm(*args):
1354 1381 raise SignalInterrupt
1355 1382
1356 1383 def run():
1357 1384 sys.exit(dispatch(sys.argv[1:]))
1358 1385
1359 1386 class ParseError(Exception):
1360 1387 """Exception raised on errors in parsing the command line."""
1361 1388
1362 1389 def parse(args):
1363 1390 options = {}
1364 1391 cmdoptions = {}
1365 1392
1366 1393 try:
1367 1394 args = fancyopts.fancyopts(args, globalopts, options)
1368 1395 except fancyopts.getopt.GetoptError, inst:
1369 1396 raise ParseError(None, inst)
1370 1397
1371 1398 if options["version"]:
1372 1399 return ("version", show_version, [], options, cmdoptions)
1373 1400 elif not args:
1374 1401 return ("help", help_, ["shortlist"], options, cmdoptions)
1375 1402 else:
1376 1403 cmd, args = args[0], args[1:]
1377 1404
1378 1405 i = find(cmd)[1]
1379 1406
1380 1407 # combine global options into local
1381 1408 c = list(i[1])
1382 1409 for o in globalopts:
1383 1410 c.append((o[0], o[1], options[o[1]], o[3]))
1384 1411
1385 1412 try:
1386 1413 args = fancyopts.fancyopts(args, c, cmdoptions)
1387 1414 except fancyopts.getopt.GetoptError, inst:
1388 1415 raise ParseError(cmd, inst)
1389 1416
1390 1417 # separate global options back out
1391 1418 for o in globalopts:
1392 1419 n = o[1]
1393 1420 options[n] = cmdoptions[n]
1394 1421 del cmdoptions[n]
1395 1422
1396 1423 return (cmd, i[0], args, options, cmdoptions)
1397 1424
1398 1425 def dispatch(args):
1399 1426 signal.signal(signal.SIGTERM, catchterm)
1400 1427 try:
1401 1428 signal.signal(signal.SIGHUP, catchterm)
1402 1429 except AttributeError:
1403 1430 pass
1404 1431
1405 1432 try:
1406 1433 cmd, func, args, options, cmdoptions = parse(args)
1407 1434 except ParseError, inst:
1408 1435 u = ui.ui()
1409 1436 if inst.args[0]:
1410 1437 u.warn("hg %s: %s\n" % (inst.args[0], inst.args[1]))
1411 1438 help_(u, inst.args[0])
1412 1439 else:
1413 1440 u.warn("hg: %s\n" % inst.args[1])
1414 1441 help_(u, 'shortlist')
1415 1442 sys.exit(-1)
1416 1443 except UnknownCommand, inst:
1417 1444 u = ui.ui()
1418 1445 u.warn("hg: unknown command '%s'\n" % inst.args[0])
1419 1446 help_(u, 'shortlist')
1420 1447 sys.exit(1)
1421 1448
1422 1449 if options["time"]:
1423 1450 def get_times():
1424 1451 t = os.times()
1425 1452 if t[4] == 0.0: # Windows leaves this as zero, so use time.clock()
1426 1453 t = (t[0], t[1], t[2], t[3], time.clock())
1427 1454 return t
1428 1455 s = get_times()
1429 1456 def print_time():
1430 1457 t = get_times()
1431 1458 u = ui.ui()
1432 1459 u.warn("Time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n" %
1433 1460 (t[4]-s[4], t[0]-s[0], t[2]-s[2], t[1]-s[1], t[3]-s[3]))
1434 1461 atexit.register(print_time)
1435 1462
1436 1463 u = ui.ui(options["verbose"], options["debug"], options["quiet"],
1437 1464 not options["noninteractive"])
1438 1465
1439 1466 try:
1440 1467 try:
1441 1468 if cmd not in norepo.split():
1442 1469 path = options["repository"] or ""
1443 1470 repo = hg.repository(ui=u, path=path)
1444 1471 d = lambda: func(u, repo, *args, **cmdoptions)
1445 1472 else:
1446 1473 d = lambda: func(u, *args, **cmdoptions)
1447 1474
1448 1475 if options['profile']:
1449 1476 import hotshot, hotshot.stats
1450 1477 prof = hotshot.Profile("hg.prof")
1451 1478 r = prof.runcall(d)
1452 1479 prof.close()
1453 1480 stats = hotshot.stats.load("hg.prof")
1454 1481 stats.strip_dirs()
1455 1482 stats.sort_stats('time', 'calls')
1456 1483 stats.print_stats(40)
1457 1484 return r
1458 1485 else:
1459 1486 return d()
1460 1487 except:
1461 1488 if options['traceback']:
1462 1489 traceback.print_exc()
1463 1490 raise
1464 1491 except hg.RepoError, inst:
1465 1492 u.warn("abort: ", inst, "!\n")
1466 1493 except SignalInterrupt:
1467 1494 u.warn("killed!\n")
1468 1495 except KeyboardInterrupt:
1469 1496 try:
1470 1497 u.warn("interrupted!\n")
1471 1498 except IOError, inst:
1472 1499 if inst.errno == errno.EPIPE:
1473 1500 if u.debugflag:
1474 1501 u.warn("\nbroken pipe\n")
1475 1502 else:
1476 1503 raise
1477 1504 except IOError, inst:
1478 1505 if hasattr(inst, "code"):
1479 1506 u.warn("abort: %s\n" % inst)
1480 1507 elif hasattr(inst, "reason"):
1481 1508 u.warn("abort: error: %s\n" % inst.reason[1])
1482 1509 elif hasattr(inst, "args") and inst[0] == errno.EPIPE:
1483 1510 if u.debugflag: u.warn("broken pipe\n")
1484 1511 else:
1485 1512 raise
1486 1513 except OSError, inst:
1487 1514 if hasattr(inst, "filename"):
1488 1515 u.warn("abort: %s: %s\n" % (inst.strerror, inst.filename))
1489 1516 else:
1490 1517 u.warn("abort: %s\n" % inst.strerror)
1491 1518 except util.Abort, inst:
1492 1519 u.warn('abort: ', inst.args[0] % inst.args[1:], '\n')
1493 1520 sys.exit(1)
1494 1521 except TypeError, inst:
1495 1522 # was this an argument error?
1496 1523 tb = traceback.extract_tb(sys.exc_info()[2])
1497 1524 if len(tb) > 2: # no
1498 1525 raise
1499 1526 u.debug(inst, "\n")
1500 1527 u.warn("%s: invalid arguments\n" % cmd)
1501 1528 help_(u, cmd)
1502 1529 except UnknownCommand, inst:
1503 1530 u.warn("hg: unknown command '%s'\n" % inst.args[0])
1504 1531 help_(u, 'shortlist')
1505 1532
1506 1533 sys.exit(-1)
@@ -1,2104 +1,2210
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 urlparse")
14 14 demandload(globals(), "bisect errno select stat")
15 15
16 16 class filelog(revlog):
17 17 def __init__(self, opener, path):
18 18 revlog.__init__(self, opener,
19 19 os.path.join("data", self.encodedir(path + ".i")),
20 20 os.path.join("data", self.encodedir(path + ".d")))
21 21
22 22 # This avoids a collision between a file named foo and a dir named
23 23 # foo.i or foo.d
24 24 def encodedir(self, path):
25 25 return (path
26 26 .replace(".hg/", ".hg.hg/")
27 27 .replace(".i/", ".i.hg/")
28 28 .replace(".d/", ".d.hg/"))
29 29
30 30 def decodedir(self, path):
31 31 return (path
32 32 .replace(".d.hg/", ".d/")
33 33 .replace(".i.hg/", ".i/")
34 34 .replace(".hg.hg/", ".hg/"))
35 35
36 36 def read(self, node):
37 37 t = self.revision(node)
38 38 if not t.startswith('\1\n'):
39 39 return t
40 40 s = t.find('\1\n', 2)
41 41 return t[s+2:]
42 42
43 43 def readmeta(self, node):
44 44 t = self.revision(node)
45 45 if not t.startswith('\1\n'):
46 46 return t
47 47 s = t.find('\1\n', 2)
48 48 mt = t[2:s]
49 49 for l in mt.splitlines():
50 50 k, v = l.split(": ", 1)
51 51 m[k] = v
52 52 return m
53 53
54 54 def add(self, text, meta, transaction, link, p1=None, p2=None):
55 55 if meta or text.startswith('\1\n'):
56 56 mt = ""
57 57 if meta:
58 58 mt = [ "%s: %s\n" % (k, v) for k,v in meta.items() ]
59 59 text = "\1\n" + "".join(mt) + "\1\n" + text
60 60 return self.addrevision(text, transaction, link, p1, p2)
61 61
62 62 def annotate(self, node):
63 63
64 64 def decorate(text, rev):
65 65 return ([rev] * len(text.splitlines()), text)
66 66
67 67 def pair(parent, child):
68 68 for a1, a2, b1, b2 in bdiff.blocks(parent[1], child[1]):
69 69 child[0][b1:b2] = parent[0][a1:a2]
70 70 return child
71 71
72 72 # find all ancestors
73 73 needed = {node:1}
74 74 visit = [node]
75 75 while visit:
76 76 n = visit.pop(0)
77 77 for p in self.parents(n):
78 78 if p not in needed:
79 79 needed[p] = 1
80 80 visit.append(p)
81 81 else:
82 82 # count how many times we'll use this
83 83 needed[p] += 1
84 84
85 85 # sort by revision which is a topological order
86 86 visit = [ (self.rev(n), n) for n in needed.keys() ]
87 87 visit.sort()
88 88 hist = {}
89 89
90 90 for r,n in visit:
91 91 curr = decorate(self.read(n), self.linkrev(n))
92 92 for p in self.parents(n):
93 93 if p != nullid:
94 94 curr = pair(hist[p], curr)
95 95 # trim the history of unneeded revs
96 96 needed[p] -= 1
97 97 if not needed[p]:
98 98 del hist[p]
99 99 hist[n] = curr
100 100
101 101 return zip(hist[n][0], hist[n][1].splitlines(1))
102 102
103 103 class manifest(revlog):
104 104 def __init__(self, opener):
105 105 self.mapcache = None
106 106 self.listcache = None
107 107 self.addlist = None
108 108 revlog.__init__(self, opener, "00manifest.i", "00manifest.d")
109 109
110 110 def read(self, node):
111 111 if node == nullid: return {} # don't upset local cache
112 112 if self.mapcache and self.mapcache[0] == node:
113 113 return self.mapcache[1]
114 114 text = self.revision(node)
115 115 map = {}
116 116 flag = {}
117 117 self.listcache = (text, text.splitlines(1))
118 118 for l in self.listcache[1]:
119 119 (f, n) = l.split('\0')
120 120 map[f] = bin(n[:40])
121 121 flag[f] = (n[40:-1] == "x")
122 122 self.mapcache = (node, map, flag)
123 123 return map
124 124
125 125 def readflags(self, node):
126 126 if node == nullid: return {} # don't upset local cache
127 127 if not self.mapcache or self.mapcache[0] != node:
128 128 self.read(node)
129 129 return self.mapcache[2]
130 130
131 131 def diff(self, a, b):
132 132 # this is sneaky, as we're not actually using a and b
133 133 if self.listcache and self.addlist and self.listcache[0] == a:
134 134 d = mdiff.diff(self.listcache[1], self.addlist, 1)
135 135 if mdiff.patch(a, d) != b:
136 136 sys.stderr.write("*** sortdiff failed, falling back ***\n")
137 137 return mdiff.textdiff(a, b)
138 138 return d
139 139 else:
140 140 return mdiff.textdiff(a, b)
141 141
142 142 def add(self, map, flags, transaction, link, p1=None, p2=None,
143 143 changed=None):
144 144 # directly generate the mdiff delta from the data collected during
145 145 # the bisect loop below
146 146 def gendelta(delta):
147 147 i = 0
148 148 result = []
149 149 while i < len(delta):
150 150 start = delta[i][2]
151 151 end = delta[i][3]
152 152 l = delta[i][4]
153 153 if l == None:
154 154 l = ""
155 155 while i < len(delta) - 1 and start <= delta[i+1][2] \
156 156 and end >= delta[i+1][2]:
157 157 if delta[i+1][3] > end:
158 158 end = delta[i+1][3]
159 159 if delta[i+1][4]:
160 160 l += delta[i+1][4]
161 161 i += 1
162 162 result.append(struct.pack(">lll", start, end, len(l)) + l)
163 163 i += 1
164 164 return result
165 165
166 166 # apply the changes collected during the bisect loop to our addlist
167 167 def addlistdelta(addlist, delta):
168 168 # apply the deltas to the addlist. start from the bottom up
169 169 # so changes to the offsets don't mess things up.
170 170 i = len(delta)
171 171 while i > 0:
172 172 i -= 1
173 173 start = delta[i][0]
174 174 end = delta[i][1]
175 175 if delta[i][4]:
176 176 addlist[start:end] = [delta[i][4]]
177 177 else:
178 178 del addlist[start:end]
179 179 return addlist
180 180
181 181 # calculate the byte offset of the start of each line in the
182 182 # manifest
183 183 def calcoffsets(addlist):
184 184 offsets = [0] * (len(addlist) + 1)
185 185 offset = 0
186 186 i = 0
187 187 while i < len(addlist):
188 188 offsets[i] = offset
189 189 offset += len(addlist[i])
190 190 i += 1
191 191 offsets[i] = offset
192 192 return offsets
193 193
194 194 # if we're using the listcache, make sure it is valid and
195 195 # parented by the same node we're diffing against
196 196 if not changed or not self.listcache or not p1 or \
197 197 self.mapcache[0] != p1:
198 198 files = map.keys()
199 199 files.sort()
200 200
201 201 self.addlist = ["%s\000%s%s\n" %
202 202 (f, hex(map[f]), flags[f] and "x" or '')
203 203 for f in files]
204 204 cachedelta = None
205 205 else:
206 206 addlist = self.listcache[1]
207 207
208 208 # find the starting offset for each line in the add list
209 209 offsets = calcoffsets(addlist)
210 210
211 211 # combine the changed lists into one list for sorting
212 212 work = [[x, 0] for x in changed[0]]
213 213 work[len(work):] = [[x, 1] for x in changed[1]]
214 214 work.sort()
215 215
216 216 delta = []
217 217 bs = 0
218 218
219 219 for w in work:
220 220 f = w[0]
221 221 # bs will either be the index of the item or the insert point
222 222 bs = bisect.bisect(addlist, f, bs)
223 223 if bs < len(addlist):
224 224 fn = addlist[bs][:addlist[bs].index('\0')]
225 225 else:
226 226 fn = None
227 227 if w[1] == 0:
228 228 l = "%s\000%s%s\n" % (f, hex(map[f]),
229 229 flags[f] and "x" or '')
230 230 else:
231 231 l = None
232 232 start = bs
233 233 if fn != f:
234 234 # item not found, insert a new one
235 235 end = bs
236 236 if w[1] == 1:
237 237 sys.stderr.write("failed to remove %s from manifest\n"
238 238 % f)
239 239 sys.exit(1)
240 240 else:
241 241 # item is found, replace/delete the existing line
242 242 end = bs + 1
243 243 delta.append([start, end, offsets[start], offsets[end], l])
244 244
245 245 self.addlist = addlistdelta(addlist, delta)
246 246 if self.mapcache[0] == self.tip():
247 247 cachedelta = "".join(gendelta(delta))
248 248 else:
249 249 cachedelta = None
250 250
251 251 text = "".join(self.addlist)
252 252 if cachedelta and mdiff.patch(self.listcache[0], cachedelta) != text:
253 253 sys.stderr.write("manifest delta failure\n")
254 254 sys.exit(1)
255 255 n = self.addrevision(text, transaction, link, p1, p2, cachedelta)
256 256 self.mapcache = (n, map, flags)
257 257 self.listcache = (text, self.addlist)
258 258 self.addlist = None
259 259
260 260 return n
261 261
262 262 class changelog(revlog):
263 263 def __init__(self, opener):
264 264 revlog.__init__(self, opener, "00changelog.i", "00changelog.d")
265 265
266 266 def extract(self, text):
267 267 if not text:
268 268 return (nullid, "", "0", [], "")
269 269 last = text.index("\n\n")
270 270 desc = text[last + 2:]
271 271 l = text[:last].splitlines()
272 272 manifest = bin(l[0])
273 273 user = l[1]
274 274 date = l[2]
275 275 files = l[3:]
276 276 return (manifest, user, date, files, desc)
277 277
278 278 def read(self, node):
279 279 return self.extract(self.revision(node))
280 280
281 281 def add(self, manifest, list, desc, transaction, p1=None, p2=None,
282 282 user=None, date=None):
283 283 date = date or "%d %d" % (time.time(), time.timezone)
284 284 list.sort()
285 285 l = [hex(manifest), user, date] + list + ["", desc]
286 286 text = "\n".join(l)
287 287 return self.addrevision(text, transaction, self.count(), p1, p2)
288 288
289 289 class dirstate:
290 290 def __init__(self, opener, ui, root):
291 291 self.opener = opener
292 292 self.root = root
293 293 self.dirty = 0
294 294 self.ui = ui
295 295 self.map = None
296 296 self.pl = None
297 297 self.copies = {}
298 298 self.ignorefunc = None
299 299
300 300 def wjoin(self, f):
301 301 return os.path.join(self.root, f)
302 302
303 303 def getcwd(self):
304 304 cwd = os.getcwd()
305 305 if cwd == self.root: return ''
306 306 return cwd[len(self.root) + 1:]
307 307
308 308 def ignore(self, f):
309 309 if not self.ignorefunc:
310 310 bigpat = []
311 311 try:
312 312 l = file(self.wjoin(".hgignore"))
313 313 for pat in l:
314 314 if pat != "\n":
315 315 p = pat[:-1]
316 316 try:
317 317 re.compile(p)
318 318 except:
319 319 self.ui.warn("ignoring invalid ignore"
320 320 + " regular expression '%s'\n" % p)
321 321 else:
322 322 bigpat.append(p)
323 323 except IOError: pass
324 324
325 325 if bigpat:
326 326 s = "(?:%s)" % (")|(?:".join(bigpat))
327 327 r = re.compile(s)
328 328 self.ignorefunc = r.search
329 329 else:
330 330 self.ignorefunc = util.never
331 331
332 332 return self.ignorefunc(f)
333 333
334 334 def __del__(self):
335 335 if self.dirty:
336 336 self.write()
337 337
338 338 def __getitem__(self, key):
339 339 try:
340 340 return self.map[key]
341 341 except TypeError:
342 342 self.read()
343 343 return self[key]
344 344
345 345 def __contains__(self, key):
346 346 if not self.map: self.read()
347 347 return key in self.map
348 348
349 349 def parents(self):
350 350 if not self.pl:
351 351 self.read()
352 352 return self.pl
353 353
354 354 def markdirty(self):
355 355 if not self.dirty:
356 356 self.dirty = 1
357 357
358 358 def setparents(self, p1, p2 = nullid):
359 359 self.markdirty()
360 360 self.pl = p1, p2
361 361
362 362 def state(self, key):
363 363 try:
364 364 return self[key][0]
365 365 except KeyError:
366 366 return "?"
367 367
368 368 def read(self):
369 369 if self.map is not None: return self.map
370 370
371 371 self.map = {}
372 372 self.pl = [nullid, nullid]
373 373 try:
374 374 st = self.opener("dirstate").read()
375 375 if not st: return
376 376 except: return
377 377
378 378 self.pl = [st[:20], st[20: 40]]
379 379
380 380 pos = 40
381 381 while pos < len(st):
382 382 e = struct.unpack(">cllll", st[pos:pos+17])
383 383 l = e[4]
384 384 pos += 17
385 385 f = st[pos:pos + l]
386 386 if '\0' in f:
387 387 f, c = f.split('\0')
388 388 self.copies[f] = c
389 389 self.map[f] = e[:4]
390 390 pos += l
391 391
392 392 def copy(self, source, dest):
393 393 self.read()
394 394 self.markdirty()
395 395 self.copies[dest] = source
396 396
397 397 def copied(self, file):
398 398 return self.copies.get(file, None)
399 399
400 400 def update(self, files, state, **kw):
401 401 ''' current states:
402 402 n normal
403 403 m needs merging
404 404 r marked for removal
405 405 a marked for addition'''
406 406
407 407 if not files: return
408 408 self.read()
409 409 self.markdirty()
410 410 for f in files:
411 411 if state == "r":
412 412 self.map[f] = ('r', 0, 0, 0)
413 413 else:
414 414 s = os.stat(os.path.join(self.root, f))
415 415 st_size = kw.get('st_size', s.st_size)
416 416 st_mtime = kw.get('st_mtime', s.st_mtime)
417 417 self.map[f] = (state, s.st_mode, st_size, st_mtime)
418 418
419 419 def forget(self, files):
420 420 if not files: return
421 421 self.read()
422 422 self.markdirty()
423 423 for f in files:
424 424 try:
425 425 del self.map[f]
426 426 except KeyError:
427 427 self.ui.warn("not in dirstate: %s!\n" % f)
428 428 pass
429 429
430 430 def clear(self):
431 431 self.map = {}
432 432 self.markdirty()
433 433
434 434 def write(self):
435 435 st = self.opener("dirstate", "w")
436 436 st.write("".join(self.pl))
437 437 for f, e in self.map.items():
438 438 c = self.copied(f)
439 439 if c:
440 440 f = f + "\0" + c
441 441 e = struct.pack(">cllll", e[0], e[1], e[2], e[3], len(f))
442 442 st.write(e + f)
443 443 self.dirty = 0
444 444
445 445 def filterfiles(self, files):
446 446 ret = {}
447 447 unknown = []
448 448
449 449 for x in files:
450 450 if x is '.':
451 451 return self.map.copy()
452 452 if x not in self.map:
453 453 unknown.append(x)
454 454 else:
455 455 ret[x] = self.map[x]
456 456
457 457 if not unknown:
458 458 return ret
459 459
460 460 b = self.map.keys()
461 461 b.sort()
462 462 blen = len(b)
463 463
464 464 for x in unknown:
465 465 bs = bisect.bisect(b, x)
466 466 if bs != 0 and b[bs-1] == x:
467 467 ret[x] = self.map[x]
468 468 continue
469 469 while bs < blen:
470 470 s = b[bs]
471 471 if len(s) > len(x) and s.startswith(x) and s[len(x)] == '/':
472 472 ret[s] = self.map[s]
473 473 else:
474 474 break
475 475 bs += 1
476 476 return ret
477 477
478 478 def walk(self, files = None, match = util.always, dc=None):
479 479 self.read()
480 480
481 481 # walk all files by default
482 482 if not files:
483 483 files = [self.root]
484 484 if not dc:
485 485 dc = self.map.copy()
486 486 elif not dc:
487 487 dc = self.filterfiles(files)
488 488
489 489 known = {'.hg': 1}
490 490 def seen(fn):
491 491 if fn in known: return True
492 492 known[fn] = 1
493 493 def traverse():
494 494 for ff in util.unique(files):
495 495 f = os.path.join(self.root, ff)
496 496 try:
497 497 st = os.stat(f)
498 498 except OSError, inst:
499 499 if ff not in dc: self.ui.warn('%s: %s\n' % (
500 500 util.pathto(self.getcwd(), ff),
501 501 inst.strerror))
502 502 continue
503 503 if stat.S_ISDIR(st.st_mode):
504 504 for dir, subdirs, fl in os.walk(f):
505 505 d = dir[len(self.root) + 1:]
506 506 nd = util.normpath(d)
507 507 if nd == '.': nd = ''
508 508 if seen(nd):
509 509 subdirs[:] = []
510 510 continue
511 511 for sd in subdirs:
512 512 ds = os.path.join(nd, sd +'/')
513 513 if self.ignore(ds) or not match(ds):
514 514 subdirs.remove(sd)
515 515 subdirs.sort()
516 516 fl.sort()
517 517 for fn in fl:
518 518 fn = util.pconvert(os.path.join(d, fn))
519 519 yield 'f', fn
520 520 elif stat.S_ISREG(st.st_mode):
521 521 yield 'f', ff
522 522 else:
523 523 kind = 'unknown'
524 524 if stat.S_ISCHR(st.st_mode): kind = 'character device'
525 525 elif stat.S_ISBLK(st.st_mode): kind = 'block device'
526 526 elif stat.S_ISFIFO(st.st_mode): kind = 'fifo'
527 527 elif stat.S_ISLNK(st.st_mode): kind = 'symbolic link'
528 528 elif stat.S_ISSOCK(st.st_mode): kind = 'socket'
529 529 self.ui.warn('%s: unsupported file type (type is %s)\n' % (
530 530 util.pathto(self.getcwd(), ff),
531 531 kind))
532 532
533 533 ks = dc.keys()
534 534 ks.sort()
535 535 for k in ks:
536 536 yield 'm', k
537 537
538 538 # yield only files that match: all in dirstate, others only if
539 539 # not in .hgignore
540 540
541 541 for src, fn in util.unique(traverse()):
542 542 fn = util.normpath(fn)
543 543 if seen(fn): continue
544 544 if fn not in dc and self.ignore(fn):
545 545 continue
546 546 if match(fn):
547 547 yield src, fn
548 548
549 549 def changes(self, files=None, match=util.always):
550 550 self.read()
551 551 if not files:
552 552 dc = self.map.copy()
553 553 else:
554 554 dc = self.filterfiles(files)
555 555 lookup, modified, added, unknown = [], [], [], []
556 556 removed, deleted = [], []
557 557
558 558 for src, fn in self.walk(files, match, dc=dc):
559 559 try:
560 560 s = os.stat(os.path.join(self.root, fn))
561 561 except OSError:
562 562 continue
563 563 if not stat.S_ISREG(s.st_mode):
564 564 continue
565 565 c = dc.get(fn)
566 566 if c:
567 567 del dc[fn]
568 568 if c[0] == 'm':
569 569 modified.append(fn)
570 570 elif c[0] == 'a':
571 571 added.append(fn)
572 572 elif c[0] == 'r':
573 573 unknown.append(fn)
574 574 elif c[2] != s.st_size or (c[1] ^ s.st_mode) & 0100:
575 575 modified.append(fn)
576 576 elif c[3] != s.st_mtime:
577 577 lookup.append(fn)
578 578 else:
579 579 unknown.append(fn)
580 580
581 581 for fn, c in [(fn, c) for fn, c in dc.items() if match(fn)]:
582 582 if c[0] == 'r':
583 583 removed.append(fn)
584 584 else:
585 585 deleted.append(fn)
586 586 return (lookup, modified, added, removed + deleted, unknown)
587 587
588 588 # used to avoid circular references so destructors work
589 589 def opener(base):
590 590 p = base
591 591 def o(path, mode="r"):
592 592 if p.startswith("http://"):
593 593 f = os.path.join(p, urllib.quote(path))
594 594 return httprangereader.httprangereader(f)
595 595
596 596 f = os.path.join(p, path)
597 597
598 598 mode += "b" # for that other OS
599 599
600 600 if mode[0] != "r":
601 601 try:
602 602 s = os.stat(f)
603 603 except OSError:
604 604 d = os.path.dirname(f)
605 605 if not os.path.isdir(d):
606 606 os.makedirs(d)
607 607 else:
608 608 if s.st_nlink > 1:
609 609 file(f + ".tmp", "wb").write(file(f, "rb").read())
610 610 util.rename(f+".tmp", f)
611 611
612 612 return file(f, mode)
613 613
614 614 return o
615 615
616 616 class RepoError(Exception): pass
617 617
618 618 class localrepository:
619 619 def __init__(self, ui, path=None, create=0):
620 620 self.remote = 0
621 621 if path and path.startswith("http://"):
622 622 self.remote = 1
623 623 self.path = path
624 624 else:
625 625 if not path:
626 626 p = os.getcwd()
627 627 while not os.path.isdir(os.path.join(p, ".hg")):
628 628 oldp = p
629 629 p = os.path.dirname(p)
630 630 if p == oldp: raise RepoError("no repo found")
631 631 path = p
632 632 self.path = os.path.join(path, ".hg")
633 633
634 634 if not create and not os.path.isdir(self.path):
635 635 raise RepoError("repository %s not found" % self.path)
636 636
637 637 self.root = path
638 638 self.ui = ui
639 639
640 640 if create:
641 641 os.mkdir(self.path)
642 642 os.mkdir(self.join("data"))
643 643
644 644 self.opener = opener(self.path)
645 645 self.wopener = opener(self.root)
646 646 self.manifest = manifest(self.opener)
647 647 self.changelog = changelog(self.opener)
648 648 self.tagscache = None
649 649 self.nodetagscache = None
650 650
651 651 if not self.remote:
652 652 self.dirstate = dirstate(self.opener, ui, self.root)
653 653 try:
654 654 self.ui.readconfig(self.opener("hgrc"))
655 655 except IOError: pass
656 656
657 657 def hook(self, name, **args):
658 658 s = self.ui.config("hooks", name)
659 659 if s:
660 660 self.ui.note("running hook %s: %s\n" % (name, s))
661 661 old = {}
662 662 for k, v in args.items():
663 663 k = k.upper()
664 664 old[k] = os.environ.get(k, None)
665 665 os.environ[k] = v
666 666
667 667 r = os.system(s)
668 668
669 669 for k, v in old.items():
670 670 if v != None:
671 671 os.environ[k] = v
672 672 else:
673 673 del os.environ[k]
674 674
675 675 if r:
676 676 self.ui.warn("abort: %s hook failed with status %d!\n" %
677 677 (name, r))
678 678 return False
679 679 return True
680 680
681 681 def tags(self):
682 682 '''return a mapping of tag to node'''
683 683 if not self.tagscache:
684 684 self.tagscache = {}
685 685 def addtag(self, k, n):
686 686 try:
687 687 bin_n = bin(n)
688 688 except TypeError:
689 689 bin_n = ''
690 690 self.tagscache[k.strip()] = bin_n
691 691
692 692 try:
693 693 # read each head of the tags file, ending with the tip
694 694 # and add each tag found to the map, with "newer" ones
695 695 # taking precedence
696 696 fl = self.file(".hgtags")
697 697 h = fl.heads()
698 698 h.reverse()
699 699 for r in h:
700 700 for l in fl.revision(r).splitlines():
701 701 if l:
702 702 n, k = l.split(" ", 1)
703 703 addtag(self, k, n)
704 704 except KeyError:
705 705 pass
706 706
707 707 try:
708 708 f = self.opener("localtags")
709 709 for l in f:
710 710 n, k = l.split(" ", 1)
711 711 addtag(self, k, n)
712 712 except IOError:
713 713 pass
714 714
715 715 self.tagscache['tip'] = self.changelog.tip()
716 716
717 717 return self.tagscache
718 718
719 719 def tagslist(self):
720 720 '''return a list of tags ordered by revision'''
721 721 l = []
722 722 for t, n in self.tags().items():
723 723 try:
724 724 r = self.changelog.rev(n)
725 725 except:
726 726 r = -2 # sort to the beginning of the list if unknown
727 727 l.append((r,t,n))
728 728 l.sort()
729 729 return [(t,n) for r,t,n in l]
730 730
731 731 def nodetags(self, node):
732 732 '''return the tags associated with a node'''
733 733 if not self.nodetagscache:
734 734 self.nodetagscache = {}
735 735 for t,n in self.tags().items():
736 736 self.nodetagscache.setdefault(n,[]).append(t)
737 737 return self.nodetagscache.get(node, [])
738 738
739 739 def lookup(self, key):
740 740 try:
741 741 return self.tags()[key]
742 742 except KeyError:
743 743 try:
744 744 return self.changelog.lookup(key)
745 745 except:
746 746 raise RepoError("unknown revision '%s'" % key)
747 747
748 748 def dev(self):
749 749 if self.remote: return -1
750 750 return os.stat(self.path).st_dev
751 751
752 752 def join(self, f):
753 753 return os.path.join(self.path, f)
754 754
755 755 def wjoin(self, f):
756 756 return os.path.join(self.root, f)
757 757
758 758 def file(self, f):
759 759 if f[0] == '/': f = f[1:]
760 760 return filelog(self.opener, f)
761 761
762 762 def getcwd(self):
763 763 return self.dirstate.getcwd()
764 764
765 765 def wfile(self, f, mode='r'):
766 766 return self.wopener(f, mode)
767 767
768 768 def transaction(self):
769 769 # save dirstate for undo
770 770 try:
771 771 ds = self.opener("dirstate").read()
772 772 except IOError:
773 773 ds = ""
774 774 self.opener("journal.dirstate", "w").write(ds)
775 775
776 776 def after():
777 777 util.rename(self.join("journal"), self.join("undo"))
778 778 util.rename(self.join("journal.dirstate"),
779 779 self.join("undo.dirstate"))
780 780
781 781 return transaction.transaction(self.ui.warn, self.opener,
782 782 self.join("journal"), after)
783 783
784 784 def recover(self):
785 785 lock = self.lock()
786 786 if os.path.exists(self.join("journal")):
787 787 self.ui.status("rolling back interrupted transaction\n")
788 788 return transaction.rollback(self.opener, self.join("journal"))
789 789 else:
790 790 self.ui.warn("no interrupted transaction available\n")
791 791
792 792 def undo(self):
793 793 lock = self.lock()
794 794 if os.path.exists(self.join("undo")):
795 795 self.ui.status("rolling back last transaction\n")
796 796 transaction.rollback(self.opener, self.join("undo"))
797 797 self.dirstate = None
798 798 util.rename(self.join("undo.dirstate"), self.join("dirstate"))
799 799 self.dirstate = dirstate(self.opener, self.ui, self.root)
800 800 else:
801 801 self.ui.warn("no undo information available\n")
802 802
803 803 def lock(self, wait = 1):
804 804 try:
805 805 return lock.lock(self.join("lock"), 0)
806 806 except lock.LockHeld, inst:
807 807 if wait:
808 808 self.ui.warn("waiting for lock held by %s\n" % inst.args[0])
809 809 return lock.lock(self.join("lock"), wait)
810 810 raise inst
811 811
812 812 def rawcommit(self, files, text, user, date, p1=None, p2=None):
813 813 orig_parent = self.dirstate.parents()[0] or nullid
814 814 p1 = p1 or self.dirstate.parents()[0] or nullid
815 815 p2 = p2 or self.dirstate.parents()[1] or nullid
816 816 c1 = self.changelog.read(p1)
817 817 c2 = self.changelog.read(p2)
818 818 m1 = self.manifest.read(c1[0])
819 819 mf1 = self.manifest.readflags(c1[0])
820 820 m2 = self.manifest.read(c2[0])
821 821
822 822 if orig_parent == p1:
823 823 update_dirstate = 1
824 824 else:
825 825 update_dirstate = 0
826 826
827 827 tr = self.transaction()
828 828 mm = m1.copy()
829 829 mfm = mf1.copy()
830 830 linkrev = self.changelog.count()
831 831 for f in files:
832 832 try:
833 833 t = self.wfile(f).read()
834 834 tm = util.is_exec(self.wjoin(f), mfm.get(f, False))
835 835 r = self.file(f)
836 836 mfm[f] = tm
837 837 mm[f] = r.add(t, {}, tr, linkrev,
838 838 m1.get(f, nullid), m2.get(f, nullid))
839 839 if update_dirstate:
840 840 self.dirstate.update([f], "n")
841 841 except IOError:
842 842 try:
843 843 del mm[f]
844 844 del mfm[f]
845 845 if update_dirstate:
846 846 self.dirstate.forget([f])
847 847 except:
848 848 # deleted from p2?
849 849 pass
850 850
851 851 mnode = self.manifest.add(mm, mfm, tr, linkrev, c1[0], c2[0])
852 852 user = user or self.ui.username()
853 853 n = self.changelog.add(mnode, files, text, tr, p1, p2, user, date)
854 854 tr.close()
855 855 if update_dirstate:
856 856 self.dirstate.setparents(n, nullid)
857 857
858 858 def commit(self, files = None, text = "", user = None, date = None,
859 859 match = util.always):
860 860 commit = []
861 861 remove = []
862 862 if files:
863 863 for f in files:
864 864 s = self.dirstate.state(f)
865 865 if s in 'nmai':
866 866 commit.append(f)
867 867 elif s == 'r':
868 868 remove.append(f)
869 869 else:
870 870 self.ui.warn("%s not tracked!\n" % f)
871 871 else:
872 872 (c, a, d, u) = self.changes(match = match)
873 873 commit = c + a
874 874 remove = d
875 875
876 876 if not commit and not remove:
877 877 self.ui.status("nothing changed\n")
878 878 return
879 879
880 880 if not self.hook("precommit"):
881 881 return 1
882 882
883 883 p1, p2 = self.dirstate.parents()
884 884 c1 = self.changelog.read(p1)
885 885 c2 = self.changelog.read(p2)
886 886 m1 = self.manifest.read(c1[0])
887 887 mf1 = self.manifest.readflags(c1[0])
888 888 m2 = self.manifest.read(c2[0])
889 889 lock = self.lock()
890 890 tr = self.transaction()
891 891
892 892 # check in files
893 893 new = {}
894 894 linkrev = self.changelog.count()
895 895 commit.sort()
896 896 for f in commit:
897 897 self.ui.note(f + "\n")
898 898 try:
899 899 mf1[f] = util.is_exec(self.wjoin(f), mf1.get(f, False))
900 900 t = self.wfile(f).read()
901 901 except IOError:
902 902 self.ui.warn("trouble committing %s!\n" % f)
903 903 raise
904 904
905 905 meta = {}
906 906 cp = self.dirstate.copied(f)
907 907 if cp:
908 908 meta["copy"] = cp
909 909 meta["copyrev"] = hex(m1.get(cp, m2.get(cp, nullid)))
910 910 self.ui.debug(" %s: copy %s:%s\n" % (f, cp, meta["copyrev"]))
911 911
912 912 r = self.file(f)
913 913 fp1 = m1.get(f, nullid)
914 914 fp2 = m2.get(f, nullid)
915 915 new[f] = r.add(t, meta, tr, linkrev, fp1, fp2)
916 916
917 917 # update manifest
918 918 m1.update(new)
919 919 for f in remove:
920 920 if f in m1:
921 921 del m1[f]
922 922 mn = self.manifest.add(m1, mf1, tr, linkrev, c1[0], c2[0],
923 923 (new, remove))
924 924
925 925 # add changeset
926 926 new = new.keys()
927 927 new.sort()
928 928
929 929 if not text:
930 930 edittext = "\n" + "HG: manifest hash %s\n" % hex(mn)
931 931 edittext += "".join(["HG: changed %s\n" % f for f in new])
932 932 edittext += "".join(["HG: removed %s\n" % f for f in remove])
933 933 edittext = self.ui.edit(edittext)
934 934 if not edittext.rstrip():
935 935 return 1
936 936 text = edittext
937 937
938 938 user = user or self.ui.username()
939 939 n = self.changelog.add(mn, new, text, tr, p1, p2, user, date)
940 940
941 941 tr.close()
942 942
943 943 self.dirstate.setparents(n)
944 944 self.dirstate.update(new, "n")
945 945 self.dirstate.forget(remove)
946 946
947 947 if not self.hook("commit", node=hex(n)):
948 948 return 1
949 949
950 950 def walk(self, node = None, files = [], match = util.always):
951 951 if node:
952 952 for fn in self.manifest.read(self.changelog.read(node)[0]):
953 953 if match(fn): yield 'm', fn
954 954 else:
955 955 for src, fn in self.dirstate.walk(files, match):
956 956 yield src, fn
957 957
958 958 def changes(self, node1 = None, node2 = None, files = [],
959 959 match = util.always):
960 960 mf2, u = None, []
961 961
962 962 def fcmp(fn, mf):
963 963 t1 = self.wfile(fn).read()
964 964 t2 = self.file(fn).revision(mf[fn])
965 965 return cmp(t1, t2)
966 966
967 967 def mfmatches(node):
968 968 mf = dict(self.manifest.read(node))
969 969 for fn in mf.keys():
970 970 if not match(fn):
971 971 del mf[fn]
972 972 return mf
973 973
974 974 # are we comparing the working directory?
975 975 if not node2:
976 976 l, c, a, d, u = self.dirstate.changes(files, match)
977 977
978 978 # are we comparing working dir against its parent?
979 979 if not node1:
980 980 if l:
981 981 # do a full compare of any files that might have changed
982 982 change = self.changelog.read(self.dirstate.parents()[0])
983 983 mf2 = mfmatches(change[0])
984 984 for f in l:
985 985 if fcmp(f, mf2):
986 986 c.append(f)
987 987
988 988 for l in c, a, d, u:
989 989 l.sort()
990 990
991 991 return (c, a, d, u)
992 992
993 993 # are we comparing working dir against non-tip?
994 994 # generate a pseudo-manifest for the working dir
995 995 if not node2:
996 996 if not mf2:
997 997 change = self.changelog.read(self.dirstate.parents()[0])
998 998 mf2 = mfmatches(change[0])
999 999 for f in a + c + l:
1000 1000 mf2[f] = ""
1001 1001 for f in d:
1002 1002 if f in mf2: del mf2[f]
1003 1003 else:
1004 1004 change = self.changelog.read(node2)
1005 1005 mf2 = mfmatches(change[0])
1006 1006
1007 1007 # flush lists from dirstate before comparing manifests
1008 1008 c, a = [], []
1009 1009
1010 1010 change = self.changelog.read(node1)
1011 1011 mf1 = mfmatches(change[0])
1012 1012
1013 1013 for fn in mf2:
1014 1014 if mf1.has_key(fn):
1015 1015 if mf1[fn] != mf2[fn]:
1016 1016 if mf2[fn] != "" or fcmp(fn, mf1):
1017 1017 c.append(fn)
1018 1018 del mf1[fn]
1019 1019 else:
1020 1020 a.append(fn)
1021 1021
1022 1022 d = mf1.keys()
1023 1023
1024 1024 for l in c, a, d, u:
1025 1025 l.sort()
1026 1026
1027 1027 return (c, a, d, u)
1028 1028
1029 1029 def add(self, list):
1030 1030 for f in list:
1031 1031 p = self.wjoin(f)
1032 1032 if not os.path.exists(p):
1033 1033 self.ui.warn("%s does not exist!\n" % f)
1034 1034 elif not os.path.isfile(p):
1035 1035 self.ui.warn("%s not added: only files supported currently\n" % f)
1036 1036 elif self.dirstate.state(f) in 'an':
1037 1037 self.ui.warn("%s already tracked!\n" % f)
1038 1038 else:
1039 1039 self.dirstate.update([f], "a")
1040 1040
1041 1041 def forget(self, list):
1042 1042 for f in list:
1043 1043 if self.dirstate.state(f) not in 'ai':
1044 1044 self.ui.warn("%s not added!\n" % f)
1045 1045 else:
1046 1046 self.dirstate.forget([f])
1047 1047
1048 1048 def remove(self, list):
1049 1049 for f in list:
1050 1050 p = self.wjoin(f)
1051 1051 if os.path.exists(p):
1052 1052 self.ui.warn("%s still exists!\n" % f)
1053 1053 elif self.dirstate.state(f) == 'a':
1054 1054 self.ui.warn("%s never committed!\n" % f)
1055 1055 self.dirstate.forget([f])
1056 1056 elif f not in self.dirstate:
1057 1057 self.ui.warn("%s not tracked!\n" % f)
1058 1058 else:
1059 1059 self.dirstate.update([f], "r")
1060 1060
1061 1061 def copy(self, source, dest):
1062 1062 p = self.wjoin(dest)
1063 1063 if not os.path.exists(p):
1064 1064 self.ui.warn("%s does not exist!\n" % dest)
1065 1065 elif not os.path.isfile(p):
1066 1066 self.ui.warn("copy failed: %s is not a file\n" % dest)
1067 1067 else:
1068 1068 if self.dirstate.state(dest) == '?':
1069 1069 self.dirstate.update([dest], "a")
1070 1070 self.dirstate.copy(source, dest)
1071 1071
1072 1072 def heads(self):
1073 1073 return self.changelog.heads()
1074 1074
1075 # branchlookup returns a dict giving a list of branches for
1076 # each head. A branch is defined as the tag of a node or
1077 # the branch of the node's parents. If a node has multiple
1078 # branch tags, tags are eliminated if they are visible from other
1079 # branch tags.
1080 #
1081 # So, for this graph: a->b->c->d->e
1082 # \ /
1083 # aa -----/
1084 # a has tag 2.6.12
1085 # d has tag 2.6.13
1086 # e would have branch tags for 2.6.12 and 2.6.13. Because the node
1087 # for 2.6.12 can be reached from the node 2.6.13, that is eliminated
1088 # from the list.
1089 #
1090 # It is possible that more than one head will have the same branch tag.
1091 # callers need to check the result for multiple heads under the same
1092 # branch tag if that is a problem for them (ie checkout of a specific
1093 # branch).
1094 #
1095 # passing in a specific branch will limit the depth of the search
1096 # through the parents. It won't limit the branches returned in the
1097 # result though.
1098 def branchlookup(self, heads=None, branch=None):
1099 if not heads:
1100 heads = self.heads()
1101 headt = [ h for h in heads ]
1102 chlog = self.changelog
1103 branches = {}
1104 merges = []
1105 seenmerge = {}
1106
1107 # traverse the tree once for each head, recording in the branches
1108 # dict which tags are visible from this head. The branches
1109 # dict also records which tags are visible from each tag
1110 # while we traverse.
1111 while headt or merges:
1112 if merges:
1113 n, found = merges.pop()
1114 visit = [n]
1115 else:
1116 h = headt.pop()
1117 visit = [h]
1118 found = [h]
1119 seen = {}
1120 while visit:
1121 n = visit.pop()
1122 if n in seen:
1123 continue
1124 pp = chlog.parents(n)
1125 tags = self.nodetags(n)
1126 if tags:
1127 for x in tags:
1128 if x == 'tip':
1129 continue
1130 for f in found:
1131 branches.setdefault(f, {})[n] = 1
1132 branches.setdefault(n, {})[n] = 1
1133 break
1134 if n not in found:
1135 found.append(n)
1136 if branch in tags:
1137 continue
1138 seen[n] = 1
1139 if pp[1] != nullid and n not in seenmerge:
1140 merges.append((pp[1], [x for x in found]))
1141 seenmerge[n] = 1
1142 if pp[0] != nullid:
1143 visit.append(pp[0])
1144 # traverse the branches dict, eliminating branch tags from each
1145 # head that are visible from another branch tag for that head.
1146 out = {}
1147 viscache = {}
1148 for h in heads:
1149 def visible(node):
1150 if node in viscache:
1151 return viscache[node]
1152 ret = {}
1153 visit = [node]
1154 while visit:
1155 x = visit.pop()
1156 if x in viscache:
1157 ret.update(viscache[x])
1158 elif x not in ret:
1159 ret[x] = 1
1160 if x in branches:
1161 visit[len(visit):] = branches[x].keys()
1162 viscache[node] = ret
1163 return ret
1164 if h not in branches:
1165 continue
1166 # O(n^2), but somewhat limited. This only searches the
1167 # tags visible from a specific head, not all the tags in the
1168 # whole repo.
1169 for b in branches[h]:
1170 vis = False
1171 for bb in branches[h].keys():
1172 if b != bb:
1173 if b in visible(bb):
1174 vis = True
1175 break
1176 if not vis:
1177 l = out.setdefault(h, [])
1178 l[len(l):] = self.nodetags(b)
1179 return out
1180
1075 1181 def branches(self, nodes):
1076 1182 if not nodes: nodes = [self.changelog.tip()]
1077 1183 b = []
1078 1184 for n in nodes:
1079 1185 t = n
1080 1186 while n:
1081 1187 p = self.changelog.parents(n)
1082 1188 if p[1] != nullid or p[0] == nullid:
1083 1189 b.append((t, n, p[0], p[1]))
1084 1190 break
1085 1191 n = p[0]
1086 1192 return b
1087 1193
1088 1194 def between(self, pairs):
1089 1195 r = []
1090 1196
1091 1197 for top, bottom in pairs:
1092 1198 n, l, i = top, [], 0
1093 1199 f = 1
1094 1200
1095 1201 while n != bottom:
1096 1202 p = self.changelog.parents(n)[0]
1097 1203 if i == f:
1098 1204 l.append(n)
1099 1205 f = f * 2
1100 1206 n = p
1101 1207 i += 1
1102 1208
1103 1209 r.append(l)
1104 1210
1105 1211 return r
1106 1212
1107 1213 def newer(self, nodes):
1108 1214 m = {}
1109 1215 nl = []
1110 1216 pm = {}
1111 1217 cl = self.changelog
1112 1218 t = l = cl.count()
1113 1219
1114 1220 # find the lowest numbered node
1115 1221 for n in nodes:
1116 1222 l = min(l, cl.rev(n))
1117 1223 m[n] = 1
1118 1224
1119 1225 for i in xrange(l, t):
1120 1226 n = cl.node(i)
1121 1227 if n in m: # explicitly listed
1122 1228 pm[n] = 1
1123 1229 nl.append(n)
1124 1230 continue
1125 1231 for p in cl.parents(n):
1126 1232 if p in pm: # parent listed
1127 1233 pm[n] = 1
1128 1234 nl.append(n)
1129 1235 break
1130 1236
1131 1237 return nl
1132 1238
1133 1239 def findincoming(self, remote, base=None, heads=None):
1134 1240 m = self.changelog.nodemap
1135 1241 search = []
1136 1242 fetch = []
1137 1243 seen = {}
1138 1244 seenbranch = {}
1139 1245 if base == None:
1140 1246 base = {}
1141 1247
1142 1248 # assume we're closer to the tip than the root
1143 1249 # and start by examining the heads
1144 1250 self.ui.status("searching for changes\n")
1145 1251
1146 1252 if not heads:
1147 1253 heads = remote.heads()
1148 1254
1149 1255 unknown = []
1150 1256 for h in heads:
1151 1257 if h not in m:
1152 1258 unknown.append(h)
1153 1259 else:
1154 1260 base[h] = 1
1155 1261
1156 1262 if not unknown:
1157 1263 return None
1158 1264
1159 1265 rep = {}
1160 1266 reqcnt = 0
1161 1267
1162 1268 # search through remote branches
1163 1269 # a 'branch' here is a linear segment of history, with four parts:
1164 1270 # head, root, first parent, second parent
1165 1271 # (a branch always has two parents (or none) by definition)
1166 1272 unknown = remote.branches(unknown)
1167 1273 while unknown:
1168 1274 r = []
1169 1275 while unknown:
1170 1276 n = unknown.pop(0)
1171 1277 if n[0] in seen:
1172 1278 continue
1173 1279
1174 1280 self.ui.debug("examining %s:%s\n" % (short(n[0]), short(n[1])))
1175 1281 if n[0] == nullid:
1176 1282 break
1177 1283 if n in seenbranch:
1178 1284 self.ui.debug("branch already found\n")
1179 1285 continue
1180 1286 if n[1] and n[1] in m: # do we know the base?
1181 1287 self.ui.debug("found incomplete branch %s:%s\n"
1182 1288 % (short(n[0]), short(n[1])))
1183 1289 search.append(n) # schedule branch range for scanning
1184 1290 seenbranch[n] = 1
1185 1291 else:
1186 1292 if n[1] not in seen and n[1] not in fetch:
1187 1293 if n[2] in m and n[3] in m:
1188 1294 self.ui.debug("found new changeset %s\n" %
1189 1295 short(n[1]))
1190 1296 fetch.append(n[1]) # earliest unknown
1191 1297 base[n[2]] = 1 # latest known
1192 1298 continue
1193 1299
1194 1300 for a in n[2:4]:
1195 1301 if a not in rep:
1196 1302 r.append(a)
1197 1303 rep[a] = 1
1198 1304
1199 1305 seen[n[0]] = 1
1200 1306
1201 1307 if r:
1202 1308 reqcnt += 1
1203 1309 self.ui.debug("request %d: %s\n" %
1204 1310 (reqcnt, " ".join(map(short, r))))
1205 1311 for p in range(0, len(r), 10):
1206 1312 for b in remote.branches(r[p:p+10]):
1207 1313 self.ui.debug("received %s:%s\n" %
1208 1314 (short(b[0]), short(b[1])))
1209 1315 if b[0] not in m and b[0] not in seen:
1210 1316 unknown.append(b)
1211 1317
1212 1318 # do binary search on the branches we found
1213 1319 while search:
1214 1320 n = search.pop(0)
1215 1321 reqcnt += 1
1216 1322 l = remote.between([(n[0], n[1])])[0]
1217 1323 l.append(n[1])
1218 1324 p = n[0]
1219 1325 f = 1
1220 1326 for i in l:
1221 1327 self.ui.debug("narrowing %d:%d %s\n" % (f, len(l), short(i)))
1222 1328 if i in m:
1223 1329 if f <= 2:
1224 1330 self.ui.debug("found new branch changeset %s\n" %
1225 1331 short(p))
1226 1332 fetch.append(p)
1227 1333 base[i] = 1
1228 1334 else:
1229 1335 self.ui.debug("narrowed branch search to %s:%s\n"
1230 1336 % (short(p), short(i)))
1231 1337 search.append((p, i))
1232 1338 break
1233 1339 p, f = i, f * 2
1234 1340
1235 1341 # sanity check our fetch list
1236 1342 for f in fetch:
1237 1343 if f in m:
1238 1344 raise RepoError("already have changeset " + short(f[:4]))
1239 1345
1240 1346 if base.keys() == [nullid]:
1241 1347 self.ui.warn("warning: pulling from an unrelated repository!\n")
1242 1348
1243 1349 self.ui.note("adding new changesets starting at " +
1244 1350 " ".join([short(f) for f in fetch]) + "\n")
1245 1351
1246 1352 self.ui.debug("%d total queries\n" % reqcnt)
1247 1353
1248 1354 return fetch
1249 1355
1250 1356 def findoutgoing(self, remote, base=None, heads=None):
1251 1357 if base == None:
1252 1358 base = {}
1253 1359 self.findincoming(remote, base, heads)
1254 1360
1255 1361 remain = dict.fromkeys(self.changelog.nodemap)
1256 1362
1257 1363 # prune everything remote has from the tree
1258 1364 del remain[nullid]
1259 1365 remove = base.keys()
1260 1366 while remove:
1261 1367 n = remove.pop(0)
1262 1368 if n in remain:
1263 1369 del remain[n]
1264 1370 for p in self.changelog.parents(n):
1265 1371 remove.append(p)
1266 1372
1267 1373 # find every node whose parents have been pruned
1268 1374 subset = []
1269 1375 for n in remain:
1270 1376 p1, p2 = self.changelog.parents(n)
1271 1377 if p1 not in remain and p2 not in remain:
1272 1378 subset.append(n)
1273 1379
1274 1380 # this is the set of all roots we have to push
1275 1381 return subset
1276 1382
1277 1383 def pull(self, remote):
1278 1384 lock = self.lock()
1279 1385
1280 1386 # if we have an empty repo, fetch everything
1281 1387 if self.changelog.tip() == nullid:
1282 1388 self.ui.status("requesting all changes\n")
1283 1389 fetch = [nullid]
1284 1390 else:
1285 1391 fetch = self.findincoming(remote)
1286 1392
1287 1393 if not fetch:
1288 1394 self.ui.status("no changes found\n")
1289 1395 return 1
1290 1396
1291 1397 cg = remote.changegroup(fetch)
1292 1398 return self.addchangegroup(cg)
1293 1399
1294 1400 def push(self, remote, force=False):
1295 1401 lock = remote.lock()
1296 1402
1297 1403 base = {}
1298 1404 heads = remote.heads()
1299 1405 inc = self.findincoming(remote, base, heads)
1300 1406 if not force and inc:
1301 1407 self.ui.warn("abort: unsynced remote changes!\n")
1302 1408 self.ui.status("(did you forget to sync? use push -f to force)\n")
1303 1409 return 1
1304 1410
1305 1411 update = self.findoutgoing(remote, base)
1306 1412 if not update:
1307 1413 self.ui.status("no changes found\n")
1308 1414 return 1
1309 1415 elif not force:
1310 1416 if len(heads) < len(self.changelog.heads()):
1311 1417 self.ui.warn("abort: push creates new remote branches!\n")
1312 1418 self.ui.status("(did you forget to merge?" +
1313 1419 " use push -f to force)\n")
1314 1420 return 1
1315 1421
1316 1422 cg = self.changegroup(update)
1317 1423 return remote.addchangegroup(cg)
1318 1424
1319 1425 def changegroup(self, basenodes):
1320 1426 class genread:
1321 1427 def __init__(self, generator):
1322 1428 self.g = generator
1323 1429 self.buf = ""
1324 1430 def read(self, l):
1325 1431 while l > len(self.buf):
1326 1432 try:
1327 1433 self.buf += self.g.next()
1328 1434 except StopIteration:
1329 1435 break
1330 1436 d, self.buf = self.buf[:l], self.buf[l:]
1331 1437 return d
1332 1438
1333 1439 def gengroup():
1334 1440 nodes = self.newer(basenodes)
1335 1441
1336 1442 # construct the link map
1337 1443 linkmap = {}
1338 1444 for n in nodes:
1339 1445 linkmap[self.changelog.rev(n)] = n
1340 1446
1341 1447 # construct a list of all changed files
1342 1448 changed = {}
1343 1449 for n in nodes:
1344 1450 c = self.changelog.read(n)
1345 1451 for f in c[3]:
1346 1452 changed[f] = 1
1347 1453 changed = changed.keys()
1348 1454 changed.sort()
1349 1455
1350 1456 # the changegroup is changesets + manifests + all file revs
1351 1457 revs = [ self.changelog.rev(n) for n in nodes ]
1352 1458
1353 1459 for y in self.changelog.group(linkmap): yield y
1354 1460 for y in self.manifest.group(linkmap): yield y
1355 1461 for f in changed:
1356 1462 yield struct.pack(">l", len(f) + 4) + f
1357 1463 g = self.file(f).group(linkmap)
1358 1464 for y in g:
1359 1465 yield y
1360 1466
1361 1467 yield struct.pack(">l", 0)
1362 1468
1363 1469 return genread(gengroup())
1364 1470
1365 1471 def addchangegroup(self, source):
1366 1472
1367 1473 def getchunk():
1368 1474 d = source.read(4)
1369 1475 if not d: return ""
1370 1476 l = struct.unpack(">l", d)[0]
1371 1477 if l <= 4: return ""
1372 1478 return source.read(l - 4)
1373 1479
1374 1480 def getgroup():
1375 1481 while 1:
1376 1482 c = getchunk()
1377 1483 if not c: break
1378 1484 yield c
1379 1485
1380 1486 def csmap(x):
1381 1487 self.ui.debug("add changeset %s\n" % short(x))
1382 1488 return self.changelog.count()
1383 1489
1384 1490 def revmap(x):
1385 1491 return self.changelog.rev(x)
1386 1492
1387 1493 if not source: return
1388 1494 changesets = files = revisions = 0
1389 1495
1390 1496 tr = self.transaction()
1391 1497
1392 1498 # pull off the changeset group
1393 1499 self.ui.status("adding changesets\n")
1394 1500 co = self.changelog.tip()
1395 1501 cn = self.changelog.addgroup(getgroup(), csmap, tr, 1) # unique
1396 1502 changesets = self.changelog.rev(cn) - self.changelog.rev(co)
1397 1503
1398 1504 # pull off the manifest group
1399 1505 self.ui.status("adding manifests\n")
1400 1506 mm = self.manifest.tip()
1401 1507 mo = self.manifest.addgroup(getgroup(), revmap, tr)
1402 1508
1403 1509 # process the files
1404 1510 self.ui.status("adding file changes\n")
1405 1511 while 1:
1406 1512 f = getchunk()
1407 1513 if not f: break
1408 1514 self.ui.debug("adding %s revisions\n" % f)
1409 1515 fl = self.file(f)
1410 1516 o = fl.count()
1411 1517 n = fl.addgroup(getgroup(), revmap, tr)
1412 1518 revisions += fl.count() - o
1413 1519 files += 1
1414 1520
1415 1521 self.ui.status(("added %d changesets" +
1416 1522 " with %d changes to %d files\n")
1417 1523 % (changesets, revisions, files))
1418 1524
1419 1525 tr.close()
1420 1526
1421 1527 if not self.hook("changegroup"):
1422 1528 return 1
1423 1529
1424 1530 return
1425 1531
1426 1532 def update(self, node, allow=False, force=False, choose=None,
1427 1533 moddirstate=True):
1428 1534 pl = self.dirstate.parents()
1429 1535 if not force and pl[1] != nullid:
1430 1536 self.ui.warn("aborting: outstanding uncommitted merges\n")
1431 1537 return 1
1432 1538
1433 1539 p1, p2 = pl[0], node
1434 1540 pa = self.changelog.ancestor(p1, p2)
1435 1541 m1n = self.changelog.read(p1)[0]
1436 1542 m2n = self.changelog.read(p2)[0]
1437 1543 man = self.manifest.ancestor(m1n, m2n)
1438 1544 m1 = self.manifest.read(m1n)
1439 1545 mf1 = self.manifest.readflags(m1n)
1440 1546 m2 = self.manifest.read(m2n)
1441 1547 mf2 = self.manifest.readflags(m2n)
1442 1548 ma = self.manifest.read(man)
1443 1549 mfa = self.manifest.readflags(man)
1444 1550
1445 1551 (c, a, d, u) = self.changes()
1446 1552
1447 1553 # is this a jump, or a merge? i.e. is there a linear path
1448 1554 # from p1 to p2?
1449 1555 linear_path = (pa == p1 or pa == p2)
1450 1556
1451 1557 # resolve the manifest to determine which files
1452 1558 # we care about merging
1453 1559 self.ui.note("resolving manifests\n")
1454 1560 self.ui.debug(" force %s allow %s moddirstate %s linear %s\n" %
1455 1561 (force, allow, moddirstate, linear_path))
1456 1562 self.ui.debug(" ancestor %s local %s remote %s\n" %
1457 1563 (short(man), short(m1n), short(m2n)))
1458 1564
1459 1565 merge = {}
1460 1566 get = {}
1461 1567 remove = []
1462 1568 mark = {}
1463 1569
1464 1570 # construct a working dir manifest
1465 1571 mw = m1.copy()
1466 1572 mfw = mf1.copy()
1467 1573 umap = dict.fromkeys(u)
1468 1574
1469 1575 for f in a + c + u:
1470 1576 mw[f] = ""
1471 1577 mfw[f] = util.is_exec(self.wjoin(f), mfw.get(f, False))
1472 1578
1473 1579 for f in d:
1474 1580 if f in mw: del mw[f]
1475 1581
1476 1582 # If we're jumping between revisions (as opposed to merging),
1477 1583 # and if neither the working directory nor the target rev has
1478 1584 # the file, then we need to remove it from the dirstate, to
1479 1585 # prevent the dirstate from listing the file when it is no
1480 1586 # longer in the manifest.
1481 1587 if moddirstate and linear_path and f not in m2:
1482 1588 self.dirstate.forget((f,))
1483 1589
1484 1590 # Compare manifests
1485 1591 for f, n in mw.iteritems():
1486 1592 if choose and not choose(f): continue
1487 1593 if f in m2:
1488 1594 s = 0
1489 1595
1490 1596 # is the wfile new since m1, and match m2?
1491 1597 if f not in m1:
1492 1598 t1 = self.wfile(f).read()
1493 1599 t2 = self.file(f).revision(m2[f])
1494 1600 if cmp(t1, t2) == 0:
1495 1601 mark[f] = 1
1496 1602 n = m2[f]
1497 1603 del t1, t2
1498 1604
1499 1605 # are files different?
1500 1606 if n != m2[f]:
1501 1607 a = ma.get(f, nullid)
1502 1608 # are both different from the ancestor?
1503 1609 if n != a and m2[f] != a:
1504 1610 self.ui.debug(" %s versions differ, resolve\n" % f)
1505 1611 # merge executable bits
1506 1612 # "if we changed or they changed, change in merge"
1507 1613 a, b, c = mfa.get(f, 0), mfw[f], mf2[f]
1508 1614 mode = ((a^b) | (a^c)) ^ a
1509 1615 merge[f] = (m1.get(f, nullid), m2[f], mode)
1510 1616 s = 1
1511 1617 # are we clobbering?
1512 1618 # is remote's version newer?
1513 1619 # or are we going back in time?
1514 1620 elif force or m2[f] != a or (p2 == pa and mw[f] == m1[f]):
1515 1621 self.ui.debug(" remote %s is newer, get\n" % f)
1516 1622 get[f] = m2[f]
1517 1623 s = 1
1518 1624 else:
1519 1625 mark[f] = 1
1520 1626 elif f in umap:
1521 1627 # this unknown file is the same as the checkout
1522 1628 get[f] = m2[f]
1523 1629
1524 1630 if not s and mfw[f] != mf2[f]:
1525 1631 if force:
1526 1632 self.ui.debug(" updating permissions for %s\n" % f)
1527 1633 util.set_exec(self.wjoin(f), mf2[f])
1528 1634 else:
1529 1635 a, b, c = mfa.get(f, 0), mfw[f], mf2[f]
1530 1636 mode = ((a^b) | (a^c)) ^ a
1531 1637 if mode != b:
1532 1638 self.ui.debug(" updating permissions for %s\n" % f)
1533 1639 util.set_exec(self.wjoin(f), mode)
1534 1640 mark[f] = 1
1535 1641 del m2[f]
1536 1642 elif f in ma:
1537 1643 if n != ma[f]:
1538 1644 r = "d"
1539 1645 if not force and (linear_path or allow):
1540 1646 r = self.ui.prompt(
1541 1647 (" local changed %s which remote deleted\n" % f) +
1542 1648 "(k)eep or (d)elete?", "[kd]", "k")
1543 1649 if r == "d":
1544 1650 remove.append(f)
1545 1651 else:
1546 1652 self.ui.debug("other deleted %s\n" % f)
1547 1653 remove.append(f) # other deleted it
1548 1654 else:
1549 1655 if n == m1.get(f, nullid): # same as parent
1550 1656 if p2 == pa: # going backwards?
1551 1657 self.ui.debug("remote deleted %s\n" % f)
1552 1658 remove.append(f)
1553 1659 else:
1554 1660 self.ui.debug("local created %s, keeping\n" % f)
1555 1661 else:
1556 1662 self.ui.debug("working dir created %s, keeping\n" % f)
1557 1663
1558 1664 for f, n in m2.iteritems():
1559 1665 if choose and not choose(f): continue
1560 1666 if f[0] == "/": continue
1561 1667 if f in ma and n != ma[f]:
1562 1668 r = "k"
1563 1669 if not force and (linear_path or allow):
1564 1670 r = self.ui.prompt(
1565 1671 ("remote changed %s which local deleted\n" % f) +
1566 1672 "(k)eep or (d)elete?", "[kd]", "k")
1567 1673 if r == "k": get[f] = n
1568 1674 elif f not in ma:
1569 1675 self.ui.debug("remote created %s\n" % f)
1570 1676 get[f] = n
1571 1677 else:
1572 1678 if force or p2 == pa: # going backwards?
1573 1679 self.ui.debug("local deleted %s, recreating\n" % f)
1574 1680 get[f] = n
1575 1681 else:
1576 1682 self.ui.debug("local deleted %s\n" % f)
1577 1683
1578 1684 del mw, m1, m2, ma
1579 1685
1580 1686 if force:
1581 1687 for f in merge:
1582 1688 get[f] = merge[f][1]
1583 1689 merge = {}
1584 1690
1585 1691 if linear_path or force:
1586 1692 # we don't need to do any magic, just jump to the new rev
1587 1693 mode = 'n'
1588 1694 p1, p2 = p2, nullid
1589 1695 else:
1590 1696 if not allow:
1591 1697 self.ui.status("this update spans a branch" +
1592 1698 " affecting the following files:\n")
1593 1699 fl = merge.keys() + get.keys()
1594 1700 fl.sort()
1595 1701 for f in fl:
1596 1702 cf = ""
1597 1703 if f in merge: cf = " (resolve)"
1598 1704 self.ui.status(" %s%s\n" % (f, cf))
1599 1705 self.ui.warn("aborting update spanning branches!\n")
1600 1706 self.ui.status("(use update -m to merge across branches" +
1601 1707 " or -C to lose changes)\n")
1602 1708 return 1
1603 1709 # we have to remember what files we needed to get/change
1604 1710 # because any file that's different from either one of its
1605 1711 # parents must be in the changeset
1606 1712 mode = 'm'
1607 1713 if moddirstate:
1608 1714 self.dirstate.update(mark.keys(), "m")
1609 1715
1610 1716 if moddirstate:
1611 1717 self.dirstate.setparents(p1, p2)
1612 1718
1613 1719 # get the files we don't need to change
1614 1720 files = get.keys()
1615 1721 files.sort()
1616 1722 for f in files:
1617 1723 if f[0] == "/": continue
1618 1724 self.ui.note("getting %s\n" % f)
1619 1725 t = self.file(f).read(get[f])
1620 1726 try:
1621 1727 self.wfile(f, "w").write(t)
1622 1728 except IOError:
1623 1729 os.makedirs(os.path.dirname(self.wjoin(f)))
1624 1730 self.wfile(f, "w").write(t)
1625 1731 util.set_exec(self.wjoin(f), mf2[f])
1626 1732 if moddirstate:
1627 1733 self.dirstate.update([f], mode)
1628 1734
1629 1735 # merge the tricky bits
1630 1736 files = merge.keys()
1631 1737 files.sort()
1632 1738 for f in files:
1633 1739 self.ui.status("merging %s\n" % f)
1634 1740 m, o, flag = merge[f]
1635 1741 self.merge3(f, m, o)
1636 1742 util.set_exec(self.wjoin(f), flag)
1637 1743 if moddirstate:
1638 1744 if mode == 'm':
1639 1745 # only update dirstate on branch merge, otherwise we
1640 1746 # could mark files with changes as unchanged
1641 1747 self.dirstate.update([f], mode)
1642 1748 elif p2 == nullid:
1643 1749 # update dirstate from parent1's manifest
1644 1750 m1n = self.changelog.read(p1)[0]
1645 1751 m1 = self.manifest.read(m1n)
1646 1752 f_len = len(self.file(f).read(m1[f]))
1647 1753 self.dirstate.update([f], mode, st_size=f_len, st_mtime=0)
1648 1754 else:
1649 1755 self.ui.warn("Second parent without branch merge!?\n"
1650 1756 "Dirstate for file %s may be wrong.\n" % f)
1651 1757
1652 1758 remove.sort()
1653 1759 for f in remove:
1654 1760 self.ui.note("removing %s\n" % f)
1655 1761 try:
1656 1762 os.unlink(f)
1657 1763 except OSError, inst:
1658 1764 self.ui.warn("update failed to remove %s: %s!\n" % (f, inst))
1659 1765 # try removing directories that might now be empty
1660 1766 try: os.removedirs(os.path.dirname(f))
1661 1767 except: pass
1662 1768 if moddirstate:
1663 1769 if mode == 'n':
1664 1770 self.dirstate.forget(remove)
1665 1771 else:
1666 1772 self.dirstate.update(remove, 'r')
1667 1773
1668 1774 def merge3(self, fn, my, other):
1669 1775 """perform a 3-way merge in the working directory"""
1670 1776
1671 1777 def temp(prefix, node):
1672 1778 pre = "%s~%s." % (os.path.basename(fn), prefix)
1673 1779 (fd, name) = tempfile.mkstemp("", pre)
1674 1780 f = os.fdopen(fd, "wb")
1675 1781 f.write(fl.revision(node))
1676 1782 f.close()
1677 1783 return name
1678 1784
1679 1785 fl = self.file(fn)
1680 1786 base = fl.ancestor(my, other)
1681 1787 a = self.wjoin(fn)
1682 1788 b = temp("base", base)
1683 1789 c = temp("other", other)
1684 1790
1685 1791 self.ui.note("resolving %s\n" % fn)
1686 1792 self.ui.debug("file %s: other %s ancestor %s\n" %
1687 1793 (fn, short(other), short(base)))
1688 1794
1689 1795 cmd = (os.environ.get("HGMERGE") or self.ui.config("ui", "merge")
1690 1796 or "hgmerge")
1691 1797 r = os.system("%s %s %s %s" % (cmd, a, b, c))
1692 1798 if r:
1693 1799 self.ui.warn("merging %s failed!\n" % fn)
1694 1800
1695 1801 os.unlink(b)
1696 1802 os.unlink(c)
1697 1803
1698 1804 def verify(self):
1699 1805 filelinkrevs = {}
1700 1806 filenodes = {}
1701 1807 changesets = revisions = files = 0
1702 1808 errors = 0
1703 1809
1704 1810 seen = {}
1705 1811 self.ui.status("checking changesets\n")
1706 1812 for i in range(self.changelog.count()):
1707 1813 changesets += 1
1708 1814 n = self.changelog.node(i)
1709 1815 if n in seen:
1710 1816 self.ui.warn("duplicate changeset at revision %d\n" % i)
1711 1817 errors += 1
1712 1818 seen[n] = 1
1713 1819
1714 1820 for p in self.changelog.parents(n):
1715 1821 if p not in self.changelog.nodemap:
1716 1822 self.ui.warn("changeset %s has unknown parent %s\n" %
1717 1823 (short(n), short(p)))
1718 1824 errors += 1
1719 1825 try:
1720 1826 changes = self.changelog.read(n)
1721 1827 except Exception, inst:
1722 1828 self.ui.warn("unpacking changeset %s: %s\n" % (short(n), inst))
1723 1829 errors += 1
1724 1830
1725 1831 for f in changes[3]:
1726 1832 filelinkrevs.setdefault(f, []).append(i)
1727 1833
1728 1834 seen = {}
1729 1835 self.ui.status("checking manifests\n")
1730 1836 for i in range(self.manifest.count()):
1731 1837 n = self.manifest.node(i)
1732 1838 if n in seen:
1733 1839 self.ui.warn("duplicate manifest at revision %d\n" % i)
1734 1840 errors += 1
1735 1841 seen[n] = 1
1736 1842
1737 1843 for p in self.manifest.parents(n):
1738 1844 if p not in self.manifest.nodemap:
1739 1845 self.ui.warn("manifest %s has unknown parent %s\n" %
1740 1846 (short(n), short(p)))
1741 1847 errors += 1
1742 1848
1743 1849 try:
1744 1850 delta = mdiff.patchtext(self.manifest.delta(n))
1745 1851 except KeyboardInterrupt:
1746 1852 self.ui.warn("aborted")
1747 1853 sys.exit(0)
1748 1854 except Exception, inst:
1749 1855 self.ui.warn("unpacking manifest %s: %s\n"
1750 1856 % (short(n), inst))
1751 1857 errors += 1
1752 1858
1753 1859 ff = [ l.split('\0') for l in delta.splitlines() ]
1754 1860 for f, fn in ff:
1755 1861 filenodes.setdefault(f, {})[bin(fn[:40])] = 1
1756 1862
1757 1863 self.ui.status("crosschecking files in changesets and manifests\n")
1758 1864 for f in filenodes:
1759 1865 if f not in filelinkrevs:
1760 1866 self.ui.warn("file %s in manifest but not in changesets\n" % f)
1761 1867 errors += 1
1762 1868
1763 1869 for f in filelinkrevs:
1764 1870 if f not in filenodes:
1765 1871 self.ui.warn("file %s in changeset but not in manifest\n" % f)
1766 1872 errors += 1
1767 1873
1768 1874 self.ui.status("checking files\n")
1769 1875 ff = filenodes.keys()
1770 1876 ff.sort()
1771 1877 for f in ff:
1772 1878 if f == "/dev/null": continue
1773 1879 files += 1
1774 1880 fl = self.file(f)
1775 1881 nodes = { nullid: 1 }
1776 1882 seen = {}
1777 1883 for i in range(fl.count()):
1778 1884 revisions += 1
1779 1885 n = fl.node(i)
1780 1886
1781 1887 if n in seen:
1782 1888 self.ui.warn("%s: duplicate revision %d\n" % (f, i))
1783 1889 errors += 1
1784 1890
1785 1891 if n not in filenodes[f]:
1786 1892 self.ui.warn("%s: %d:%s not in manifests\n"
1787 1893 % (f, i, short(n)))
1788 1894 errors += 1
1789 1895 else:
1790 1896 del filenodes[f][n]
1791 1897
1792 1898 flr = fl.linkrev(n)
1793 1899 if flr not in filelinkrevs[f]:
1794 1900 self.ui.warn("%s:%s points to unexpected changeset %d\n"
1795 1901 % (f, short(n), fl.linkrev(n)))
1796 1902 errors += 1
1797 1903 else:
1798 1904 filelinkrevs[f].remove(flr)
1799 1905
1800 1906 # verify contents
1801 1907 try:
1802 1908 t = fl.read(n)
1803 1909 except Exception, inst:
1804 1910 self.ui.warn("unpacking file %s %s: %s\n"
1805 1911 % (f, short(n), inst))
1806 1912 errors += 1
1807 1913
1808 1914 # verify parents
1809 1915 (p1, p2) = fl.parents(n)
1810 1916 if p1 not in nodes:
1811 1917 self.ui.warn("file %s:%s unknown parent 1 %s" %
1812 1918 (f, short(n), short(p1)))
1813 1919 errors += 1
1814 1920 if p2 not in nodes:
1815 1921 self.ui.warn("file %s:%s unknown parent 2 %s" %
1816 1922 (f, short(n), short(p1)))
1817 1923 errors += 1
1818 1924 nodes[n] = 1
1819 1925
1820 1926 # cross-check
1821 1927 for node in filenodes[f]:
1822 1928 self.ui.warn("node %s in manifests not in %s\n"
1823 1929 % (hex(node), f))
1824 1930 errors += 1
1825 1931
1826 1932 self.ui.status("%d files, %d changesets, %d total revisions\n" %
1827 1933 (files, changesets, revisions))
1828 1934
1829 1935 if errors:
1830 1936 self.ui.warn("%d integrity errors encountered!\n" % errors)
1831 1937 return 1
1832 1938
1833 1939 class httprepository:
1834 1940 def __init__(self, ui, path):
1835 1941 # fix missing / after hostname
1836 1942 s = urlparse.urlsplit(path)
1837 1943 partial = s[2]
1838 1944 if not partial: partial = "/"
1839 1945 self.url = urlparse.urlunsplit((s[0], s[1], partial, '', ''))
1840 1946 self.ui = ui
1841 1947 no_list = [ "localhost", "127.0.0.1" ]
1842 1948 host = ui.config("http_proxy", "host")
1843 1949 if host is None:
1844 1950 host = os.environ.get("http_proxy")
1845 1951 if host and host.startswith('http://'):
1846 1952 host = host[7:]
1847 1953 user = ui.config("http_proxy", "user")
1848 1954 passwd = ui.config("http_proxy", "passwd")
1849 1955 no = ui.config("http_proxy", "no")
1850 1956 if no is None:
1851 1957 no = os.environ.get("no_proxy")
1852 1958 if no:
1853 1959 no_list = no_list + no.split(",")
1854 1960
1855 1961 no_proxy = 0
1856 1962 for h in no_list:
1857 1963 if (path.startswith("http://" + h + "/") or
1858 1964 path.startswith("http://" + h + ":") or
1859 1965 path == "http://" + h):
1860 1966 no_proxy = 1
1861 1967
1862 1968 # Note: urllib2 takes proxy values from the environment and those will
1863 1969 # take precedence
1864 1970 for env in ["HTTP_PROXY", "http_proxy", "no_proxy"]:
1865 1971 try:
1866 1972 if os.environ.has_key(env):
1867 1973 del os.environ[env]
1868 1974 except OSError:
1869 1975 pass
1870 1976
1871 1977 proxy_handler = urllib2.BaseHandler()
1872 1978 if host and not no_proxy:
1873 1979 proxy_handler = urllib2.ProxyHandler({"http" : "http://" + host})
1874 1980
1875 1981 authinfo = None
1876 1982 if user and passwd:
1877 1983 passmgr = urllib2.HTTPPasswordMgrWithDefaultRealm()
1878 1984 passmgr.add_password(None, host, user, passwd)
1879 1985 authinfo = urllib2.ProxyBasicAuthHandler(passmgr)
1880 1986
1881 1987 opener = urllib2.build_opener(proxy_handler, authinfo)
1882 1988 urllib2.install_opener(opener)
1883 1989
1884 1990 def dev(self):
1885 1991 return -1
1886 1992
1887 1993 def do_cmd(self, cmd, **args):
1888 1994 self.ui.debug("sending %s command\n" % cmd)
1889 1995 q = {"cmd": cmd}
1890 1996 q.update(args)
1891 1997 qs = urllib.urlencode(q)
1892 1998 cu = "%s?%s" % (self.url, qs)
1893 1999 resp = urllib2.urlopen(cu)
1894 2000 proto = resp.headers['content-type']
1895 2001
1896 2002 # accept old "text/plain" and "application/hg-changegroup" for now
1897 2003 if not proto.startswith('application/mercurial') and \
1898 2004 not proto.startswith('text/plain') and \
1899 2005 not proto.startswith('application/hg-changegroup'):
1900 2006 raise RepoError("'%s' does not appear to be an hg repository"
1901 2007 % self.url)
1902 2008
1903 2009 if proto.startswith('application/mercurial'):
1904 2010 version = proto[22:]
1905 2011 if float(version) > 0.1:
1906 2012 raise RepoError("'%s' uses newer protocol %s" %
1907 2013 (self.url, version))
1908 2014
1909 2015 return resp
1910 2016
1911 2017 def heads(self):
1912 2018 d = self.do_cmd("heads").read()
1913 2019 try:
1914 2020 return map(bin, d[:-1].split(" "))
1915 2021 except:
1916 2022 self.ui.warn("unexpected response:\n" + d[:400] + "\n...\n")
1917 2023 raise
1918 2024
1919 2025 def branches(self, nodes):
1920 2026 n = " ".join(map(hex, nodes))
1921 2027 d = self.do_cmd("branches", nodes=n).read()
1922 2028 try:
1923 2029 br = [ tuple(map(bin, b.split(" "))) for b in d.splitlines() ]
1924 2030 return br
1925 2031 except:
1926 2032 self.ui.warn("unexpected response:\n" + d[:400] + "\n...\n")
1927 2033 raise
1928 2034
1929 2035 def between(self, pairs):
1930 2036 n = "\n".join(["-".join(map(hex, p)) for p in pairs])
1931 2037 d = self.do_cmd("between", pairs=n).read()
1932 2038 try:
1933 2039 p = [ l and map(bin, l.split(" ")) or [] for l in d.splitlines() ]
1934 2040 return p
1935 2041 except:
1936 2042 self.ui.warn("unexpected response:\n" + d[:400] + "\n...\n")
1937 2043 raise
1938 2044
1939 2045 def changegroup(self, nodes):
1940 2046 n = " ".join(map(hex, nodes))
1941 2047 f = self.do_cmd("changegroup", roots=n)
1942 2048 bytes = 0
1943 2049
1944 2050 class zread:
1945 2051 def __init__(self, f):
1946 2052 self.zd = zlib.decompressobj()
1947 2053 self.f = f
1948 2054 self.buf = ""
1949 2055 def read(self, l):
1950 2056 while l > len(self.buf):
1951 2057 r = self.f.read(4096)
1952 2058 if r:
1953 2059 self.buf += self.zd.decompress(r)
1954 2060 else:
1955 2061 self.buf += self.zd.flush()
1956 2062 break
1957 2063 d, self.buf = self.buf[:l], self.buf[l:]
1958 2064 return d
1959 2065
1960 2066 return zread(f)
1961 2067
1962 2068 class remotelock:
1963 2069 def __init__(self, repo):
1964 2070 self.repo = repo
1965 2071 def release(self):
1966 2072 self.repo.unlock()
1967 2073 self.repo = None
1968 2074 def __del__(self):
1969 2075 if self.repo:
1970 2076 self.release()
1971 2077
1972 2078 class sshrepository:
1973 2079 def __init__(self, ui, path):
1974 2080 self.url = path
1975 2081 self.ui = ui
1976 2082
1977 2083 m = re.match(r'ssh://(([^@]+)@)?([^:/]+)(:(\d+))?(/(.*))', path)
1978 2084 if not m:
1979 2085 raise RepoError("couldn't parse destination %s" % path)
1980 2086
1981 2087 self.user = m.group(2)
1982 2088 self.host = m.group(3)
1983 2089 self.port = m.group(5)
1984 2090 self.path = m.group(7)
1985 2091
1986 2092 args = self.user and ("%s@%s" % (self.user, self.host)) or self.host
1987 2093 args = self.port and ("%s -p %s") % (args, self.port) or args
1988 2094 path = self.path or ""
1989 2095
1990 2096 if not path:
1991 2097 raise RepoError("no remote repository path specified")
1992 2098
1993 2099 cmd = "ssh %s 'hg -R %s serve --stdio'"
1994 2100 cmd = cmd % (args, path)
1995 2101
1996 2102 self.pipeo, self.pipei, self.pipee = os.popen3(cmd)
1997 2103
1998 2104 def readerr(self):
1999 2105 while 1:
2000 2106 r,w,x = select.select([self.pipee], [], [], 0)
2001 2107 if not r: break
2002 2108 l = self.pipee.readline()
2003 2109 if not l: break
2004 2110 self.ui.status("remote: ", l)
2005 2111
2006 2112 def __del__(self):
2007 2113 try:
2008 2114 self.pipeo.close()
2009 2115 self.pipei.close()
2010 2116 for l in self.pipee:
2011 2117 self.ui.status("remote: ", l)
2012 2118 self.pipee.close()
2013 2119 except:
2014 2120 pass
2015 2121
2016 2122 def dev(self):
2017 2123 return -1
2018 2124
2019 2125 def do_cmd(self, cmd, **args):
2020 2126 self.ui.debug("sending %s command\n" % cmd)
2021 2127 self.pipeo.write("%s\n" % cmd)
2022 2128 for k, v in args.items():
2023 2129 self.pipeo.write("%s %d\n" % (k, len(v)))
2024 2130 self.pipeo.write(v)
2025 2131 self.pipeo.flush()
2026 2132
2027 2133 return self.pipei
2028 2134
2029 2135 def call(self, cmd, **args):
2030 2136 r = self.do_cmd(cmd, **args)
2031 2137 l = r.readline()
2032 2138 self.readerr()
2033 2139 try:
2034 2140 l = int(l)
2035 2141 except:
2036 2142 raise RepoError("unexpected response '%s'" % l)
2037 2143 return r.read(l)
2038 2144
2039 2145 def lock(self):
2040 2146 self.call("lock")
2041 2147 return remotelock(self)
2042 2148
2043 2149 def unlock(self):
2044 2150 self.call("unlock")
2045 2151
2046 2152 def heads(self):
2047 2153 d = self.call("heads")
2048 2154 try:
2049 2155 return map(bin, d[:-1].split(" "))
2050 2156 except:
2051 2157 raise RepoError("unexpected response '%s'" % (d[:400] + "..."))
2052 2158
2053 2159 def branches(self, nodes):
2054 2160 n = " ".join(map(hex, nodes))
2055 2161 d = self.call("branches", nodes=n)
2056 2162 try:
2057 2163 br = [ tuple(map(bin, b.split(" "))) for b in d.splitlines() ]
2058 2164 return br
2059 2165 except:
2060 2166 raise RepoError("unexpected response '%s'" % (d[:400] + "..."))
2061 2167
2062 2168 def between(self, pairs):
2063 2169 n = "\n".join(["-".join(map(hex, p)) for p in pairs])
2064 2170 d = self.call("between", pairs=n)
2065 2171 try:
2066 2172 p = [ l and map(bin, l.split(" ")) or [] for l in d.splitlines() ]
2067 2173 return p
2068 2174 except:
2069 2175 raise RepoError("unexpected response '%s'" % (d[:400] + "..."))
2070 2176
2071 2177 def changegroup(self, nodes):
2072 2178 n = " ".join(map(hex, nodes))
2073 2179 f = self.do_cmd("changegroup", roots=n)
2074 2180 return self.pipei
2075 2181
2076 2182 def addchangegroup(self, cg):
2077 2183 d = self.call("addchangegroup")
2078 2184 if d:
2079 2185 raise RepoError("push refused: %s", d)
2080 2186
2081 2187 while 1:
2082 2188 d = cg.read(4096)
2083 2189 if not d: break
2084 2190 self.pipeo.write(d)
2085 2191 self.readerr()
2086 2192
2087 2193 self.pipeo.flush()
2088 2194
2089 2195 self.readerr()
2090 2196 l = int(self.pipei.readline())
2091 2197 return self.pipei.read(l) != ""
2092 2198
2093 2199 def repository(ui, path=None, create=0):
2094 2200 if path:
2095 2201 if path.startswith("http://"):
2096 2202 return httprepository(ui, path)
2097 2203 if path.startswith("hg://"):
2098 2204 return httprepository(ui, path.replace("hg://", "http://"))
2099 2205 if path.startswith("old-http://"):
2100 2206 return localrepository(ui, path.replace("old-http://", "http://"))
2101 2207 if path.startswith("ssh://"):
2102 2208 return sshrepository(ui, path)
2103 2209
2104 2210 return localrepository(ui, path, create)
General Comments 0
You need to be logged in to leave comments. Login now