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