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