##// END OF EJS Templates
abort when merging two heads and repository has local changes
Benoit Boissinot -
r1581:db10b711 default
parent child Browse files
Show More
@@ -1,2708 +1,2710 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 from node import *
10 10 from i18n import gettext as _
11 11 demandload(globals(), "os re sys signal shutil imp urllib pdb")
12 12 demandload(globals(), "fancyopts ui hg util lock revlog")
13 13 demandload(globals(), "fnmatch hgweb mdiff random signal time traceback")
14 14 demandload(globals(), "errno socket version struct atexit sets bz2")
15 15
16 16 class UnknownCommand(Exception):
17 17 """Exception raised if command is not in the command table."""
18 18 class AmbiguousCommand(Exception):
19 19 """Exception raised if command shortcut matches more than one command."""
20 20
21 21 def filterfiles(filters, files):
22 22 l = [x for x in files if x in filters]
23 23
24 24 for t in filters:
25 25 if t and t[-1] != "/":
26 26 t += "/"
27 27 l += [x for x in files if x.startswith(t)]
28 28 return l
29 29
30 30 def relpath(repo, args):
31 31 cwd = repo.getcwd()
32 32 if cwd:
33 33 return [util.normpath(os.path.join(cwd, x)) for x in args]
34 34 return args
35 35
36 36 def matchpats(repo, pats=[], opts={}, head=''):
37 37 cwd = repo.getcwd()
38 38 if not pats and cwd:
39 39 opts['include'] = [os.path.join(cwd, i) for i in opts['include']]
40 40 opts['exclude'] = [os.path.join(cwd, x) for x in opts['exclude']]
41 41 cwd = ''
42 42 return util.cmdmatcher(repo.root, cwd, pats or ['.'], opts.get('include'),
43 43 opts.get('exclude'), head) + (cwd,)
44 44
45 45 def makewalk(repo, pats, opts, head=''):
46 46 files, matchfn, anypats, cwd = matchpats(repo, pats, opts, head)
47 47 exact = dict(zip(files, files))
48 48 def walk():
49 49 for src, fn in repo.walk(files=files, match=matchfn):
50 50 yield src, fn, util.pathto(cwd, fn), fn in exact
51 51 return files, matchfn, walk()
52 52
53 53 def walk(repo, pats, opts, head=''):
54 54 files, matchfn, results = makewalk(repo, pats, opts, head)
55 55 for r in results:
56 56 yield r
57 57
58 58 def walkchangerevs(ui, repo, pats, opts):
59 59 '''Iterate over files and the revs they changed in.
60 60
61 61 Callers most commonly need to iterate backwards over the history
62 62 it is interested in. Doing so has awful (quadratic-looking)
63 63 performance, so we use iterators in a "windowed" way.
64 64
65 65 We walk a window of revisions in the desired order. Within the
66 66 window, we first walk forwards to gather data, then in the desired
67 67 order (usually backwards) to display it.
68 68
69 69 This function returns an (iterator, getchange, matchfn) tuple. The
70 70 getchange function returns the changelog entry for a numeric
71 71 revision. The iterator yields 3-tuples. They will be of one of
72 72 the following forms:
73 73
74 74 "window", incrementing, lastrev: stepping through a window,
75 75 positive if walking forwards through revs, last rev in the
76 76 sequence iterated over - use to reset state for the current window
77 77
78 78 "add", rev, fns: out-of-order traversal of the given file names
79 79 fns, which changed during revision rev - use to gather data for
80 80 possible display
81 81
82 82 "iter", rev, None: in-order traversal of the revs earlier iterated
83 83 over with "add" - use to display data'''
84 84
85 85 files, matchfn, anypats, cwd = matchpats(repo, pats, opts)
86 86
87 87 if repo.changelog.count() == 0:
88 88 return [], False, matchfn
89 89
90 90 revs = map(int, revrange(ui, repo, opts['rev'] or ['tip:0']))
91 91 wanted = {}
92 92 slowpath = anypats
93 93 window = 300
94 94 fncache = {}
95 95
96 96 chcache = {}
97 97 def getchange(rev):
98 98 ch = chcache.get(rev)
99 99 if ch is None:
100 100 chcache[rev] = ch = repo.changelog.read(repo.lookup(str(rev)))
101 101 return ch
102 102
103 103 if not slowpath and not files:
104 104 # No files, no patterns. Display all revs.
105 105 wanted = dict(zip(revs, revs))
106 106 if not slowpath:
107 107 # Only files, no patterns. Check the history of each file.
108 108 def filerevgen(filelog):
109 109 for i in xrange(filelog.count() - 1, -1, -window):
110 110 revs = []
111 111 for j in xrange(max(0, i - window), i + 1):
112 112 revs.append(filelog.linkrev(filelog.node(j)))
113 113 revs.reverse()
114 114 for rev in revs:
115 115 yield rev
116 116
117 117 minrev, maxrev = min(revs), max(revs)
118 118 for file in files:
119 119 filelog = repo.file(file)
120 120 # A zero count may be a directory or deleted file, so
121 121 # try to find matching entries on the slow path.
122 122 if filelog.count() == 0:
123 123 slowpath = True
124 124 break
125 125 for rev in filerevgen(filelog):
126 126 if rev <= maxrev:
127 127 if rev < minrev:
128 128 break
129 129 fncache.setdefault(rev, [])
130 130 fncache[rev].append(file)
131 131 wanted[rev] = 1
132 132 if slowpath:
133 133 # The slow path checks files modified in every changeset.
134 134 def changerevgen():
135 135 for i in xrange(repo.changelog.count() - 1, -1, -window):
136 136 for j in xrange(max(0, i - window), i + 1):
137 137 yield j, getchange(j)[3]
138 138
139 139 for rev, changefiles in changerevgen():
140 140 matches = filter(matchfn, changefiles)
141 141 if matches:
142 142 fncache[rev] = matches
143 143 wanted[rev] = 1
144 144
145 145 def iterate():
146 146 for i in xrange(0, len(revs), window):
147 147 yield 'window', revs[0] < revs[-1], revs[-1]
148 148 nrevs = [rev for rev in revs[i:min(i+window, len(revs))]
149 149 if rev in wanted]
150 150 srevs = list(nrevs)
151 151 srevs.sort()
152 152 for rev in srevs:
153 153 fns = fncache.get(rev) or filter(matchfn, getchange(rev)[3])
154 154 yield 'add', rev, fns
155 155 for rev in nrevs:
156 156 yield 'iter', rev, None
157 157 return iterate(), getchange, matchfn
158 158
159 159 revrangesep = ':'
160 160
161 161 def revrange(ui, repo, revs, revlog=None):
162 162 """Yield revision as strings from a list of revision specifications."""
163 163 if revlog is None:
164 164 revlog = repo.changelog
165 165 revcount = revlog.count()
166 166 def fix(val, defval):
167 167 if not val:
168 168 return defval
169 169 try:
170 170 num = int(val)
171 171 if str(num) != val:
172 172 raise ValueError
173 173 if num < 0: num += revcount
174 174 if num < 0: num = 0
175 175 elif num >= revcount:
176 176 raise ValueError
177 177 except ValueError:
178 178 try:
179 179 num = repo.changelog.rev(repo.lookup(val))
180 180 except KeyError:
181 181 try:
182 182 num = revlog.rev(revlog.lookup(val))
183 183 except KeyError:
184 184 raise util.Abort(_('invalid revision identifier %s'), val)
185 185 return num
186 186 seen = {}
187 187 for spec in revs:
188 188 if spec.find(revrangesep) >= 0:
189 189 start, end = spec.split(revrangesep, 1)
190 190 start = fix(start, 0)
191 191 end = fix(end, revcount - 1)
192 192 step = start > end and -1 or 1
193 193 for rev in xrange(start, end+step, step):
194 194 if rev in seen: continue
195 195 seen[rev] = 1
196 196 yield str(rev)
197 197 else:
198 198 rev = fix(spec, None)
199 199 if rev in seen: continue
200 200 seen[rev] = 1
201 201 yield str(rev)
202 202
203 203 def make_filename(repo, r, pat, node=None,
204 204 total=None, seqno=None, revwidth=None, pathname=None):
205 205 node_expander = {
206 206 'H': lambda: hex(node),
207 207 'R': lambda: str(r.rev(node)),
208 208 'h': lambda: short(node),
209 209 }
210 210 expander = {
211 211 '%': lambda: '%',
212 212 'b': lambda: os.path.basename(repo.root),
213 213 }
214 214
215 215 try:
216 216 if node:
217 217 expander.update(node_expander)
218 218 if node and revwidth is not None:
219 219 expander['r'] = lambda: str(r.rev(node)).zfill(revwidth)
220 220 if total is not None:
221 221 expander['N'] = lambda: str(total)
222 222 if seqno is not None:
223 223 expander['n'] = lambda: str(seqno)
224 224 if total is not None and seqno is not None:
225 225 expander['n'] = lambda:str(seqno).zfill(len(str(total)))
226 226 if pathname is not None:
227 227 expander['s'] = lambda: os.path.basename(pathname)
228 228 expander['d'] = lambda: os.path.dirname(pathname) or '.'
229 229 expander['p'] = lambda: pathname
230 230
231 231 newname = []
232 232 patlen = len(pat)
233 233 i = 0
234 234 while i < patlen:
235 235 c = pat[i]
236 236 if c == '%':
237 237 i += 1
238 238 c = pat[i]
239 239 c = expander[c]()
240 240 newname.append(c)
241 241 i += 1
242 242 return ''.join(newname)
243 243 except KeyError, inst:
244 244 raise util.Abort(_("invalid format spec '%%%s' in output file name"),
245 245 inst.args[0])
246 246
247 247 def make_file(repo, r, pat, node=None,
248 248 total=None, seqno=None, revwidth=None, mode='wb', pathname=None):
249 249 if not pat or pat == '-':
250 250 return 'w' in mode and sys.stdout or sys.stdin
251 251 if hasattr(pat, 'write') and 'w' in mode:
252 252 return pat
253 253 if hasattr(pat, 'read') and 'r' in mode:
254 254 return pat
255 255 return open(make_filename(repo, r, pat, node, total, seqno, revwidth,
256 256 pathname),
257 257 mode)
258 258
259 259 def dodiff(fp, ui, repo, node1, node2, files=None, match=util.always,
260 260 changes=None, text=False):
261 261 if not changes:
262 262 (c, a, d, u) = repo.changes(node1, node2, files, match=match)
263 263 else:
264 264 (c, a, d, u) = changes
265 265 if files:
266 266 c, a, d = map(lambda x: filterfiles(files, x), (c, a, d))
267 267
268 268 if not c and not a and not d:
269 269 return
270 270
271 271 if node2:
272 272 change = repo.changelog.read(node2)
273 273 mmap2 = repo.manifest.read(change[0])
274 274 date2 = util.datestr(change[2])
275 275 def read(f):
276 276 return repo.file(f).read(mmap2[f])
277 277 else:
278 278 date2 = util.datestr()
279 279 if not node1:
280 280 node1 = repo.dirstate.parents()[0]
281 281 def read(f):
282 282 return repo.wfile(f).read()
283 283
284 284 if ui.quiet:
285 285 r = None
286 286 else:
287 287 hexfunc = ui.verbose and hex or short
288 288 r = [hexfunc(node) for node in [node1, node2] if node]
289 289
290 290 change = repo.changelog.read(node1)
291 291 mmap = repo.manifest.read(change[0])
292 292 date1 = util.datestr(change[2])
293 293
294 294 for f in c:
295 295 to = None
296 296 if f in mmap:
297 297 to = repo.file(f).read(mmap[f])
298 298 tn = read(f)
299 299 fp.write(mdiff.unidiff(to, date1, tn, date2, f, r, text=text))
300 300 for f in a:
301 301 to = None
302 302 tn = read(f)
303 303 fp.write(mdiff.unidiff(to, date1, tn, date2, f, r, text=text))
304 304 for f in d:
305 305 to = repo.file(f).read(mmap[f])
306 306 tn = None
307 307 fp.write(mdiff.unidiff(to, date1, tn, date2, f, r, text=text))
308 308
309 309 def trimuser(ui, name, rev, revcache):
310 310 """trim the name of the user who committed a change"""
311 311 user = revcache.get(rev)
312 312 if user is None:
313 313 user = revcache[rev] = ui.shortuser(name)
314 314 return user
315 315
316 316 def show_changeset(ui, repo, rev=0, changenode=None, brinfo=None):
317 317 """show a single changeset or file revision"""
318 318 log = repo.changelog
319 319 if changenode is None:
320 320 changenode = log.node(rev)
321 321 elif not rev:
322 322 rev = log.rev(changenode)
323 323
324 324 if ui.quiet:
325 325 ui.write("%d:%s\n" % (rev, short(changenode)))
326 326 return
327 327
328 328 changes = log.read(changenode)
329 329 date = util.datestr(changes[2])
330 330
331 331 parents = [(log.rev(p), ui.verbose and hex(p) or short(p))
332 332 for p in log.parents(changenode)
333 333 if ui.debugflag or p != nullid]
334 334 if not ui.debugflag and len(parents) == 1 and parents[0][0] == rev-1:
335 335 parents = []
336 336
337 337 if ui.verbose:
338 338 ui.write(_("changeset: %d:%s\n") % (rev, hex(changenode)))
339 339 else:
340 340 ui.write(_("changeset: %d:%s\n") % (rev, short(changenode)))
341 341
342 342 for tag in repo.nodetags(changenode):
343 343 ui.status(_("tag: %s\n") % tag)
344 344 for parent in parents:
345 345 ui.write(_("parent: %d:%s\n") % parent)
346 346
347 347 if brinfo and changenode in brinfo:
348 348 br = brinfo[changenode]
349 349 ui.write(_("branch: %s\n") % " ".join(br))
350 350
351 351 ui.debug(_("manifest: %d:%s\n") % (repo.manifest.rev(changes[0]),
352 352 hex(changes[0])))
353 353 ui.status(_("user: %s\n") % changes[1])
354 354 ui.status(_("date: %s\n") % date)
355 355
356 356 if ui.debugflag:
357 357 files = repo.changes(log.parents(changenode)[0], changenode)
358 358 for key, value in zip([_("files:"), _("files+:"), _("files-:")], files):
359 359 if value:
360 360 ui.note("%-12s %s\n" % (key, " ".join(value)))
361 361 else:
362 362 ui.note(_("files: %s\n") % " ".join(changes[3]))
363 363
364 364 description = changes[4].strip()
365 365 if description:
366 366 if ui.verbose:
367 367 ui.status(_("description:\n"))
368 368 ui.status(description)
369 369 ui.status("\n\n")
370 370 else:
371 371 ui.status(_("summary: %s\n") % description.splitlines()[0])
372 372 ui.status("\n")
373 373
374 374 def show_version(ui):
375 375 """output version and copyright information"""
376 376 ui.write(_("Mercurial Distributed SCM (version %s)\n")
377 377 % version.get_version())
378 378 ui.status(_(
379 379 "\nCopyright (C) 2005 Matt Mackall <mpm@selenic.com>\n"
380 380 "This is free software; see the source for copying conditions. "
381 381 "There is NO\nwarranty; "
382 382 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
383 383 ))
384 384
385 385 def help_(ui, cmd=None, with_version=False):
386 386 """show help for a given command or all commands"""
387 387 option_lists = []
388 388 if cmd and cmd != 'shortlist':
389 389 if with_version:
390 390 show_version(ui)
391 391 ui.write('\n')
392 392 aliases, i = find(cmd)
393 393 # synopsis
394 394 ui.write("%s\n\n" % i[2])
395 395
396 396 # description
397 397 doc = i[0].__doc__
398 398 if ui.quiet:
399 399 doc = doc.splitlines(0)[0]
400 400 ui.write("%s\n" % doc.rstrip())
401 401
402 402 if not ui.quiet:
403 403 # aliases
404 404 if len(aliases) > 1:
405 405 ui.write(_("\naliases: %s\n") % ', '.join(aliases[1:]))
406 406
407 407 # options
408 408 if i[1]:
409 409 option_lists.append(("options", i[1]))
410 410
411 411 else:
412 412 # program name
413 413 if ui.verbose or with_version:
414 414 show_version(ui)
415 415 else:
416 416 ui.status(_("Mercurial Distributed SCM\n"))
417 417 ui.status('\n')
418 418
419 419 # list of commands
420 420 if cmd == "shortlist":
421 421 ui.status(_('basic commands (use "hg help" '
422 422 'for the full list or option "-v" for details):\n\n'))
423 423 elif ui.verbose:
424 424 ui.status(_('list of commands:\n\n'))
425 425 else:
426 426 ui.status(_('list of commands (use "hg help -v" '
427 427 'to show aliases and global options):\n\n'))
428 428
429 429 h = {}
430 430 cmds = {}
431 431 for c, e in table.items():
432 432 f = c.split("|")[0]
433 433 if cmd == "shortlist" and not f.startswith("^"):
434 434 continue
435 435 f = f.lstrip("^")
436 436 if not ui.debugflag and f.startswith("debug"):
437 437 continue
438 438 d = ""
439 439 if e[0].__doc__:
440 440 d = e[0].__doc__.splitlines(0)[0].rstrip()
441 441 h[f] = d
442 442 cmds[f]=c.lstrip("^")
443 443
444 444 fns = h.keys()
445 445 fns.sort()
446 446 m = max(map(len, fns))
447 447 for f in fns:
448 448 if ui.verbose:
449 449 commands = cmds[f].replace("|",", ")
450 450 ui.write(" %s:\n %s\n"%(commands,h[f]))
451 451 else:
452 452 ui.write(' %-*s %s\n' % (m, f, h[f]))
453 453
454 454 # global options
455 455 if ui.verbose:
456 456 option_lists.append(("global options", globalopts))
457 457
458 458 # list all option lists
459 459 opt_output = []
460 460 for title, options in option_lists:
461 461 opt_output.append(("\n%s:\n" % title, None))
462 462 for shortopt, longopt, default, desc in options:
463 463 opt_output.append(("%2s%s" % (shortopt and "-%s" % shortopt,
464 464 longopt and " --%s" % longopt),
465 465 "%s%s" % (desc,
466 466 default and _(" (default: %s)") % default
467 467 or "")))
468 468
469 469 if opt_output:
470 470 opts_len = max([len(line[0]) for line in opt_output if line[1]])
471 471 for first, second in opt_output:
472 472 if second:
473 473 ui.write(" %-*s %s\n" % (opts_len, first, second))
474 474 else:
475 475 ui.write("%s\n" % first)
476 476
477 477 # Commands start here, listed alphabetically
478 478
479 479 def add(ui, repo, *pats, **opts):
480 480 """add the specified files on the next commit
481 481
482 482 Schedule files to be version controlled and added to the repository.
483 483
484 484 The files will be added to the repository at the next commit.
485 485
486 486 If no names are given, add all files in the repository.
487 487 """
488 488
489 489 names = []
490 490 for src, abs, rel, exact in walk(repo, pats, opts):
491 491 if exact:
492 492 if ui.verbose: ui.status(_('adding %s\n') % rel)
493 493 names.append(abs)
494 494 elif repo.dirstate.state(abs) == '?':
495 495 ui.status(_('adding %s\n') % rel)
496 496 names.append(abs)
497 497 repo.add(names)
498 498
499 499 def addremove(ui, repo, *pats, **opts):
500 500 """add all new files, delete all missing files
501 501
502 502 Add all new files and remove all missing files from the repository.
503 503
504 504 New files are ignored if they match any of the patterns in .hgignore. As
505 505 with add, these changes take effect at the next commit.
506 506 """
507 507 add, remove = [], []
508 508 for src, abs, rel, exact in walk(repo, pats, opts):
509 509 if src == 'f' and repo.dirstate.state(abs) == '?':
510 510 add.append(abs)
511 511 if ui.verbose or not exact:
512 512 ui.status(_('adding %s\n') % rel)
513 513 if repo.dirstate.state(abs) != 'r' and not os.path.exists(rel):
514 514 remove.append(abs)
515 515 if ui.verbose or not exact:
516 516 ui.status(_('removing %s\n') % rel)
517 517 repo.add(add)
518 518 repo.remove(remove)
519 519
520 520 def annotate(ui, repo, *pats, **opts):
521 521 """show changeset information per file line
522 522
523 523 List changes in files, showing the revision id responsible for each line
524 524
525 525 This command is useful to discover who did a change or when a change took
526 526 place.
527 527
528 528 Without the -a option, annotate will avoid processing files it
529 529 detects as binary. With -a, annotate will generate an annotation
530 530 anyway, probably with undesirable results.
531 531 """
532 532 def getnode(rev):
533 533 return short(repo.changelog.node(rev))
534 534
535 535 ucache = {}
536 536 def getname(rev):
537 537 cl = repo.changelog.read(repo.changelog.node(rev))
538 538 return trimuser(ui, cl[1], rev, ucache)
539 539
540 540 dcache = {}
541 541 def getdate(rev):
542 542 datestr = dcache.get(rev)
543 543 if datestr is None:
544 544 cl = repo.changelog.read(repo.changelog.node(rev))
545 545 datestr = dcache[rev] = util.datestr(cl[2])
546 546 return datestr
547 547
548 548 if not pats:
549 549 raise util.Abort(_('at least one file name or pattern required'))
550 550
551 551 opmap = [['user', getname], ['number', str], ['changeset', getnode],
552 552 ['date', getdate]]
553 553 if not opts['user'] and not opts['changeset'] and not opts['date']:
554 554 opts['number'] = 1
555 555
556 556 if opts['rev']:
557 557 node = repo.changelog.lookup(opts['rev'])
558 558 else:
559 559 node = repo.dirstate.parents()[0]
560 560 change = repo.changelog.read(node)
561 561 mmap = repo.manifest.read(change[0])
562 562
563 563 for src, abs, rel, exact in walk(repo, pats, opts):
564 564 if abs not in mmap:
565 565 ui.warn(_("warning: %s is not in the repository!\n") % rel)
566 566 continue
567 567
568 568 f = repo.file(abs)
569 569 if not opts['text'] and util.binary(f.read(mmap[abs])):
570 570 ui.write(_("%s: binary file\n") % rel)
571 571 continue
572 572
573 573 lines = f.annotate(mmap[abs])
574 574 pieces = []
575 575
576 576 for o, f in opmap:
577 577 if opts[o]:
578 578 l = [f(n) for n, dummy in lines]
579 579 if l:
580 580 m = max(map(len, l))
581 581 pieces.append(["%*s" % (m, x) for x in l])
582 582
583 583 if pieces:
584 584 for p, l in zip(zip(*pieces), lines):
585 585 ui.write("%s: %s" % (" ".join(p), l[1]))
586 586
587 587 def bundle(ui, repo, fname, dest="default-push", **opts):
588 588 """create a changegroup file
589 589
590 590 Generate a compressed changegroup file collecting all changesets
591 591 not found in the other repository.
592 592
593 593 This file can then be transferred using conventional means and
594 594 applied to another repository with the unbundle command. This is
595 595 useful when native push and pull are not available or when
596 596 exporting an entire repository is undesirable. The standard file
597 597 extension is ".hg".
598 598
599 599 Unlike import/export, this exactly preserves all changeset
600 600 contents including permissions, rename data, and revision history.
601 601 """
602 602 f = open(fname, "wb")
603 603 dest = ui.expandpath(dest, repo.root)
604 604 other = hg.repository(ui, dest)
605 605 o = repo.findoutgoing(other)
606 606 cg = repo.changegroup(o)
607 607
608 608 try:
609 609 f.write("HG10")
610 610 z = bz2.BZ2Compressor(9)
611 611 while 1:
612 612 chunk = cg.read(4096)
613 613 if not chunk:
614 614 break
615 615 f.write(z.compress(chunk))
616 616 f.write(z.flush())
617 617 except:
618 618 os.unlink(fname)
619 619 raise
620 620
621 621 def cat(ui, repo, file1, *pats, **opts):
622 622 """output the latest or given revisions of files
623 623
624 624 Print the specified files as they were at the given revision.
625 625 If no revision is given then the tip is used.
626 626
627 627 Output may be to a file, in which case the name of the file is
628 628 given using a format string. The formatting rules are the same as
629 629 for the export command, with the following additions:
630 630
631 631 %s basename of file being printed
632 632 %d dirname of file being printed, or '.' if in repo root
633 633 %p root-relative path name of file being printed
634 634 """
635 635 mf = {}
636 636 rev = opts['rev']
637 637 if rev:
638 638 change = repo.changelog.read(repo.lookup(rev))
639 639 mf = repo.manifest.read(change[0])
640 640 for src, abs, rel, exact in walk(repo, (file1,) + pats, opts):
641 641 r = repo.file(abs)
642 642 if rev:
643 643 try:
644 644 n = mf[abs]
645 645 except (hg.RepoError, KeyError):
646 646 try:
647 647 n = r.lookup(rev)
648 648 except KeyError, inst:
649 649 raise util.Abort(_('cannot find file %s in rev %s'), rel, rev)
650 650 else:
651 651 n = r.tip()
652 652 fp = make_file(repo, r, opts['output'], node=n, pathname=abs)
653 653 fp.write(r.read(n))
654 654
655 655 def clone(ui, source, dest=None, **opts):
656 656 """make a copy of an existing repository
657 657
658 658 Create a copy of an existing repository in a new directory.
659 659
660 660 If no destination directory name is specified, it defaults to the
661 661 basename of the source.
662 662
663 663 The location of the source is added to the new repository's
664 664 .hg/hgrc file, as the default to be used for future pulls.
665 665
666 666 For efficiency, hardlinks are used for cloning whenever the source
667 667 and destination are on the same filesystem. Some filesystems,
668 668 such as AFS, implement hardlinking incorrectly, but do not report
669 669 errors. In these cases, use the --pull option to avoid
670 670 hardlinking.
671 671 """
672 672 if dest is None:
673 673 dest = os.path.basename(os.path.normpath(source))
674 674
675 675 if os.path.exists(dest):
676 676 raise util.Abort(_("destination '%s' already exists"), dest)
677 677
678 678 dest = os.path.realpath(dest)
679 679
680 680 class Dircleanup(object):
681 681 def __init__(self, dir_):
682 682 self.rmtree = shutil.rmtree
683 683 self.dir_ = dir_
684 684 os.mkdir(dir_)
685 685 def close(self):
686 686 self.dir_ = None
687 687 def __del__(self):
688 688 if self.dir_:
689 689 self.rmtree(self.dir_, True)
690 690
691 691 if opts['ssh']:
692 692 ui.setconfig("ui", "ssh", opts['ssh'])
693 693 if opts['remotecmd']:
694 694 ui.setconfig("ui", "remotecmd", opts['remotecmd'])
695 695
696 696 if not os.path.exists(source):
697 697 source = ui.expandpath(source)
698 698
699 699 d = Dircleanup(dest)
700 700 abspath = source
701 701 other = hg.repository(ui, source)
702 702
703 703 copy = False
704 704 if other.dev() != -1:
705 705 abspath = os.path.abspath(source)
706 706 if not opts['pull'] and not opts['rev']:
707 707 copy = True
708 708
709 709 if copy:
710 710 try:
711 711 # we use a lock here because if we race with commit, we
712 712 # can end up with extra data in the cloned revlogs that's
713 713 # not pointed to by changesets, thus causing verify to
714 714 # fail
715 715 l1 = lock.lock(os.path.join(source, ".hg", "lock"))
716 716 except OSError:
717 717 copy = False
718 718
719 719 if copy:
720 720 # we lock here to avoid premature writing to the target
721 721 os.mkdir(os.path.join(dest, ".hg"))
722 722 l2 = lock.lock(os.path.join(dest, ".hg", "lock"))
723 723
724 724 files = "data 00manifest.d 00manifest.i 00changelog.d 00changelog.i"
725 725 for f in files.split():
726 726 src = os.path.join(source, ".hg", f)
727 727 dst = os.path.join(dest, ".hg", f)
728 728 try:
729 729 util.copyfiles(src, dst)
730 730 except OSError, inst:
731 731 if inst.errno != errno.ENOENT: raise
732 732
733 733 repo = hg.repository(ui, dest)
734 734
735 735 else:
736 736 revs = None
737 737 if opts['rev']:
738 738 if not other.local():
739 739 raise util.Abort("clone -r not supported yet for remote repositories.")
740 740 else:
741 741 revs = [other.lookup(rev) for rev in opts['rev']]
742 742 repo = hg.repository(ui, dest, create=1)
743 743 repo.pull(other, heads = revs)
744 744
745 745 f = repo.opener("hgrc", "w", text=True)
746 746 f.write("[paths]\n")
747 747 f.write("default = %s\n" % abspath)
748 748 f.close()
749 749
750 750 if not opts['noupdate']:
751 751 update(ui, repo)
752 752
753 753 d.close()
754 754
755 755 def commit(ui, repo, *pats, **opts):
756 756 """commit the specified files or all outstanding changes
757 757
758 758 Commit changes to the given files into the repository.
759 759
760 760 If a list of files is omitted, all changes reported by "hg status"
761 761 will be commited.
762 762
763 763 The HGEDITOR or EDITOR environment variables are used to start an
764 764 editor to add a commit comment.
765 765 """
766 766 message = opts['message']
767 767 logfile = opts['logfile']
768 768
769 769 if message and logfile:
770 770 raise util.Abort(_('options --message and --logfile are mutually '
771 771 'exclusive'))
772 772 if not message and logfile:
773 773 try:
774 774 if logfile == '-':
775 775 message = sys.stdin.read()
776 776 else:
777 777 message = open(logfile).read()
778 778 except IOError, inst:
779 779 raise util.Abort(_("can't read commit message '%s': %s") %
780 780 (logfile, inst.strerror))
781 781
782 782 if opts['addremove']:
783 783 addremove(ui, repo, *pats, **opts)
784 784 fns, match, anypats, cwd = matchpats(repo, pats, opts)
785 785 if pats:
786 786 c, a, d, u = repo.changes(files=fns, match=match)
787 787 files = c + a + [fn for fn in d if repo.dirstate.state(fn) == 'r']
788 788 else:
789 789 files = []
790 790 try:
791 791 repo.commit(files, message, opts['user'], opts['date'], match)
792 792 except ValueError, inst:
793 793 raise util.Abort(str(inst))
794 794
795 795 def docopy(ui, repo, pats, opts):
796 796 cwd = repo.getcwd()
797 797 errors = 0
798 798 copied = []
799 799 targets = {}
800 800
801 801 def okaytocopy(abs, rel, exact):
802 802 reasons = {'?': _('is not managed'),
803 803 'a': _('has been marked for add')}
804 804 reason = reasons.get(repo.dirstate.state(abs))
805 805 if reason:
806 806 if exact: ui.warn(_('%s: not copying - file %s\n') % (rel, reason))
807 807 else:
808 808 return True
809 809
810 810 def copy(abssrc, relsrc, target, exact):
811 811 abstarget = util.canonpath(repo.root, cwd, target)
812 812 reltarget = util.pathto(cwd, abstarget)
813 813 prevsrc = targets.get(abstarget)
814 814 if prevsrc is not None:
815 815 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
816 816 (reltarget, abssrc, prevsrc))
817 817 return
818 818 if (not opts['after'] and os.path.exists(reltarget) or
819 819 opts['after'] and repo.dirstate.state(abstarget) not in '?r'):
820 820 if not opts['force']:
821 821 ui.warn(_('%s: not overwriting - file exists\n') %
822 822 reltarget)
823 823 return
824 824 if not opts['after']:
825 825 os.unlink(reltarget)
826 826 if opts['after']:
827 827 if not os.path.exists(reltarget):
828 828 return
829 829 else:
830 830 targetdir = os.path.dirname(reltarget) or '.'
831 831 if not os.path.isdir(targetdir):
832 832 os.makedirs(targetdir)
833 833 try:
834 834 shutil.copyfile(relsrc, reltarget)
835 835 shutil.copymode(relsrc, reltarget)
836 836 except shutil.Error, inst:
837 837 raise util.Abort(str(inst))
838 838 except IOError, inst:
839 839 if inst.errno == errno.ENOENT:
840 840 ui.warn(_('%s: deleted in working copy\n') % relsrc)
841 841 else:
842 842 ui.warn(_('%s: cannot copy - %s\n') %
843 843 (relsrc, inst.strerror))
844 844 errors += 1
845 845 return
846 846 if ui.verbose or not exact:
847 847 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
848 848 targets[abstarget] = abssrc
849 849 repo.copy(abssrc, abstarget)
850 850 copied.append((abssrc, relsrc, exact))
851 851
852 852 def targetpathfn(pat, dest, srcs):
853 853 if os.path.isdir(pat):
854 854 if pat.endswith(os.sep):
855 855 pat = pat[:-len(os.sep)]
856 856 if destdirexists:
857 857 striplen = len(os.path.split(pat)[0])
858 858 else:
859 859 striplen = len(pat)
860 860 if striplen:
861 861 striplen += len(os.sep)
862 862 res = lambda p: os.path.join(dest, p[striplen:])
863 863 elif destdirexists:
864 864 res = lambda p: os.path.join(dest, os.path.basename(p))
865 865 else:
866 866 res = lambda p: dest
867 867 return res
868 868
869 869 def targetpathafterfn(pat, dest, srcs):
870 870 if util.patkind(pat, None)[0]:
871 871 # a mercurial pattern
872 872 res = lambda p: os.path.join(dest, os.path.basename(p))
873 873 elif len(util.canonpath(repo.root, cwd, pat)) < len(srcs[0][0]):
874 874 # A directory. Either the target path contains the last
875 875 # component of the source path or it does not.
876 876 def evalpath(striplen):
877 877 score = 0
878 878 for s in srcs:
879 879 t = os.path.join(dest, s[1][striplen:])
880 880 if os.path.exists(t):
881 881 score += 1
882 882 return score
883 883
884 884 if pat.endswith(os.sep):
885 885 pat = pat[:-len(os.sep)]
886 886 striplen = len(pat) + len(os.sep)
887 887 if os.path.isdir(os.path.join(dest, os.path.split(pat)[1])):
888 888 score = evalpath(striplen)
889 889 striplen1 = len(os.path.split(pat)[0])
890 890 if striplen1:
891 891 striplen1 += len(os.sep)
892 892 if evalpath(striplen1) > score:
893 893 striplen = striplen1
894 894 res = lambda p: os.path.join(dest, p[striplen:])
895 895 else:
896 896 # a file
897 897 if destdirexists:
898 898 res = lambda p: os.path.join(dest, os.path.basename(p))
899 899 else:
900 900 res = lambda p: dest
901 901 return res
902 902
903 903
904 904 pats = list(pats)
905 905 if not pats:
906 906 raise util.Abort(_('no source or destination specified'))
907 907 if len(pats) == 1:
908 908 raise util.Abort(_('no destination specified'))
909 909 dest = pats.pop()
910 910 destdirexists = os.path.isdir(dest)
911 911 if (len(pats) > 1 or util.patkind(pats[0], None)[0]) and not destdirexists:
912 912 raise util.Abort(_('with multiple sources, destination must be an '
913 913 'existing directory'))
914 914 if opts['after']:
915 915 tfn = targetpathafterfn
916 916 else:
917 917 tfn = targetpathfn
918 918 copylist = []
919 919 for pat in pats:
920 920 srcs = []
921 921 for tag, abssrc, relsrc, exact in walk(repo, [pat], opts):
922 922 if okaytocopy(abssrc, relsrc, exact):
923 923 srcs.append((abssrc, relsrc, exact))
924 924 if not srcs:
925 925 continue
926 926 copylist.append((tfn(pat, dest, srcs), srcs))
927 927 if not copylist:
928 928 raise util.Abort(_('no files to copy'))
929 929
930 930 for targetpath, srcs in copylist:
931 931 for abssrc, relsrc, exact in srcs:
932 932 copy(abssrc, relsrc, targetpath(relsrc), exact)
933 933
934 934 if errors:
935 935 ui.warn(_('(consider using --after)\n'))
936 936 return errors, copied
937 937
938 938 def copy(ui, repo, *pats, **opts):
939 939 """mark files as copied for the next commit
940 940
941 941 Mark dest as having copies of source files. If dest is a
942 942 directory, copies are put in that directory. If dest is a file,
943 943 there can only be one source.
944 944
945 945 By default, this command copies the contents of files as they
946 946 stand in the working directory. If invoked with --after, the
947 947 operation is recorded, but no copying is performed.
948 948
949 949 This command takes effect in the next commit.
950 950
951 951 NOTE: This command should be treated as experimental. While it
952 952 should properly record copied files, this information is not yet
953 953 fully used by merge, nor fully reported by log.
954 954 """
955 955 errs, copied = docopy(ui, repo, pats, opts)
956 956 return errs
957 957
958 958 def debugancestor(ui, index, rev1, rev2):
959 959 """find the ancestor revision of two revisions in a given index"""
960 960 r = revlog.revlog(util.opener(os.getcwd()), index, "")
961 961 a = r.ancestor(r.lookup(rev1), r.lookup(rev2))
962 962 ui.write("%d:%s\n" % (r.rev(a), hex(a)))
963 963
964 964 def debugcheckstate(ui, repo):
965 965 """validate the correctness of the current dirstate"""
966 966 parent1, parent2 = repo.dirstate.parents()
967 967 repo.dirstate.read()
968 968 dc = repo.dirstate.map
969 969 keys = dc.keys()
970 970 keys.sort()
971 971 m1n = repo.changelog.read(parent1)[0]
972 972 m2n = repo.changelog.read(parent2)[0]
973 973 m1 = repo.manifest.read(m1n)
974 974 m2 = repo.manifest.read(m2n)
975 975 errors = 0
976 976 for f in dc:
977 977 state = repo.dirstate.state(f)
978 978 if state in "nr" and f not in m1:
979 979 ui.warn(_("%s in state %s, but not in manifest1\n") % (f, state))
980 980 errors += 1
981 981 if state in "a" and f in m1:
982 982 ui.warn(_("%s in state %s, but also in manifest1\n") % (f, state))
983 983 errors += 1
984 984 if state in "m" and f not in m1 and f not in m2:
985 985 ui.warn(_("%s in state %s, but not in either manifest\n") %
986 986 (f, state))
987 987 errors += 1
988 988 for f in m1:
989 989 state = repo.dirstate.state(f)
990 990 if state not in "nrm":
991 991 ui.warn(_("%s in manifest1, but listed as state %s") % (f, state))
992 992 errors += 1
993 993 if errors:
994 994 raise util.Abort(_(".hg/dirstate inconsistent with current parent's manifest"))
995 995
996 996 def debugconfig(ui):
997 997 """show combined config settings from all hgrc files"""
998 998 try:
999 999 repo = hg.repository(ui)
1000 1000 except hg.RepoError:
1001 1001 pass
1002 1002 for section, name, value in ui.walkconfig():
1003 1003 ui.write('%s.%s=%s\n' % (section, name, value))
1004 1004
1005 1005 def debugsetparents(ui, repo, rev1, rev2=None):
1006 1006 """manually set the parents of the current working directory
1007 1007
1008 1008 This is useful for writing repository conversion tools, but should
1009 1009 be used with care.
1010 1010 """
1011 1011
1012 1012 if not rev2:
1013 1013 rev2 = hex(nullid)
1014 1014
1015 1015 repo.dirstate.setparents(repo.lookup(rev1), repo.lookup(rev2))
1016 1016
1017 1017 def debugstate(ui, repo):
1018 1018 """show the contents of the current dirstate"""
1019 1019 repo.dirstate.read()
1020 1020 dc = repo.dirstate.map
1021 1021 keys = dc.keys()
1022 1022 keys.sort()
1023 1023 for file_ in keys:
1024 1024 ui.write("%c %3o %10d %s %s\n"
1025 1025 % (dc[file_][0], dc[file_][1] & 0777, dc[file_][2],
1026 1026 time.strftime("%x %X",
1027 1027 time.localtime(dc[file_][3])), file_))
1028 1028 for f in repo.dirstate.copies:
1029 1029 ui.write(_("copy: %s -> %s\n") % (repo.dirstate.copies[f], f))
1030 1030
1031 1031 def debugdata(ui, file_, rev):
1032 1032 """dump the contents of an data file revision"""
1033 1033 r = revlog.revlog(util.opener(os.getcwd()), file_[:-2] + ".i", file_)
1034 1034 try:
1035 1035 ui.write(r.revision(r.lookup(rev)))
1036 1036 except KeyError:
1037 1037 raise util.Abort(_('invalid revision identifier %s'), rev)
1038 1038
1039 1039 def debugindex(ui, file_):
1040 1040 """dump the contents of an index file"""
1041 1041 r = revlog.revlog(util.opener(os.getcwd()), file_, "")
1042 1042 ui.write(" rev offset length base linkrev" +
1043 1043 " nodeid p1 p2\n")
1044 1044 for i in range(r.count()):
1045 1045 e = r.index[i]
1046 1046 ui.write("% 6d % 9d % 7d % 6d % 7d %s %s %s\n" % (
1047 1047 i, e[0], e[1], e[2], e[3],
1048 1048 short(e[6]), short(e[4]), short(e[5])))
1049 1049
1050 1050 def debugindexdot(ui, file_):
1051 1051 """dump an index DAG as a .dot file"""
1052 1052 r = revlog.revlog(util.opener(os.getcwd()), file_, "")
1053 1053 ui.write("digraph G {\n")
1054 1054 for i in range(r.count()):
1055 1055 e = r.index[i]
1056 1056 ui.write("\t%d -> %d\n" % (r.rev(e[4]), i))
1057 1057 if e[5] != nullid:
1058 1058 ui.write("\t%d -> %d\n" % (r.rev(e[5]), i))
1059 1059 ui.write("}\n")
1060 1060
1061 1061 def debugrename(ui, repo, file, rev=None):
1062 1062 """dump rename information"""
1063 1063 r = repo.file(relpath(repo, [file])[0])
1064 1064 if rev:
1065 1065 try:
1066 1066 # assume all revision numbers are for changesets
1067 1067 n = repo.lookup(rev)
1068 1068 change = repo.changelog.read(n)
1069 1069 m = repo.manifest.read(change[0])
1070 1070 n = m[relpath(repo, [file])[0]]
1071 1071 except (hg.RepoError, KeyError):
1072 1072 n = r.lookup(rev)
1073 1073 else:
1074 1074 n = r.tip()
1075 1075 m = r.renamed(n)
1076 1076 if m:
1077 1077 ui.write(_("renamed from %s:%s\n") % (m[0], hex(m[1])))
1078 1078 else:
1079 1079 ui.write(_("not renamed\n"))
1080 1080
1081 1081 def debugwalk(ui, repo, *pats, **opts):
1082 1082 """show how files match on given patterns"""
1083 1083 items = list(walk(repo, pats, opts))
1084 1084 if not items:
1085 1085 return
1086 1086 fmt = '%%s %%-%ds %%-%ds %%s' % (
1087 1087 max([len(abs) for (src, abs, rel, exact) in items]),
1088 1088 max([len(rel) for (src, abs, rel, exact) in items]))
1089 1089 for src, abs, rel, exact in items:
1090 1090 line = fmt % (src, abs, rel, exact and 'exact' or '')
1091 1091 ui.write("%s\n" % line.rstrip())
1092 1092
1093 1093 def diff(ui, repo, *pats, **opts):
1094 1094 """diff repository (or selected files)
1095 1095
1096 1096 Show differences between revisions for the specified files.
1097 1097
1098 1098 Differences between files are shown using the unified diff format.
1099 1099
1100 1100 When two revision arguments are given, then changes are shown
1101 1101 between those revisions. If only one revision is specified then
1102 1102 that revision is compared to the working directory, and, when no
1103 1103 revisions are specified, the working directory files are compared
1104 1104 to its parent.
1105 1105
1106 1106 Without the -a option, diff will avoid generating diffs of files
1107 1107 it detects as binary. With -a, diff will generate a diff anyway,
1108 1108 probably with undesirable results.
1109 1109 """
1110 1110 node1, node2 = None, None
1111 1111 revs = [repo.lookup(x) for x in opts['rev']]
1112 1112
1113 1113 if len(revs) > 0:
1114 1114 node1 = revs[0]
1115 1115 if len(revs) > 1:
1116 1116 node2 = revs[1]
1117 1117 if len(revs) > 2:
1118 1118 raise util.Abort(_("too many revisions to diff"))
1119 1119
1120 1120 fns, matchfn, anypats, cwd = matchpats(repo, pats, opts)
1121 1121
1122 1122 dodiff(sys.stdout, ui, repo, node1, node2, fns, match=matchfn,
1123 1123 text=opts['text'])
1124 1124
1125 1125 def doexport(ui, repo, changeset, seqno, total, revwidth, opts):
1126 1126 node = repo.lookup(changeset)
1127 1127 parents = [p for p in repo.changelog.parents(node) if p != nullid]
1128 1128 prev = (parents and parents[0]) or nullid
1129 1129 change = repo.changelog.read(node)
1130 1130
1131 1131 if opts['switch_parent']:
1132 1132 parents.reverse()
1133 1133 fp = make_file(repo, repo.changelog, opts['output'],
1134 1134 node=node, total=total, seqno=seqno,
1135 1135 revwidth=revwidth)
1136 1136 if fp != sys.stdout:
1137 1137 ui.note("%s\n" % fp.name)
1138 1138
1139 1139 fp.write("# HG changeset patch\n")
1140 1140 fp.write("# User %s\n" % change[1])
1141 1141 fp.write("# Node ID %s\n" % hex(node))
1142 1142 fp.write("# Parent %s\n" % hex(prev))
1143 1143 if len(parents) > 1:
1144 1144 fp.write("# Parent %s\n" % hex(parents[1]))
1145 1145 fp.write(change[4].rstrip())
1146 1146 fp.write("\n\n")
1147 1147
1148 1148 dodiff(fp, ui, repo, prev, node, text=opts['text'])
1149 1149 if fp != sys.stdout:
1150 1150 fp.close()
1151 1151
1152 1152 def export(ui, repo, *changesets, **opts):
1153 1153 """dump the header and diffs for one or more changesets
1154 1154
1155 1155 Print the changeset header and diffs for one or more revisions.
1156 1156
1157 1157 The information shown in the changeset header is: author,
1158 1158 changeset hash, parent and commit comment.
1159 1159
1160 1160 Output may be to a file, in which case the name of the file is
1161 1161 given using a format string. The formatting rules are as follows:
1162 1162
1163 1163 %% literal "%" character
1164 1164 %H changeset hash (40 bytes of hexadecimal)
1165 1165 %N number of patches being generated
1166 1166 %R changeset revision number
1167 1167 %b basename of the exporting repository
1168 1168 %h short-form changeset hash (12 bytes of hexadecimal)
1169 1169 %n zero-padded sequence number, starting at 1
1170 1170 %r zero-padded changeset revision number
1171 1171
1172 1172 Without the -a option, export will avoid generating diffs of files
1173 1173 it detects as binary. With -a, export will generate a diff anyway,
1174 1174 probably with undesirable results.
1175 1175
1176 1176 With the --switch-parent option, the diff will be against the second
1177 1177 parent. It can be useful to review a merge.
1178 1178 """
1179 1179 if not changesets:
1180 1180 raise util.Abort(_("export requires at least one changeset"))
1181 1181 seqno = 0
1182 1182 revs = list(revrange(ui, repo, changesets))
1183 1183 total = len(revs)
1184 1184 revwidth = max(map(len, revs))
1185 1185 ui.note(len(revs) > 1 and _("Exporting patches:\n") or _("Exporting patch:\n"))
1186 1186 for cset in revs:
1187 1187 seqno += 1
1188 1188 doexport(ui, repo, cset, seqno, total, revwidth, opts)
1189 1189
1190 1190 def forget(ui, repo, *pats, **opts):
1191 1191 """don't add the specified files on the next commit
1192 1192
1193 1193 Undo an 'hg add' scheduled for the next commit.
1194 1194 """
1195 1195 forget = []
1196 1196 for src, abs, rel, exact in walk(repo, pats, opts):
1197 1197 if repo.dirstate.state(abs) == 'a':
1198 1198 forget.append(abs)
1199 1199 if ui.verbose or not exact:
1200 1200 ui.status(_('forgetting %s\n') % rel)
1201 1201 repo.forget(forget)
1202 1202
1203 1203 def grep(ui, repo, pattern, *pats, **opts):
1204 1204 """search for a pattern in specified files and revisions
1205 1205
1206 1206 Search revisions of files for a regular expression.
1207 1207
1208 1208 This command behaves differently than Unix grep. It only accepts
1209 1209 Python/Perl regexps. It searches repository history, not the
1210 1210 working directory. It always prints the revision number in which
1211 1211 a match appears.
1212 1212
1213 1213 By default, grep only prints output for the first revision of a
1214 1214 file in which it finds a match. To get it to print every revision
1215 1215 that contains a change in match status ("-" for a match that
1216 1216 becomes a non-match, or "+" for a non-match that becomes a match),
1217 1217 use the --all flag.
1218 1218 """
1219 1219 reflags = 0
1220 1220 if opts['ignore_case']:
1221 1221 reflags |= re.I
1222 1222 regexp = re.compile(pattern, reflags)
1223 1223 sep, eol = ':', '\n'
1224 1224 if opts['print0']:
1225 1225 sep = eol = '\0'
1226 1226
1227 1227 fcache = {}
1228 1228 def getfile(fn):
1229 1229 if fn not in fcache:
1230 1230 fcache[fn] = repo.file(fn)
1231 1231 return fcache[fn]
1232 1232
1233 1233 def matchlines(body):
1234 1234 begin = 0
1235 1235 linenum = 0
1236 1236 while True:
1237 1237 match = regexp.search(body, begin)
1238 1238 if not match:
1239 1239 break
1240 1240 mstart, mend = match.span()
1241 1241 linenum += body.count('\n', begin, mstart) + 1
1242 1242 lstart = body.rfind('\n', begin, mstart) + 1 or begin
1243 1243 lend = body.find('\n', mend)
1244 1244 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
1245 1245 begin = lend + 1
1246 1246
1247 1247 class linestate(object):
1248 1248 def __init__(self, line, linenum, colstart, colend):
1249 1249 self.line = line
1250 1250 self.linenum = linenum
1251 1251 self.colstart = colstart
1252 1252 self.colend = colend
1253 1253 def __eq__(self, other):
1254 1254 return self.line == other.line
1255 1255 def __hash__(self):
1256 1256 return hash(self.line)
1257 1257
1258 1258 matches = {}
1259 1259 def grepbody(fn, rev, body):
1260 1260 matches[rev].setdefault(fn, {})
1261 1261 m = matches[rev][fn]
1262 1262 for lnum, cstart, cend, line in matchlines(body):
1263 1263 s = linestate(line, lnum, cstart, cend)
1264 1264 m[s] = s
1265 1265
1266 1266 prev = {}
1267 1267 ucache = {}
1268 1268 def display(fn, rev, states, prevstates):
1269 1269 diff = list(sets.Set(states).symmetric_difference(sets.Set(prevstates)))
1270 1270 diff.sort(lambda x, y: cmp(x.linenum, y.linenum))
1271 1271 counts = {'-': 0, '+': 0}
1272 1272 filerevmatches = {}
1273 1273 for l in diff:
1274 1274 if incrementing or not opts['all']:
1275 1275 change = ((l in prevstates) and '-') or '+'
1276 1276 r = rev
1277 1277 else:
1278 1278 change = ((l in states) and '-') or '+'
1279 1279 r = prev[fn]
1280 1280 cols = [fn, str(rev)]
1281 1281 if opts['line_number']: cols.append(str(l.linenum))
1282 1282 if opts['all']: cols.append(change)
1283 1283 if opts['user']: cols.append(trimuser(ui, getchange(rev)[1], rev,
1284 1284 ucache))
1285 1285 if opts['files_with_matches']:
1286 1286 c = (fn, rev)
1287 1287 if c in filerevmatches: continue
1288 1288 filerevmatches[c] = 1
1289 1289 else:
1290 1290 cols.append(l.line)
1291 1291 ui.write(sep.join(cols), eol)
1292 1292 counts[change] += 1
1293 1293 return counts['+'], counts['-']
1294 1294
1295 1295 fstate = {}
1296 1296 skip = {}
1297 1297 changeiter, getchange, matchfn = walkchangerevs(ui, repo, pats, opts)
1298 1298 count = 0
1299 1299 incrementing = False
1300 1300 for st, rev, fns in changeiter:
1301 1301 if st == 'window':
1302 1302 incrementing = rev
1303 1303 matches.clear()
1304 1304 elif st == 'add':
1305 1305 change = repo.changelog.read(repo.lookup(str(rev)))
1306 1306 mf = repo.manifest.read(change[0])
1307 1307 matches[rev] = {}
1308 1308 for fn in fns:
1309 1309 if fn in skip: continue
1310 1310 fstate.setdefault(fn, {})
1311 1311 try:
1312 1312 grepbody(fn, rev, getfile(fn).read(mf[fn]))
1313 1313 except KeyError:
1314 1314 pass
1315 1315 elif st == 'iter':
1316 1316 states = matches[rev].items()
1317 1317 states.sort()
1318 1318 for fn, m in states:
1319 1319 if fn in skip: continue
1320 1320 if incrementing or not opts['all'] or fstate[fn]:
1321 1321 pos, neg = display(fn, rev, m, fstate[fn])
1322 1322 count += pos + neg
1323 1323 if pos and not opts['all']:
1324 1324 skip[fn] = True
1325 1325 fstate[fn] = m
1326 1326 prev[fn] = rev
1327 1327
1328 1328 if not incrementing:
1329 1329 fstate = fstate.items()
1330 1330 fstate.sort()
1331 1331 for fn, state in fstate:
1332 1332 if fn in skip: continue
1333 1333 display(fn, rev, {}, state)
1334 1334 return (count == 0 and 1) or 0
1335 1335
1336 1336 def heads(ui, repo, **opts):
1337 1337 """show current repository heads
1338 1338
1339 1339 Show all repository head changesets.
1340 1340
1341 1341 Repository "heads" are changesets that don't have children
1342 1342 changesets. They are where development generally takes place and
1343 1343 are the usual targets for update and merge operations.
1344 1344 """
1345 1345 if opts['rev']:
1346 1346 heads = repo.heads(repo.lookup(opts['rev']))
1347 1347 else:
1348 1348 heads = repo.heads()
1349 1349 br = None
1350 1350 if opts['branches']:
1351 1351 br = repo.branchlookup(heads)
1352 1352 for n in heads:
1353 1353 show_changeset(ui, repo, changenode=n, brinfo=br)
1354 1354
1355 1355 def identify(ui, repo):
1356 1356 """print information about the working copy
1357 1357
1358 1358 Print a short summary of the current state of the repo.
1359 1359
1360 1360 This summary identifies the repository state using one or two parent
1361 1361 hash identifiers, followed by a "+" if there are uncommitted changes
1362 1362 in the working directory, followed by a list of tags for this revision.
1363 1363 """
1364 1364 parents = [p for p in repo.dirstate.parents() if p != nullid]
1365 1365 if not parents:
1366 1366 ui.write(_("unknown\n"))
1367 1367 return
1368 1368
1369 1369 hexfunc = ui.verbose and hex or short
1370 1370 (c, a, d, u) = repo.changes()
1371 1371 output = ["%s%s" % ('+'.join([hexfunc(parent) for parent in parents]),
1372 1372 (c or a or d) and "+" or "")]
1373 1373
1374 1374 if not ui.quiet:
1375 1375 # multiple tags for a single parent separated by '/'
1376 1376 parenttags = ['/'.join(tags)
1377 1377 for tags in map(repo.nodetags, parents) if tags]
1378 1378 # tags for multiple parents separated by ' + '
1379 1379 if parenttags:
1380 1380 output.append(' + '.join(parenttags))
1381 1381
1382 1382 ui.write("%s\n" % ' '.join(output))
1383 1383
1384 1384 def import_(ui, repo, patch1, *patches, **opts):
1385 1385 """import an ordered set of patches
1386 1386
1387 1387 Import a list of patches and commit them individually.
1388 1388
1389 1389 If there are outstanding changes in the working directory, import
1390 1390 will abort unless given the -f flag.
1391 1391
1392 1392 If a patch looks like a mail message (its first line starts with
1393 1393 "From " or looks like an RFC822 header), it will not be applied
1394 1394 unless the -f option is used. The importer neither parses nor
1395 1395 discards mail headers, so use -f only to override the "mailness"
1396 1396 safety check, not to import a real mail message.
1397 1397 """
1398 1398 patches = (patch1,) + patches
1399 1399
1400 1400 if not opts['force']:
1401 1401 (c, a, d, u) = repo.changes()
1402 1402 if c or a or d:
1403 1403 raise util.Abort(_("outstanding uncommitted changes"))
1404 1404
1405 1405 d = opts["base"]
1406 1406 strip = opts["strip"]
1407 1407
1408 1408 mailre = re.compile(r'(?:From |[\w-]+:)')
1409 1409
1410 1410 # attempt to detect the start of a patch
1411 1411 # (this heuristic is borrowed from quilt)
1412 1412 diffre = re.compile(r'(?:Index:[ \t]|diff[ \t]|RCS file: |' +
1413 1413 'retrieving revision [0-9]+(\.[0-9]+)*$|' +
1414 1414 '(---|\*\*\*)[ \t])')
1415 1415
1416 1416 for patch in patches:
1417 1417 ui.status(_("applying %s\n") % patch)
1418 1418 pf = os.path.join(d, patch)
1419 1419
1420 1420 message = []
1421 1421 user = None
1422 1422 hgpatch = False
1423 1423 for line in file(pf):
1424 1424 line = line.rstrip()
1425 1425 if (not message and not hgpatch and
1426 1426 mailre.match(line) and not opts['force']):
1427 1427 if len(line) > 35: line = line[:32] + '...'
1428 1428 raise util.Abort(_('first line looks like a '
1429 1429 'mail header: ') + line)
1430 1430 if diffre.match(line):
1431 1431 break
1432 1432 elif hgpatch:
1433 1433 # parse values when importing the result of an hg export
1434 1434 if line.startswith("# User "):
1435 1435 user = line[7:]
1436 1436 ui.debug(_('User: %s\n') % user)
1437 1437 elif not line.startswith("# ") and line:
1438 1438 message.append(line)
1439 1439 hgpatch = False
1440 1440 elif line == '# HG changeset patch':
1441 1441 hgpatch = True
1442 1442 message = [] # We may have collected garbage
1443 1443 else:
1444 1444 message.append(line)
1445 1445
1446 1446 # make sure message isn't empty
1447 1447 if not message:
1448 1448 message = _("imported patch %s\n") % patch
1449 1449 else:
1450 1450 message = "%s\n" % '\n'.join(message)
1451 1451 ui.debug(_('message:\n%s\n') % message)
1452 1452
1453 1453 files = util.patch(strip, pf, ui)
1454 1454
1455 1455 if len(files) > 0:
1456 1456 addremove(ui, repo, *files)
1457 1457 repo.commit(files, message, user)
1458 1458
1459 1459 def incoming(ui, repo, source="default", **opts):
1460 1460 """show new changesets found in source
1461 1461
1462 1462 Show new changesets found in the specified repo or the default
1463 1463 pull repo. These are the changesets that would be pulled if a pull
1464 1464 was requested.
1465 1465
1466 1466 Currently only local repositories are supported.
1467 1467 """
1468 1468 source = ui.expandpath(source, repo.root)
1469 1469 other = hg.repository(ui, source)
1470 1470 if not other.local():
1471 1471 raise util.Abort(_("incoming doesn't work for remote repositories yet"))
1472 1472 o = repo.findincoming(other)
1473 1473 if not o:
1474 1474 return
1475 1475 o = other.changelog.nodesbetween(o)[0]
1476 1476 if opts['newest_first']:
1477 1477 o.reverse()
1478 1478 for n in o:
1479 1479 parents = [p for p in other.changelog.parents(n) if p != nullid]
1480 1480 if opts['no_merges'] and len(parents) == 2:
1481 1481 continue
1482 1482 show_changeset(ui, other, changenode=n)
1483 1483 if opts['patch']:
1484 1484 prev = (parents and parents[0]) or nullid
1485 1485 dodiff(ui, ui, other, prev, n)
1486 1486 ui.write("\n")
1487 1487
1488 1488 def init(ui, dest="."):
1489 1489 """create a new repository in the given directory
1490 1490
1491 1491 Initialize a new repository in the given directory. If the given
1492 1492 directory does not exist, it is created.
1493 1493
1494 1494 If no directory is given, the current directory is used.
1495 1495 """
1496 1496 if not os.path.exists(dest):
1497 1497 os.mkdir(dest)
1498 1498 hg.repository(ui, dest, create=1)
1499 1499
1500 1500 def locate(ui, repo, *pats, **opts):
1501 1501 """locate files matching specific patterns
1502 1502
1503 1503 Print all files under Mercurial control whose names match the
1504 1504 given patterns.
1505 1505
1506 1506 This command searches the current directory and its
1507 1507 subdirectories. To search an entire repository, move to the root
1508 1508 of the repository.
1509 1509
1510 1510 If no patterns are given to match, this command prints all file
1511 1511 names.
1512 1512
1513 1513 If you want to feed the output of this command into the "xargs"
1514 1514 command, use the "-0" option to both this command and "xargs".
1515 1515 This will avoid the problem of "xargs" treating single filenames
1516 1516 that contain white space as multiple filenames.
1517 1517 """
1518 1518 end = opts['print0'] and '\0' or '\n'
1519 1519
1520 1520 for src, abs, rel, exact in walk(repo, pats, opts, '(?:.*/|)'):
1521 1521 if repo.dirstate.state(abs) == '?':
1522 1522 continue
1523 1523 if opts['fullpath']:
1524 1524 ui.write(os.path.join(repo.root, abs), end)
1525 1525 else:
1526 1526 ui.write(rel, end)
1527 1527
1528 1528 def log(ui, repo, *pats, **opts):
1529 1529 """show revision history of entire repository or files
1530 1530
1531 1531 Print the revision history of the specified files or the entire project.
1532 1532
1533 1533 By default this command outputs: changeset id and hash, tags,
1534 1534 non-trivial parents, user, date and time, and a summary for each
1535 1535 commit. When the -v/--verbose switch is used, the list of changed
1536 1536 files and full commit message is shown.
1537 1537 """
1538 1538 class dui(object):
1539 1539 # Implement and delegate some ui protocol. Save hunks of
1540 1540 # output for later display in the desired order.
1541 1541 def __init__(self, ui):
1542 1542 self.ui = ui
1543 1543 self.hunk = {}
1544 1544 def bump(self, rev):
1545 1545 self.rev = rev
1546 1546 self.hunk[rev] = []
1547 1547 def note(self, *args):
1548 1548 if self.verbose:
1549 1549 self.write(*args)
1550 1550 def status(self, *args):
1551 1551 if not self.quiet:
1552 1552 self.write(*args)
1553 1553 def write(self, *args):
1554 1554 self.hunk[self.rev].append(args)
1555 1555 def debug(self, *args):
1556 1556 if self.debugflag:
1557 1557 self.write(*args)
1558 1558 def __getattr__(self, key):
1559 1559 return getattr(self.ui, key)
1560 1560 changeiter, getchange, matchfn = walkchangerevs(ui, repo, pats, opts)
1561 1561 for st, rev, fns in changeiter:
1562 1562 if st == 'window':
1563 1563 du = dui(ui)
1564 1564 elif st == 'add':
1565 1565 du.bump(rev)
1566 1566 changenode = repo.changelog.node(rev)
1567 1567 parents = [p for p in repo.changelog.parents(changenode)
1568 1568 if p != nullid]
1569 1569 if opts['no_merges'] and len(parents) == 2:
1570 1570 continue
1571 1571 if opts['only_merges'] and len(parents) != 2:
1572 1572 continue
1573 1573
1574 1574 br = None
1575 1575 if opts['keyword']:
1576 1576 changes = getchange(rev)
1577 1577 miss = 0
1578 1578 for k in [kw.lower() for kw in opts['keyword']]:
1579 1579 if not (k in changes[1].lower() or
1580 1580 k in changes[4].lower() or
1581 1581 k in " ".join(changes[3][:20]).lower()):
1582 1582 miss = 1
1583 1583 break
1584 1584 if miss:
1585 1585 continue
1586 1586
1587 1587 if opts['branch']:
1588 1588 br = repo.branchlookup([repo.changelog.node(rev)])
1589 1589
1590 1590 show_changeset(du, repo, rev, brinfo=br)
1591 1591 if opts['patch']:
1592 1592 prev = (parents and parents[0]) or nullid
1593 1593 dodiff(du, du, repo, prev, changenode, match=matchfn)
1594 1594 du.write("\n\n")
1595 1595 elif st == 'iter':
1596 1596 for args in du.hunk[rev]:
1597 1597 ui.write(*args)
1598 1598
1599 1599 def manifest(ui, repo, rev=None):
1600 1600 """output the latest or given revision of the project manifest
1601 1601
1602 1602 Print a list of version controlled files for the given revision.
1603 1603
1604 1604 The manifest is the list of files being version controlled. If no revision
1605 1605 is given then the tip is used.
1606 1606 """
1607 1607 if rev:
1608 1608 try:
1609 1609 # assume all revision numbers are for changesets
1610 1610 n = repo.lookup(rev)
1611 1611 change = repo.changelog.read(n)
1612 1612 n = change[0]
1613 1613 except hg.RepoError:
1614 1614 n = repo.manifest.lookup(rev)
1615 1615 else:
1616 1616 n = repo.manifest.tip()
1617 1617 m = repo.manifest.read(n)
1618 1618 mf = repo.manifest.readflags(n)
1619 1619 files = m.keys()
1620 1620 files.sort()
1621 1621
1622 1622 for f in files:
1623 1623 ui.write("%40s %3s %s\n" % (hex(m[f]), mf[f] and "755" or "644", f))
1624 1624
1625 1625 def outgoing(ui, repo, dest="default-push", **opts):
1626 1626 """show changesets not found in destination
1627 1627
1628 1628 Show changesets not found in the specified destination repo or the
1629 1629 default push repo. These are the changesets that would be pushed
1630 1630 if a push was requested.
1631 1631 """
1632 1632 dest = ui.expandpath(dest, repo.root)
1633 1633 other = hg.repository(ui, dest)
1634 1634 o = repo.findoutgoing(other)
1635 1635 o = repo.changelog.nodesbetween(o)[0]
1636 1636 if opts['newest_first']:
1637 1637 o.reverse()
1638 1638 for n in o:
1639 1639 parents = [p for p in repo.changelog.parents(n) if p != nullid]
1640 1640 if opts['no_merges'] and len(parents) == 2:
1641 1641 continue
1642 1642 show_changeset(ui, repo, changenode=n)
1643 1643 if opts['patch']:
1644 1644 prev = (parents and parents[0]) or nullid
1645 1645 dodiff(ui, ui, repo, prev, n)
1646 1646 ui.write("\n")
1647 1647
1648 1648 def parents(ui, repo, rev=None):
1649 1649 """show the parents of the working dir or revision
1650 1650
1651 1651 Print the working directory's parent revisions.
1652 1652 """
1653 1653 if rev:
1654 1654 p = repo.changelog.parents(repo.lookup(rev))
1655 1655 else:
1656 1656 p = repo.dirstate.parents()
1657 1657
1658 1658 for n in p:
1659 1659 if n != nullid:
1660 1660 show_changeset(ui, repo, changenode=n)
1661 1661
1662 1662 def paths(ui, search=None):
1663 1663 """show definition of symbolic path names
1664 1664
1665 1665 Show definition of symbolic path name NAME. If no name is given, show
1666 1666 definition of available names.
1667 1667
1668 1668 Path names are defined in the [paths] section of /etc/mercurial/hgrc
1669 1669 and $HOME/.hgrc. If run inside a repository, .hg/hgrc is used, too.
1670 1670 """
1671 1671 try:
1672 1672 repo = hg.repository(ui=ui)
1673 1673 except hg.RepoError:
1674 1674 pass
1675 1675
1676 1676 if search:
1677 1677 for name, path in ui.configitems("paths"):
1678 1678 if name == search:
1679 1679 ui.write("%s\n" % path)
1680 1680 return
1681 1681 ui.warn(_("not found!\n"))
1682 1682 return 1
1683 1683 else:
1684 1684 for name, path in ui.configitems("paths"):
1685 1685 ui.write("%s = %s\n" % (name, path))
1686 1686
1687 1687 def pull(ui, repo, source="default", **opts):
1688 1688 """pull changes from the specified source
1689 1689
1690 1690 Pull changes from a remote repository to a local one.
1691 1691
1692 1692 This finds all changes from the repository at the specified path
1693 1693 or URL and adds them to the local repository. By default, this
1694 1694 does not update the copy of the project in the working directory.
1695 1695
1696 1696 Valid URLs are of the form:
1697 1697
1698 1698 local/filesystem/path
1699 1699 http://[user@]host[:port][/path]
1700 1700 https://[user@]host[:port][/path]
1701 1701 ssh://[user@]host[:port][/path]
1702 1702
1703 1703 SSH requires an accessible shell account on the destination machine
1704 1704 and a copy of hg in the remote path. With SSH, paths are relative
1705 1705 to the remote user's home directory by default; use two slashes at
1706 1706 the start of a path to specify it as relative to the filesystem root.
1707 1707 """
1708 1708 source = ui.expandpath(source, repo.root)
1709 1709 ui.status(_('pulling from %s\n') % (source))
1710 1710
1711 1711 if opts['ssh']:
1712 1712 ui.setconfig("ui", "ssh", opts['ssh'])
1713 1713 if opts['remotecmd']:
1714 1714 ui.setconfig("ui", "remotecmd", opts['remotecmd'])
1715 1715
1716 1716 other = hg.repository(ui, source)
1717 1717 revs = None
1718 1718 if opts['rev'] and not other.local():
1719 1719 raise util.Abort("pull -r doesn't work for remote repositories yet")
1720 1720 elif opts['rev']:
1721 1721 revs = [other.lookup(rev) for rev in opts['rev']]
1722 1722 r = repo.pull(other, heads=revs)
1723 1723 if not r:
1724 1724 if opts['update']:
1725 1725 return update(ui, repo)
1726 1726 else:
1727 1727 ui.status(_("(run 'hg update' to get a working copy)\n"))
1728 1728
1729 1729 return r
1730 1730
1731 1731 def push(ui, repo, dest="default-push", force=False, ssh=None, remotecmd=None):
1732 1732 """push changes to the specified destination
1733 1733
1734 1734 Push changes from the local repository to the given destination.
1735 1735
1736 1736 This is the symmetrical operation for pull. It helps to move
1737 1737 changes from the current repository to a different one. If the
1738 1738 destination is local this is identical to a pull in that directory
1739 1739 from the current one.
1740 1740
1741 1741 By default, push will refuse to run if it detects the result would
1742 1742 increase the number of remote heads. This generally indicates the
1743 1743 the client has forgotten to sync and merge before pushing.
1744 1744
1745 1745 Valid URLs are of the form:
1746 1746
1747 1747 local/filesystem/path
1748 1748 ssh://[user@]host[:port][/path]
1749 1749
1750 1750 SSH requires an accessible shell account on the destination
1751 1751 machine and a copy of hg in the remote path.
1752 1752 """
1753 1753 dest = ui.expandpath(dest, repo.root)
1754 1754 ui.status('pushing to %s\n' % (dest))
1755 1755
1756 1756 if ssh:
1757 1757 ui.setconfig("ui", "ssh", ssh)
1758 1758 if remotecmd:
1759 1759 ui.setconfig("ui", "remotecmd", remotecmd)
1760 1760
1761 1761 other = hg.repository(ui, dest)
1762 1762 r = repo.push(other, force)
1763 1763 return r
1764 1764
1765 1765 def rawcommit(ui, repo, *flist, **rc):
1766 1766 """raw commit interface
1767 1767
1768 1768 Lowlevel commit, for use in helper scripts.
1769 1769
1770 1770 This command is not intended to be used by normal users, as it is
1771 1771 primarily useful for importing from other SCMs.
1772 1772 """
1773 1773 message = rc['message']
1774 1774 if not message and rc['logfile']:
1775 1775 try:
1776 1776 message = open(rc['logfile']).read()
1777 1777 except IOError:
1778 1778 pass
1779 1779 if not message and not rc['logfile']:
1780 1780 raise util.Abort(_("missing commit message"))
1781 1781
1782 1782 files = relpath(repo, list(flist))
1783 1783 if rc['files']:
1784 1784 files += open(rc['files']).read().splitlines()
1785 1785
1786 1786 rc['parent'] = map(repo.lookup, rc['parent'])
1787 1787
1788 1788 try:
1789 1789 repo.rawcommit(files, message, rc['user'], rc['date'], *rc['parent'])
1790 1790 except ValueError, inst:
1791 1791 raise util.Abort(str(inst))
1792 1792
1793 1793 def recover(ui, repo):
1794 1794 """roll back an interrupted transaction
1795 1795
1796 1796 Recover from an interrupted commit or pull.
1797 1797
1798 1798 This command tries to fix the repository status after an interrupted
1799 1799 operation. It should only be necessary when Mercurial suggests it.
1800 1800 """
1801 1801 if repo.recover():
1802 1802 return repo.verify()
1803 1803 return False
1804 1804
1805 1805 def remove(ui, repo, pat, *pats, **opts):
1806 1806 """remove the specified files on the next commit
1807 1807
1808 1808 Schedule the indicated files for removal from the repository.
1809 1809
1810 1810 This command schedules the files to be removed at the next commit.
1811 1811 This only removes files from the current branch, not from the
1812 1812 entire project history. If the files still exist in the working
1813 1813 directory, they will be deleted from it.
1814 1814 """
1815 1815 names = []
1816 1816 def okaytoremove(abs, rel, exact):
1817 1817 c, a, d, u = repo.changes(files = [abs])
1818 1818 reason = None
1819 1819 if c: reason = _('is modified')
1820 1820 elif a: reason = _('has been marked for add')
1821 1821 elif u: reason = _('is not managed')
1822 1822 if reason:
1823 1823 if exact: ui.warn(_('not removing %s: file %s\n') % (rel, reason))
1824 1824 else:
1825 1825 return True
1826 1826 for src, abs, rel, exact in walk(repo, (pat,) + pats, opts):
1827 1827 if okaytoremove(abs, rel, exact):
1828 1828 if ui.verbose or not exact: ui.status(_('removing %s\n') % rel)
1829 1829 names.append(abs)
1830 1830 repo.remove(names, unlink=True)
1831 1831
1832 1832 def rename(ui, repo, *pats, **opts):
1833 1833 """rename files; equivalent of copy + remove
1834 1834
1835 1835 Mark dest as copies of sources; mark sources for deletion. If
1836 1836 dest is a directory, copies are put in that directory. If dest is
1837 1837 a file, there can only be one source.
1838 1838
1839 1839 By default, this command copies the contents of files as they
1840 1840 stand in the working directory. If invoked with --after, the
1841 1841 operation is recorded, but no copying is performed.
1842 1842
1843 1843 This command takes effect in the next commit.
1844 1844
1845 1845 NOTE: This command should be treated as experimental. While it
1846 1846 should properly record rename files, this information is not yet
1847 1847 fully used by merge, nor fully reported by log.
1848 1848 """
1849 1849 errs, copied = docopy(ui, repo, pats, opts)
1850 1850 names = []
1851 1851 for abs, rel, exact in copied:
1852 1852 if ui.verbose or not exact: ui.status(_('removing %s\n') % rel)
1853 1853 names.append(abs)
1854 1854 repo.remove(names, unlink=True)
1855 1855 return errs
1856 1856
1857 1857 def revert(ui, repo, *pats, **opts):
1858 1858 """revert modified files or dirs back to their unmodified states
1859 1859
1860 1860 Revert any uncommitted modifications made to the named files or
1861 1861 directories. This restores the contents of the affected files to
1862 1862 an unmodified state.
1863 1863
1864 1864 If a file has been deleted, it is recreated. If the executable
1865 1865 mode of a file was changed, it is reset.
1866 1866
1867 1867 If names are given, all files matching the names are reverted.
1868 1868
1869 1869 If no arguments are given, all files in the repository are reverted.
1870 1870 """
1871 1871 node = opts['rev'] and repo.lookup(opts['rev']) or \
1872 1872 repo.dirstate.parents()[0]
1873 1873
1874 1874 files, choose, anypats, cwd = matchpats(repo, pats, opts)
1875 1875 (c, a, d, u) = repo.changes(match=choose)
1876 1876 repo.forget(a)
1877 1877 repo.undelete(d)
1878 1878
1879 1879 return repo.update(node, False, True, choose, False)
1880 1880
1881 1881 def root(ui, repo):
1882 1882 """print the root (top) of the current working dir
1883 1883
1884 1884 Print the root directory of the current repository.
1885 1885 """
1886 1886 ui.write(repo.root + "\n")
1887 1887
1888 1888 def serve(ui, repo, **opts):
1889 1889 """export the repository via HTTP
1890 1890
1891 1891 Start a local HTTP repository browser and pull server.
1892 1892
1893 1893 By default, the server logs accesses to stdout and errors to
1894 1894 stderr. Use the "-A" and "-E" options to log to files.
1895 1895 """
1896 1896
1897 1897 if opts["stdio"]:
1898 1898 fin, fout = sys.stdin, sys.stdout
1899 1899 sys.stdout = sys.stderr
1900 1900
1901 1901 # Prevent insertion/deletion of CRs
1902 1902 util.set_binary(fin)
1903 1903 util.set_binary(fout)
1904 1904
1905 1905 def getarg():
1906 1906 argline = fin.readline()[:-1]
1907 1907 arg, l = argline.split()
1908 1908 val = fin.read(int(l))
1909 1909 return arg, val
1910 1910 def respond(v):
1911 1911 fout.write("%d\n" % len(v))
1912 1912 fout.write(v)
1913 1913 fout.flush()
1914 1914
1915 1915 lock = None
1916 1916
1917 1917 while 1:
1918 1918 cmd = fin.readline()[:-1]
1919 1919 if cmd == '':
1920 1920 return
1921 1921 if cmd == "heads":
1922 1922 h = repo.heads()
1923 1923 respond(" ".join(map(hex, h)) + "\n")
1924 1924 if cmd == "lock":
1925 1925 lock = repo.lock()
1926 1926 respond("")
1927 1927 if cmd == "unlock":
1928 1928 if lock:
1929 1929 lock.release()
1930 1930 lock = None
1931 1931 respond("")
1932 1932 elif cmd == "branches":
1933 1933 arg, nodes = getarg()
1934 1934 nodes = map(bin, nodes.split(" "))
1935 1935 r = []
1936 1936 for b in repo.branches(nodes):
1937 1937 r.append(" ".join(map(hex, b)) + "\n")
1938 1938 respond("".join(r))
1939 1939 elif cmd == "between":
1940 1940 arg, pairs = getarg()
1941 1941 pairs = [map(bin, p.split("-")) for p in pairs.split(" ")]
1942 1942 r = []
1943 1943 for b in repo.between(pairs):
1944 1944 r.append(" ".join(map(hex, b)) + "\n")
1945 1945 respond("".join(r))
1946 1946 elif cmd == "changegroup":
1947 1947 nodes = []
1948 1948 arg, roots = getarg()
1949 1949 nodes = map(bin, roots.split(" "))
1950 1950
1951 1951 cg = repo.changegroup(nodes)
1952 1952 while 1:
1953 1953 d = cg.read(4096)
1954 1954 if not d:
1955 1955 break
1956 1956 fout.write(d)
1957 1957
1958 1958 fout.flush()
1959 1959
1960 1960 elif cmd == "addchangegroup":
1961 1961 if not lock:
1962 1962 respond("not locked")
1963 1963 continue
1964 1964 respond("")
1965 1965
1966 1966 r = repo.addchangegroup(fin)
1967 1967 respond("")
1968 1968
1969 1969 optlist = "name templates style address port ipv6 accesslog errorlog"
1970 1970 for o in optlist.split():
1971 1971 if opts[o]:
1972 1972 ui.setconfig("web", o, opts[o])
1973 1973
1974 1974 try:
1975 1975 httpd = hgweb.create_server(repo)
1976 1976 except socket.error, inst:
1977 1977 raise util.Abort('cannot start server: ' + inst.args[1])
1978 1978
1979 1979 if ui.verbose:
1980 1980 addr, port = httpd.socket.getsockname()
1981 1981 if addr == '0.0.0.0':
1982 1982 addr = socket.gethostname()
1983 1983 else:
1984 1984 try:
1985 1985 addr = socket.gethostbyaddr(addr)[0]
1986 1986 except socket.error:
1987 1987 pass
1988 1988 if port != 80:
1989 1989 ui.status(_('listening at http://%s:%d/\n') % (addr, port))
1990 1990 else:
1991 1991 ui.status(_('listening at http://%s/\n') % addr)
1992 1992 httpd.serve_forever()
1993 1993
1994 1994 def status(ui, repo, *pats, **opts):
1995 1995 """show changed files in the working directory
1996 1996
1997 1997 Show changed files in the repository. If names are
1998 1998 given, only files that match are shown.
1999 1999
2000 2000 The codes used to show the status of files are:
2001 2001 M = modified
2002 2002 A = added
2003 2003 R = removed
2004 2004 ? = not tracked
2005 2005 """
2006 2006
2007 2007 files, matchfn, anypats, cwd = matchpats(repo, pats, opts)
2008 2008 (c, a, d, u) = [[util.pathto(cwd, x) for x in n]
2009 2009 for n in repo.changes(files=files, match=matchfn)]
2010 2010
2011 2011 changetypes = [(_('modified'), 'M', c),
2012 2012 (_('added'), 'A', a),
2013 2013 (_('removed'), 'R', d),
2014 2014 (_('unknown'), '?', u)]
2015 2015
2016 2016 end = opts['print0'] and '\0' or '\n'
2017 2017
2018 2018 for opt, char, changes in ([ct for ct in changetypes if opts[ct[0]]]
2019 2019 or changetypes):
2020 2020 if opts['no_status']:
2021 2021 format = "%%s%s" % end
2022 2022 else:
2023 2023 format = "%s %%s%s" % (char, end);
2024 2024
2025 2025 for f in changes:
2026 2026 ui.write(format % f)
2027 2027
2028 2028 def tag(ui, repo, name, rev=None, **opts):
2029 2029 """add a tag for the current tip or a given revision
2030 2030
2031 2031 Name a particular revision using <name>.
2032 2032
2033 2033 Tags are used to name particular revisions of the repository and are
2034 2034 very useful to compare different revision, to go back to significant
2035 2035 earlier versions or to mark branch points as releases, etc.
2036 2036
2037 2037 If no revision is given, the tip is used.
2038 2038
2039 2039 To facilitate version control, distribution, and merging of tags,
2040 2040 they are stored as a file named ".hgtags" which is managed
2041 2041 similarly to other project files and can be hand-edited if
2042 2042 necessary.
2043 2043 """
2044 2044 if name == "tip":
2045 2045 raise util.Abort(_("the name 'tip' is reserved"))
2046 2046 if 'rev' in opts:
2047 2047 rev = opts['rev']
2048 2048 if rev:
2049 2049 r = hex(repo.lookup(rev))
2050 2050 else:
2051 2051 r = hex(repo.changelog.tip())
2052 2052
2053 2053 disallowed = (revrangesep, '\r', '\n')
2054 2054 for c in disallowed:
2055 2055 if name.find(c) >= 0:
2056 2056 raise util.Abort(_("%s cannot be used in a tag name") % repr(c))
2057 2057
2058 2058 if opts['local']:
2059 2059 repo.opener("localtags", "a").write("%s %s\n" % (r, name))
2060 2060 return
2061 2061
2062 2062 (c, a, d, u) = repo.changes()
2063 2063 for x in (c, a, d, u):
2064 2064 if ".hgtags" in x:
2065 2065 raise util.Abort(_("working copy of .hgtags is changed "
2066 2066 "(please commit .hgtags manually)"))
2067 2067
2068 2068 repo.wfile(".hgtags", "ab").write("%s %s\n" % (r, name))
2069 2069 if repo.dirstate.state(".hgtags") == '?':
2070 2070 repo.add([".hgtags"])
2071 2071
2072 2072 message = (opts['message'] or
2073 2073 _("Added tag %s for changeset %s") % (name, r))
2074 2074 try:
2075 2075 repo.commit([".hgtags"], message, opts['user'], opts['date'])
2076 2076 except ValueError, inst:
2077 2077 raise util.Abort(str(inst))
2078 2078
2079 2079 def tags(ui, repo):
2080 2080 """list repository tags
2081 2081
2082 2082 List the repository tags.
2083 2083
2084 2084 This lists both regular and local tags.
2085 2085 """
2086 2086
2087 2087 l = repo.tagslist()
2088 2088 l.reverse()
2089 2089 for t, n in l:
2090 2090 try:
2091 2091 r = "%5d:%s" % (repo.changelog.rev(n), hex(n))
2092 2092 except KeyError:
2093 2093 r = " ?:?"
2094 2094 ui.write("%-30s %s\n" % (t, r))
2095 2095
2096 2096 def tip(ui, repo):
2097 2097 """show the tip revision
2098 2098
2099 2099 Show the tip revision.
2100 2100 """
2101 2101 n = repo.changelog.tip()
2102 2102 show_changeset(ui, repo, changenode=n)
2103 2103
2104 2104 def unbundle(ui, repo, fname):
2105 2105 """apply a changegroup file
2106 2106
2107 2107 Apply a compressed changegroup file generated by the bundle
2108 2108 command.
2109 2109 """
2110 2110 f = urllib.urlopen(fname)
2111 2111
2112 2112 if f.read(4) != "HG10":
2113 2113 raise util.Abort(_("%s: not a Mercurial bundle file") % fname)
2114 2114
2115 2115 def bzgenerator(f):
2116 2116 zd = bz2.BZ2Decompressor()
2117 2117 for chunk in f:
2118 2118 yield zd.decompress(chunk)
2119 2119
2120 2120 bzgen = bzgenerator(util.filechunkiter(f, 4096))
2121 2121 repo.addchangegroup(util.chunkbuffer(bzgen))
2122 2122
2123 2123 def undo(ui, repo):
2124 2124 """undo the last commit or pull
2125 2125
2126 2126 Roll back the last pull or commit transaction on the
2127 2127 repository, restoring the project to its earlier state.
2128 2128
2129 2129 This command should be used with care. There is only one level of
2130 2130 undo and there is no redo.
2131 2131
2132 2132 This command is not intended for use on public repositories. Once
2133 2133 a change is visible for pull by other users, undoing it locally is
2134 2134 ineffective.
2135 2135 """
2136 2136 repo.undo()
2137 2137
2138 def update(ui, repo, node=None, merge=False, clean=False, branch=None):
2138 def update(ui, repo, node=None, merge=False, clean=False, force=None,
2139 branch=None):
2139 2140 """update or merge working directory
2140 2141
2141 2142 Update the working directory to the specified revision.
2142 2143
2143 2144 If there are no outstanding changes in the working directory and
2144 2145 there is a linear relationship between the current version and the
2145 2146 requested version, the result is the requested version.
2146 2147
2147 2148 Otherwise the result is a merge between the contents of the
2148 2149 current working directory and the requested version. Files that
2149 2150 changed between either parent are marked as changed for the next
2150 2151 commit and a commit must be performed before any further updates
2151 2152 are allowed.
2152 2153
2153 2154 By default, update will refuse to run if doing so would require
2154 2155 merging or discarding local changes.
2155 2156 """
2156 2157 if branch:
2157 2158 br = repo.branchlookup(branch=branch)
2158 2159 found = []
2159 2160 for x in br:
2160 2161 if branch in br[x]:
2161 2162 found.append(x)
2162 2163 if len(found) > 1:
2163 2164 ui.warn(_("Found multiple heads for %s\n") % branch)
2164 2165 for x in found:
2165 2166 show_changeset(ui, repo, changenode=x, brinfo=br)
2166 2167 return 1
2167 2168 if len(found) == 1:
2168 2169 node = found[0]
2169 2170 ui.warn(_("Using head %s for branch %s\n") % (short(node), branch))
2170 2171 else:
2171 2172 ui.warn(_("branch %s not found\n") % (branch))
2172 2173 return 1
2173 2174 else:
2174 2175 node = node and repo.lookup(node) or repo.changelog.tip()
2175 return repo.update(node, allow=merge, force=clean)
2176 return repo.update(node, allow=merge, force=clean, forcemerge=force)
2176 2177
2177 2178 def verify(ui, repo):
2178 2179 """verify the integrity of the repository
2179 2180
2180 2181 Verify the integrity of the current repository.
2181 2182
2182 2183 This will perform an extensive check of the repository's
2183 2184 integrity, validating the hashes and checksums of each entry in
2184 2185 the changelog, manifest, and tracked files, as well as the
2185 2186 integrity of their crosslinks and indices.
2186 2187 """
2187 2188 return repo.verify()
2188 2189
2189 2190 # Command options and aliases are listed here, alphabetically
2190 2191
2191 2192 table = {
2192 2193 "^add":
2193 2194 (add,
2194 2195 [('I', 'include', [], _('include names matching the given patterns')),
2195 2196 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2196 2197 "hg add [OPTION]... [FILE]..."),
2197 2198 "addremove":
2198 2199 (addremove,
2199 2200 [('I', 'include', [], _('include names matching the given patterns')),
2200 2201 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2201 2202 "hg addremove [OPTION]... [FILE]..."),
2202 2203 "^annotate":
2203 2204 (annotate,
2204 2205 [('r', 'rev', '', _('annotate the specified revision')),
2205 2206 ('a', 'text', None, _('treat all files as text')),
2206 2207 ('u', 'user', None, _('list the author')),
2207 2208 ('d', 'date', None, _('list the date')),
2208 2209 ('n', 'number', None, _('list the revision number (default)')),
2209 2210 ('c', 'changeset', None, _('list the changeset')),
2210 2211 ('I', 'include', [], _('include names matching the given patterns')),
2211 2212 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2212 2213 _('hg annotate [OPTION]... FILE...')),
2213 2214 "bundle":
2214 2215 (bundle,
2215 2216 [],
2216 2217 _('hg bundle FILE DEST')),
2217 2218 "cat":
2218 2219 (cat,
2219 2220 [('I', 'include', [], _('include names matching the given patterns')),
2220 2221 ('X', 'exclude', [], _('exclude names matching the given patterns')),
2221 2222 ('o', 'output', "", _('print output to file with formatted name')),
2222 2223 ('r', 'rev', '', _('print the given revision'))],
2223 2224 _('hg cat [OPTION]... FILE...')),
2224 2225 "^clone":
2225 2226 (clone,
2226 2227 [('U', 'noupdate', None, _('do not update the new working directory')),
2227 2228 ('e', 'ssh', "", _('specify ssh command to use')),
2228 2229 ('', 'pull', None, _('use pull protocol to copy metadata')),
2229 2230 ('r', 'rev', [], _('a changeset you would like to have after cloning')),
2230 2231 ('', 'remotecmd', "", _('specify hg command to run on the remote side'))],
2231 2232 _('hg clone [OPTION]... SOURCE [DEST]')),
2232 2233 "^commit|ci":
2233 2234 (commit,
2234 2235 [('A', 'addremove', None, _('run addremove during commit')),
2235 2236 ('I', 'include', [], _('include names matching the given patterns')),
2236 2237 ('X', 'exclude', [], _('exclude names matching the given patterns')),
2237 2238 ('m', 'message', "", _('use <text> as commit message')),
2238 2239 ('l', 'logfile', "", _('read the commit message from <file>')),
2239 2240 ('d', 'date', "", _('record datecode as commit date')),
2240 2241 ('u', 'user', "", _('record user as commiter'))],
2241 2242 _('hg commit [OPTION]... [FILE]...')),
2242 2243 "copy|cp": (copy,
2243 2244 [('I', 'include', [], _('include names matching the given patterns')),
2244 2245 ('X', 'exclude', [], _('exclude names matching the given patterns')),
2245 2246 ('A', 'after', None, _('record a copy that has already occurred')),
2246 2247 ('f', 'force', None, _('forcibly copy over an existing managed file'))],
2247 2248 _('hg copy [OPTION]... [SOURCE]... DEST')),
2248 2249 "debugancestor": (debugancestor, [], _('debugancestor INDEX REV1 REV2')),
2249 2250 "debugcheckstate": (debugcheckstate, [], _('debugcheckstate')),
2250 2251 "debugconfig": (debugconfig, [], _('debugconfig')),
2251 2252 "debugsetparents": (debugsetparents, [], _('debugsetparents REV1 [REV2]')),
2252 2253 "debugstate": (debugstate, [], _('debugstate')),
2253 2254 "debugdata": (debugdata, [], _('debugdata FILE REV')),
2254 2255 "debugindex": (debugindex, [], _('debugindex FILE')),
2255 2256 "debugindexdot": (debugindexdot, [], _('debugindexdot FILE')),
2256 2257 "debugrename": (debugrename, [], _('debugrename FILE [REV]')),
2257 2258 "debugwalk":
2258 2259 (debugwalk,
2259 2260 [('I', 'include', [], _('include names matching the given patterns')),
2260 2261 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2261 2262 _('debugwalk [OPTION]... [FILE]...')),
2262 2263 "^diff":
2263 2264 (diff,
2264 2265 [('r', 'rev', [], _('revision')),
2265 2266 ('a', 'text', None, _('treat all files as text')),
2266 2267 ('I', 'include', [], _('include names matching the given patterns')),
2267 2268 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2268 2269 _('hg diff [-a] [-I] [-X] [-r REV1 [-r REV2]] [FILE]...')),
2269 2270 "^export":
2270 2271 (export,
2271 2272 [('o', 'output', "", _('print output to file with formatted name')),
2272 2273 ('a', 'text', None, _('treat all files as text')),
2273 2274 ('', 'switch-parent', None, _('diff against the second parent'))],
2274 2275 "hg export [-a] [-o OUTFILE] REV..."),
2275 2276 "forget":
2276 2277 (forget,
2277 2278 [('I', 'include', [], _('include names matching the given patterns')),
2278 2279 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2279 2280 "hg forget [OPTION]... FILE..."),
2280 2281 "grep":
2281 2282 (grep,
2282 2283 [('0', 'print0', None, _('end fields with NUL')),
2283 2284 ('I', 'include', [], _('include names matching the given patterns')),
2284 2285 ('X', 'exclude', [], _('exclude names matching the given patterns')),
2285 2286 ('', 'all', None, _('print all revisions that match')),
2286 2287 ('i', 'ignore-case', None, _('ignore case when matching')),
2287 2288 ('l', 'files-with-matches', None, _('print only filenames and revs that match')),
2288 2289 ('n', 'line-number', None, _('print matching line numbers')),
2289 2290 ('r', 'rev', [], _('search in given revision range')),
2290 2291 ('u', 'user', None, _('print user who committed change'))],
2291 2292 "hg grep [OPTION]... PATTERN [FILE]..."),
2292 2293 "heads":
2293 2294 (heads,
2294 2295 [('b', 'branches', None, _('find branch info')),
2295 2296 ('r', 'rev', "", _('show only heads which are descendants of rev'))],
2296 2297 _('hg heads [-b] [-r <rev>]')),
2297 2298 "help": (help_, [], _('hg help [COMMAND]')),
2298 2299 "identify|id": (identify, [], _('hg identify')),
2299 2300 "import|patch":
2300 2301 (import_,
2301 2302 [('p', 'strip', 1, _('directory strip option for patch. This has the same\n') +
2302 2303 _('meaning as the corresponding patch option')),
2303 2304 ('f', 'force', None, _('skip check for outstanding uncommitted changes')),
2304 2305 ('b', 'base', "", _('base path'))],
2305 2306 "hg import [-f] [-p NUM] [-b BASE] PATCH..."),
2306 2307 "incoming|in": (incoming,
2307 2308 [('M', 'no-merges', None, _("do not show merges")),
2308 2309 ('p', 'patch', None, _('show patch')),
2309 2310 ('n', 'newest-first', None, _('show newest record first'))],
2310 2311 _('hg incoming [-p] [-n] [-M] [SOURCE]')),
2311 2312 "^init": (init, [], _('hg init [DEST]')),
2312 2313 "locate":
2313 2314 (locate,
2314 2315 [('r', 'rev', '', _('search the repository as it stood at rev')),
2315 2316 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
2316 2317 ('f', 'fullpath', None, _('print complete paths from the filesystem root')),
2317 2318 ('I', 'include', [], _('include names matching the given patterns')),
2318 2319 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2319 2320 _('hg locate [OPTION]... [PATTERN]...')),
2320 2321 "^log|history":
2321 2322 (log,
2322 2323 [('I', 'include', [], _('include names matching the given patterns')),
2323 2324 ('X', 'exclude', [], _('exclude names matching the given patterns')),
2324 2325 ('b', 'branch', None, _('show branches')),
2325 2326 ('k', 'keyword', [], _('search for a keyword')),
2326 2327 ('r', 'rev', [], _('show the specified revision or range')),
2327 2328 ('M', 'no-merges', None, _("do not show merges")),
2328 2329 ('m', 'only-merges', None, _("show only merges")),
2329 2330 ('p', 'patch', None, _('show patch'))],
2330 2331 _('hg log [-I] [-X] [-r REV]... [-p] [FILE]')),
2331 2332 "manifest": (manifest, [], _('hg manifest [REV]')),
2332 2333 "outgoing|out": (outgoing,
2333 2334 [('M', 'no-merges', None, _("do not show merges")),
2334 2335 ('p', 'patch', None, _('show patch')),
2335 2336 ('n', 'newest-first', None, _('show newest record first'))],
2336 2337 _('hg outgoing [-p] [-n] [-M] [DEST]')),
2337 2338 "^parents": (parents, [], _('hg parents [REV]')),
2338 2339 "paths": (paths, [], _('hg paths [NAME]')),
2339 2340 "^pull":
2340 2341 (pull,
2341 2342 [('u', 'update', None, _('update the working directory to tip after pull')),
2342 2343 ('e', 'ssh', "", _('specify ssh command to use')),
2343 2344 ('r', 'rev', [], _('a specific revision you would like to pull')),
2344 2345 ('', 'remotecmd', "", _('specify hg command to run on the remote side'))],
2345 2346 _('hg pull [-u] [-e FILE] [-r rev] [--remotecmd FILE] [SOURCE]')),
2346 2347 "^push":
2347 2348 (push,
2348 2349 [('f', 'force', None, _('force push')),
2349 2350 ('e', 'ssh', "", _('specify ssh command to use')),
2350 2351 ('', 'remotecmd', "", _('specify hg command to run on the remote side'))],
2351 2352 _('hg push [-f] [-e FILE] [--remotecmd FILE] [DEST]')),
2352 2353 "rawcommit":
2353 2354 (rawcommit,
2354 2355 [('p', 'parent', [], _('parent')),
2355 2356 ('d', 'date', "", _('date code')),
2356 2357 ('u', 'user', "", _('user')),
2357 2358 ('F', 'files', "", _('file list')),
2358 2359 ('m', 'message', "", _('commit message')),
2359 2360 ('l', 'logfile', "", _('commit message file'))],
2360 2361 _('hg rawcommit [OPTION]... [FILE]...')),
2361 2362 "recover": (recover, [], _("hg recover")),
2362 2363 "^remove|rm": (remove,
2363 2364 [('I', 'include', [], _('include names matching the given patterns')),
2364 2365 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2365 2366 _("hg remove [OPTION]... FILE...")),
2366 2367 "rename|mv": (rename,
2367 2368 [('I', 'include', [], _('include names matching the given patterns')),
2368 2369 ('X', 'exclude', [], _('exclude names matching the given patterns')),
2369 2370 ('A', 'after', None, _('record a rename that has already occurred')),
2370 2371 ('f', 'force', None, _('forcibly copy over an existing managed file'))],
2371 2372 _('hg rename [OPTION]... [SOURCE]... DEST')),
2372 2373 "^revert":
2373 2374 (revert,
2374 2375 [('I', 'include', [], _('include names matching the given patterns')),
2375 2376 ('X', 'exclude', [], _('exclude names matching the given patterns')),
2376 2377 ("r", "rev", "", _("revision to revert to"))],
2377 2378 _("hg revert [-n] [-r REV] [NAME]...")),
2378 2379 "root": (root, [], _("hg root")),
2379 2380 "^serve":
2380 2381 (serve,
2381 2382 [('A', 'accesslog', '', _('name of access log file to write to')),
2382 2383 ('E', 'errorlog', '', _('name of error log file to write to')),
2383 2384 ('p', 'port', 0, _('port to use (default: 8000)')),
2384 2385 ('a', 'address', '', _('address to use')),
2385 2386 ('n', 'name', "", _('name to show in web pages (default: working dir)')),
2386 2387 ('', 'stdio', None, _('for remote clients')),
2387 2388 ('t', 'templates', "", _('web templates to use')),
2388 2389 ('', 'style', "", _('template style to use')),
2389 2390 ('6', 'ipv6', None, _('use IPv6 in addition to IPv4'))],
2390 2391 _("hg serve [OPTION]...")),
2391 2392 "^status|st":
2392 2393 (status,
2393 2394 [('m', 'modified', None, _('show only modified files')),
2394 2395 ('a', 'added', None, _('show only added files')),
2395 2396 ('r', 'removed', None, _('show only removed files')),
2396 2397 ('u', 'unknown', None, _('show only unknown (not tracked) files')),
2397 2398 ('n', 'no-status', None, _('hide status prefix')),
2398 2399 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
2399 2400 ('I', 'include', [], _('include names matching the given patterns')),
2400 2401 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2401 2402 _("hg status [OPTION]... [FILE]...")),
2402 2403 "tag":
2403 2404 (tag,
2404 2405 [('l', 'local', None, _('make the tag local')),
2405 2406 ('m', 'message', "", _('message for tag commit log entry')),
2406 2407 ('d', 'date', "", _('record datecode as commit date')),
2407 2408 ('u', 'user', "", _('record user as commiter')),
2408 2409 ('r', 'rev', "", _('revision to tag'))],
2409 2410 _('hg tag [OPTION]... NAME [REV]')),
2410 2411 "tags": (tags, [], _('hg tags')),
2411 2412 "tip": (tip, [], _('hg tip')),
2412 2413 "unbundle":
2413 2414 (unbundle,
2414 2415 [],
2415 2416 _('hg unbundle FILE')),
2416 2417 "undo": (undo, [], _('hg undo')),
2417 2418 "^update|up|checkout|co":
2418 2419 (update,
2419 2420 [('b', 'branch', "", _('checkout the head of a specific branch')),
2420 2421 ('m', 'merge', None, _('allow merging of branches')),
2421 ('C', 'clean', None, _('overwrite locally modified files'))],
2422 _('hg update [-b TAG] [-m] [-C] [REV]')),
2422 ('C', 'clean', None, _('overwrite locally modified files')),
2423 ('f', 'force', None, _('force a merge with outstanding changes'))],
2424 _('hg update [-b TAG] [-m] [-C] [-f] [REV]')),
2423 2425 "verify": (verify, [], _('hg verify')),
2424 2426 "version": (show_version, [], _('hg version')),
2425 2427 }
2426 2428
2427 2429 globalopts = [
2428 2430 ('R', 'repository', "", _("repository root directory")),
2429 2431 ('', 'cwd', '', _("change working directory")),
2430 2432 ('y', 'noninteractive', None, _("do not prompt, assume 'yes' for any required answers")),
2431 2433 ('q', 'quiet', None, _("suppress output")),
2432 2434 ('v', 'verbose', None, _("enable additional output")),
2433 2435 ('', 'debug', None, _("enable debugging output")),
2434 2436 ('', 'debugger', None, _("start debugger")),
2435 2437 ('', 'traceback', None, _("print traceback on exception")),
2436 2438 ('', 'time', None, _("time how long the command takes")),
2437 2439 ('', 'profile', None, _("print command execution profile")),
2438 2440 ('', 'version', None, _("output version information and exit")),
2439 2441 ('h', 'help', None, _("display help and exit")),
2440 2442 ]
2441 2443
2442 2444 norepo = ("clone init version help debugancestor debugconfig debugdata"
2443 2445 " debugindex debugindexdot paths")
2444 2446
2445 2447 def find(cmd):
2446 2448 """Return (aliases, command table entry) for command string."""
2447 2449 choice = None
2448 2450 for e in table.keys():
2449 2451 aliases = e.lstrip("^").split("|")
2450 2452 if cmd in aliases:
2451 2453 return aliases, table[e]
2452 2454 for a in aliases:
2453 2455 if a.startswith(cmd):
2454 2456 if choice:
2455 2457 raise AmbiguousCommand(cmd)
2456 2458 else:
2457 2459 choice = aliases, table[e]
2458 2460 break
2459 2461 if choice:
2460 2462 return choice
2461 2463
2462 2464 raise UnknownCommand(cmd)
2463 2465
2464 2466 class SignalInterrupt(Exception):
2465 2467 """Exception raised on SIGTERM and SIGHUP."""
2466 2468
2467 2469 def catchterm(*args):
2468 2470 raise SignalInterrupt
2469 2471
2470 2472 def run():
2471 2473 sys.exit(dispatch(sys.argv[1:]))
2472 2474
2473 2475 class ParseError(Exception):
2474 2476 """Exception raised on errors in parsing the command line."""
2475 2477
2476 2478 def parse(ui, args):
2477 2479 options = {}
2478 2480 cmdoptions = {}
2479 2481
2480 2482 try:
2481 2483 args = fancyopts.fancyopts(args, globalopts, options)
2482 2484 except fancyopts.getopt.GetoptError, inst:
2483 2485 raise ParseError(None, inst)
2484 2486
2485 2487 if args:
2486 2488 cmd, args = args[0], args[1:]
2487 2489 aliases, i = find(cmd)
2488 2490 cmd = aliases[0]
2489 2491 defaults = ui.config("defaults", cmd)
2490 2492 if defaults:
2491 2493 args = defaults.split() + args
2492 2494 c = list(i[1])
2493 2495 else:
2494 2496 cmd = None
2495 2497 c = []
2496 2498
2497 2499 # combine global options into local
2498 2500 for o in globalopts:
2499 2501 c.append((o[0], o[1], options[o[1]], o[3]))
2500 2502
2501 2503 try:
2502 2504 args = fancyopts.fancyopts(args, c, cmdoptions)
2503 2505 except fancyopts.getopt.GetoptError, inst:
2504 2506 raise ParseError(cmd, inst)
2505 2507
2506 2508 # separate global options back out
2507 2509 for o in globalopts:
2508 2510 n = o[1]
2509 2511 options[n] = cmdoptions[n]
2510 2512 del cmdoptions[n]
2511 2513
2512 2514 return (cmd, cmd and i[0] or None, args, options, cmdoptions)
2513 2515
2514 2516 def dispatch(args):
2515 2517 signal.signal(signal.SIGTERM, catchterm)
2516 2518 try:
2517 2519 signal.signal(signal.SIGHUP, catchterm)
2518 2520 except AttributeError:
2519 2521 pass
2520 2522
2521 2523 try:
2522 2524 u = ui.ui()
2523 2525 except util.Abort, inst:
2524 2526 sys.stderr.write(_("abort: %s\n") % inst)
2525 2527 sys.exit(1)
2526 2528
2527 2529 external = []
2528 2530 for x in u.extensions():
2529 2531 def on_exception(exc, inst):
2530 2532 u.warn(_("*** failed to import extension %s\n") % x[1])
2531 2533 u.warn("%s\n" % inst)
2532 2534 if "--traceback" in sys.argv[1:]:
2533 2535 traceback.print_exc()
2534 2536 if x[1]:
2535 2537 try:
2536 2538 mod = imp.load_source(x[0], x[1])
2537 2539 except Exception, inst:
2538 2540 on_exception(Exception, inst)
2539 2541 continue
2540 2542 else:
2541 2543 def importh(name):
2542 2544 mod = __import__(name)
2543 2545 components = name.split('.')
2544 2546 for comp in components[1:]:
2545 2547 mod = getattr(mod, comp)
2546 2548 return mod
2547 2549 try:
2548 2550 mod = importh(x[0])
2549 2551 except Exception, inst:
2550 2552 on_exception(Exception, inst)
2551 2553 continue
2552 2554
2553 2555 external.append(mod)
2554 2556 for x in external:
2555 2557 cmdtable = getattr(x, 'cmdtable', {})
2556 2558 for t in cmdtable:
2557 2559 if t in table:
2558 2560 u.warn(_("module %s overrides %s\n") % (x.__name__, t))
2559 2561 table.update(cmdtable)
2560 2562
2561 2563 try:
2562 2564 cmd, func, args, options, cmdoptions = parse(u, args)
2563 2565 except ParseError, inst:
2564 2566 if inst.args[0]:
2565 2567 u.warn(_("hg %s: %s\n") % (inst.args[0], inst.args[1]))
2566 2568 help_(u, inst.args[0])
2567 2569 else:
2568 2570 u.warn(_("hg: %s\n") % inst.args[1])
2569 2571 help_(u, 'shortlist')
2570 2572 sys.exit(-1)
2571 2573 except AmbiguousCommand, inst:
2572 2574 u.warn(_("hg: command '%s' is ambiguous.\n") % inst.args[0])
2573 2575 sys.exit(1)
2574 2576 except UnknownCommand, inst:
2575 2577 u.warn(_("hg: unknown command '%s'\n") % inst.args[0])
2576 2578 help_(u, 'shortlist')
2577 2579 sys.exit(1)
2578 2580
2579 2581 if options["time"]:
2580 2582 def get_times():
2581 2583 t = os.times()
2582 2584 if t[4] == 0.0: # Windows leaves this as zero, so use time.clock()
2583 2585 t = (t[0], t[1], t[2], t[3], time.clock())
2584 2586 return t
2585 2587 s = get_times()
2586 2588 def print_time():
2587 2589 t = get_times()
2588 2590 u.warn(_("Time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n") %
2589 2591 (t[4]-s[4], t[0]-s[0], t[2]-s[2], t[1]-s[1], t[3]-s[3]))
2590 2592 atexit.register(print_time)
2591 2593
2592 2594 u.updateopts(options["verbose"], options["debug"], options["quiet"],
2593 2595 not options["noninteractive"])
2594 2596
2595 2597 # enter the debugger before command execution
2596 2598 if options['debugger']:
2597 2599 pdb.set_trace()
2598 2600
2599 2601 try:
2600 2602 try:
2601 2603 if options['help']:
2602 2604 help_(u, cmd, options['version'])
2603 2605 sys.exit(0)
2604 2606 elif options['version']:
2605 2607 show_version(u)
2606 2608 sys.exit(0)
2607 2609 elif not cmd:
2608 2610 help_(u, 'shortlist')
2609 2611 sys.exit(0)
2610 2612
2611 2613 if options['cwd']:
2612 2614 try:
2613 2615 os.chdir(options['cwd'])
2614 2616 except OSError, inst:
2615 2617 raise util.Abort('%s: %s' %
2616 2618 (options['cwd'], inst.strerror))
2617 2619
2618 2620 if cmd not in norepo.split():
2619 2621 path = options["repository"] or ""
2620 2622 repo = hg.repository(ui=u, path=path)
2621 2623 for x in external:
2622 2624 if hasattr(x, 'reposetup'): x.reposetup(u, repo)
2623 2625 d = lambda: func(u, repo, *args, **cmdoptions)
2624 2626 else:
2625 2627 d = lambda: func(u, *args, **cmdoptions)
2626 2628
2627 2629 if options['profile']:
2628 2630 import hotshot, hotshot.stats
2629 2631 prof = hotshot.Profile("hg.prof")
2630 2632 r = prof.runcall(d)
2631 2633 prof.close()
2632 2634 stats = hotshot.stats.load("hg.prof")
2633 2635 stats.strip_dirs()
2634 2636 stats.sort_stats('time', 'calls')
2635 2637 stats.print_stats(40)
2636 2638 return r
2637 2639 else:
2638 2640 return d()
2639 2641 except:
2640 2642 # enter the debugger when we hit an exception
2641 2643 if options['debugger']:
2642 2644 pdb.post_mortem(sys.exc_info()[2])
2643 2645 if options['traceback']:
2644 2646 traceback.print_exc()
2645 2647 raise
2646 2648 except hg.RepoError, inst:
2647 2649 u.warn(_("abort: "), inst, "!\n")
2648 2650 except revlog.RevlogError, inst:
2649 2651 u.warn(_("abort: "), inst, "!\n")
2650 2652 except SignalInterrupt:
2651 2653 u.warn(_("killed!\n"))
2652 2654 except KeyboardInterrupt:
2653 2655 try:
2654 2656 u.warn(_("interrupted!\n"))
2655 2657 except IOError, inst:
2656 2658 if inst.errno == errno.EPIPE:
2657 2659 if u.debugflag:
2658 2660 u.warn(_("\nbroken pipe\n"))
2659 2661 else:
2660 2662 raise
2661 2663 except IOError, inst:
2662 2664 if hasattr(inst, "code"):
2663 2665 u.warn(_("abort: %s\n") % inst)
2664 2666 elif hasattr(inst, "reason"):
2665 2667 u.warn(_("abort: error: %s\n") % inst.reason[1])
2666 2668 elif hasattr(inst, "args") and inst[0] == errno.EPIPE:
2667 2669 if u.debugflag:
2668 2670 u.warn(_("broken pipe\n"))
2669 2671 elif getattr(inst, "strerror", None):
2670 2672 if getattr(inst, "filename", None):
2671 2673 u.warn(_("abort: %s - %s\n") % (inst.strerror, inst.filename))
2672 2674 else:
2673 2675 u.warn(_("abort: %s\n") % inst.strerror)
2674 2676 else:
2675 2677 raise
2676 2678 except OSError, inst:
2677 2679 if hasattr(inst, "filename"):
2678 2680 u.warn(_("abort: %s: %s\n") % (inst.strerror, inst.filename))
2679 2681 else:
2680 2682 u.warn(_("abort: %s\n") % inst.strerror)
2681 2683 except util.Abort, inst:
2682 2684 u.warn(_('abort: '), inst.args[0] % inst.args[1:], '\n')
2683 2685 sys.exit(1)
2684 2686 except TypeError, inst:
2685 2687 # was this an argument error?
2686 2688 tb = traceback.extract_tb(sys.exc_info()[2])
2687 2689 if len(tb) > 2: # no
2688 2690 raise
2689 2691 u.debug(inst, "\n")
2690 2692 u.warn(_("%s: invalid arguments\n") % cmd)
2691 2693 help_(u, cmd)
2692 2694 except AmbiguousCommand, inst:
2693 2695 u.warn(_("hg: command '%s' is ambiguous.\n") % inst.args[0])
2694 2696 help_(u, 'shortlist')
2695 2697 except UnknownCommand, inst:
2696 2698 u.warn(_("hg: unknown command '%s'\n") % inst.args[0])
2697 2699 help_(u, 'shortlist')
2698 2700 except SystemExit:
2699 2701 # don't catch this in the catch-all below
2700 2702 raise
2701 2703 except:
2702 2704 u.warn(_("** unknown exception encountered, details follow\n"))
2703 2705 u.warn(_("** report bug details to mercurial@selenic.com\n"))
2704 2706 u.warn(_("** Mercurial Distributed SCM (version %s)\n")
2705 2707 % version.get_version())
2706 2708 raise
2707 2709
2708 2710 sys.exit(-1)
@@ -1,1780 +1,1792 b''
1 1 # localrepo.py - read/write repository class 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 struct, os, util
9 9 import filelog, manifest, changelog, dirstate, repo
10 10 from node import *
11 11 from i18n import gettext as _
12 12 from demandload import *
13 13 demandload(globals(), "re lock transaction tempfile stat mdiff errno")
14 14
15 15 class localrepository(object):
16 16 def __init__(self, ui, path=None, create=0):
17 17 if not path:
18 18 p = os.getcwd()
19 19 while not os.path.isdir(os.path.join(p, ".hg")):
20 20 oldp = p
21 21 p = os.path.dirname(p)
22 22 if p == oldp: raise repo.RepoError(_("no repo found"))
23 23 path = p
24 24 self.path = os.path.join(path, ".hg")
25 25
26 26 if not create and not os.path.isdir(self.path):
27 27 raise repo.RepoError(_("repository %s not found") % self.path)
28 28
29 29 self.root = os.path.abspath(path)
30 30 self.ui = ui
31 31 self.opener = util.opener(self.path)
32 32 self.wopener = util.opener(self.root)
33 33 self.manifest = manifest.manifest(self.opener)
34 34 self.changelog = changelog.changelog(self.opener)
35 35 self.tagscache = None
36 36 self.nodetagscache = None
37 37 self.encodepats = None
38 38 self.decodepats = None
39 39
40 40 if create:
41 41 os.mkdir(self.path)
42 42 os.mkdir(self.join("data"))
43 43
44 44 self.dirstate = dirstate.dirstate(self.opener, ui, self.root)
45 45 try:
46 46 self.ui.readconfig(self.join("hgrc"))
47 47 except IOError: pass
48 48
49 49 def hook(self, name, **args):
50 50 def runhook(name, cmd):
51 51 self.ui.note(_("running hook %s: %s\n") % (name, cmd))
52 52 old = {}
53 53 for k, v in args.items():
54 54 k = k.upper()
55 55 old[k] = os.environ.get(k, None)
56 56 os.environ[k] = v
57 57
58 58 # Hooks run in the repository root
59 59 olddir = os.getcwd()
60 60 os.chdir(self.root)
61 61 r = os.system(cmd)
62 62 os.chdir(olddir)
63 63
64 64 for k, v in old.items():
65 65 if v != None:
66 66 os.environ[k] = v
67 67 else:
68 68 del os.environ[k]
69 69
70 70 if r:
71 71 self.ui.warn(_("abort: %s hook failed with status %d!\n") %
72 72 (name, r))
73 73 return False
74 74 return True
75 75
76 76 r = True
77 77 for hname, cmd in self.ui.configitems("hooks"):
78 78 s = hname.split(".")
79 79 if s[0] == name and cmd:
80 80 r = runhook(hname, cmd) and r
81 81 return r
82 82
83 83 def tags(self):
84 84 '''return a mapping of tag to node'''
85 85 if not self.tagscache:
86 86 self.tagscache = {}
87 87 def addtag(self, k, n):
88 88 try:
89 89 bin_n = bin(n)
90 90 except TypeError:
91 91 bin_n = ''
92 92 self.tagscache[k.strip()] = bin_n
93 93
94 94 try:
95 95 # read each head of the tags file, ending with the tip
96 96 # and add each tag found to the map, with "newer" ones
97 97 # taking precedence
98 98 fl = self.file(".hgtags")
99 99 h = fl.heads()
100 100 h.reverse()
101 101 for r in h:
102 102 for l in fl.read(r).splitlines():
103 103 if l:
104 104 n, k = l.split(" ", 1)
105 105 addtag(self, k, n)
106 106 except KeyError:
107 107 pass
108 108
109 109 try:
110 110 f = self.opener("localtags")
111 111 for l in f:
112 112 n, k = l.split(" ", 1)
113 113 addtag(self, k, n)
114 114 except IOError:
115 115 pass
116 116
117 117 self.tagscache['tip'] = self.changelog.tip()
118 118
119 119 return self.tagscache
120 120
121 121 def tagslist(self):
122 122 '''return a list of tags ordered by revision'''
123 123 l = []
124 124 for t, n in self.tags().items():
125 125 try:
126 126 r = self.changelog.rev(n)
127 127 except:
128 128 r = -2 # sort to the beginning of the list if unknown
129 129 l.append((r,t,n))
130 130 l.sort()
131 131 return [(t,n) for r,t,n in l]
132 132
133 133 def nodetags(self, node):
134 134 '''return the tags associated with a node'''
135 135 if not self.nodetagscache:
136 136 self.nodetagscache = {}
137 137 for t,n in self.tags().items():
138 138 self.nodetagscache.setdefault(n,[]).append(t)
139 139 return self.nodetagscache.get(node, [])
140 140
141 141 def lookup(self, key):
142 142 try:
143 143 return self.tags()[key]
144 144 except KeyError:
145 145 try:
146 146 return self.changelog.lookup(key)
147 147 except:
148 148 raise repo.RepoError(_("unknown revision '%s'") % key)
149 149
150 150 def dev(self):
151 151 return os.stat(self.path).st_dev
152 152
153 153 def local(self):
154 154 return True
155 155
156 156 def join(self, f):
157 157 return os.path.join(self.path, f)
158 158
159 159 def wjoin(self, f):
160 160 return os.path.join(self.root, f)
161 161
162 162 def file(self, f):
163 163 if f[0] == '/': f = f[1:]
164 164 return filelog.filelog(self.opener, f)
165 165
166 166 def getcwd(self):
167 167 return self.dirstate.getcwd()
168 168
169 169 def wfile(self, f, mode='r'):
170 170 return self.wopener(f, mode)
171 171
172 172 def wread(self, filename):
173 173 if self.encodepats == None:
174 174 l = []
175 175 for pat, cmd in self.ui.configitems("encode"):
176 176 mf = util.matcher("", "/", [pat], [], [])[1]
177 177 l.append((mf, cmd))
178 178 self.encodepats = l
179 179
180 180 data = self.wopener(filename, 'r').read()
181 181
182 182 for mf, cmd in self.encodepats:
183 183 if mf(filename):
184 184 self.ui.debug(_("filtering %s through %s\n") % (filename, cmd))
185 185 data = util.filter(data, cmd)
186 186 break
187 187
188 188 return data
189 189
190 190 def wwrite(self, filename, data, fd=None):
191 191 if self.decodepats == None:
192 192 l = []
193 193 for pat, cmd in self.ui.configitems("decode"):
194 194 mf = util.matcher("", "/", [pat], [], [])[1]
195 195 l.append((mf, cmd))
196 196 self.decodepats = l
197 197
198 198 for mf, cmd in self.decodepats:
199 199 if mf(filename):
200 200 self.ui.debug(_("filtering %s through %s\n") % (filename, cmd))
201 201 data = util.filter(data, cmd)
202 202 break
203 203
204 204 if fd:
205 205 return fd.write(data)
206 206 return self.wopener(filename, 'w').write(data)
207 207
208 208 def transaction(self):
209 209 # save dirstate for undo
210 210 try:
211 211 ds = self.opener("dirstate").read()
212 212 except IOError:
213 213 ds = ""
214 214 self.opener("journal.dirstate", "w").write(ds)
215 215
216 216 def after():
217 217 util.rename(self.join("journal"), self.join("undo"))
218 218 util.rename(self.join("journal.dirstate"),
219 219 self.join("undo.dirstate"))
220 220
221 221 return transaction.transaction(self.ui.warn, self.opener,
222 222 self.join("journal"), after)
223 223
224 224 def recover(self):
225 225 lock = self.lock()
226 226 if os.path.exists(self.join("journal")):
227 227 self.ui.status(_("rolling back interrupted transaction\n"))
228 228 transaction.rollback(self.opener, self.join("journal"))
229 229 return True
230 230 else:
231 231 self.ui.warn(_("no interrupted transaction available\n"))
232 232 return False
233 233
234 234 def undo(self):
235 235 wlock = self.wlock()
236 236 lock = self.lock()
237 237 if os.path.exists(self.join("undo")):
238 238 self.ui.status(_("rolling back last transaction\n"))
239 239 transaction.rollback(self.opener, self.join("undo"))
240 240 util.rename(self.join("undo.dirstate"), self.join("dirstate"))
241 241 self.dirstate.read()
242 242 else:
243 243 self.ui.warn(_("no undo information available\n"))
244 244
245 245 def lock(self, wait=1):
246 246 try:
247 247 return lock.lock(self.join("lock"), 0)
248 248 except lock.LockHeld, inst:
249 249 if wait:
250 250 self.ui.warn(_("waiting for lock held by %s\n") % inst.args[0])
251 251 return lock.lock(self.join("lock"), wait)
252 252 raise inst
253 253
254 254 def wlock(self, wait=1):
255 255 try:
256 256 wlock = lock.lock(self.join("wlock"), 0, self.dirstate.write)
257 257 except lock.LockHeld, inst:
258 258 if not wait:
259 259 raise inst
260 260 self.ui.warn(_("waiting for lock held by %s\n") % inst.args[0])
261 261 wlock = lock.lock(self.join("wlock"), wait, self.dirstate.write)
262 262 self.dirstate.read()
263 263 return wlock
264 264
265 265 def rawcommit(self, files, text, user, date, p1=None, p2=None):
266 266 orig_parent = self.dirstate.parents()[0] or nullid
267 267 p1 = p1 or self.dirstate.parents()[0] or nullid
268 268 p2 = p2 or self.dirstate.parents()[1] or nullid
269 269 c1 = self.changelog.read(p1)
270 270 c2 = self.changelog.read(p2)
271 271 m1 = self.manifest.read(c1[0])
272 272 mf1 = self.manifest.readflags(c1[0])
273 273 m2 = self.manifest.read(c2[0])
274 274 changed = []
275 275
276 276 if orig_parent == p1:
277 277 update_dirstate = 1
278 278 else:
279 279 update_dirstate = 0
280 280
281 281 wlock = self.wlock()
282 282 lock = self.lock()
283 283 tr = self.transaction()
284 284 mm = m1.copy()
285 285 mfm = mf1.copy()
286 286 linkrev = self.changelog.count()
287 287 for f in files:
288 288 try:
289 289 t = self.wread(f)
290 290 tm = util.is_exec(self.wjoin(f), mfm.get(f, False))
291 291 r = self.file(f)
292 292 mfm[f] = tm
293 293
294 294 fp1 = m1.get(f, nullid)
295 295 fp2 = m2.get(f, nullid)
296 296
297 297 # is the same revision on two branches of a merge?
298 298 if fp2 == fp1:
299 299 fp2 = nullid
300 300
301 301 if fp2 != nullid:
302 302 # is one parent an ancestor of the other?
303 303 fpa = r.ancestor(fp1, fp2)
304 304 if fpa == fp1:
305 305 fp1, fp2 = fp2, nullid
306 306 elif fpa == fp2:
307 307 fp2 = nullid
308 308
309 309 # is the file unmodified from the parent?
310 310 if t == r.read(fp1):
311 311 # record the proper existing parent in manifest
312 312 # no need to add a revision
313 313 mm[f] = fp1
314 314 continue
315 315
316 316 mm[f] = r.add(t, {}, tr, linkrev, fp1, fp2)
317 317 changed.append(f)
318 318 if update_dirstate:
319 319 self.dirstate.update([f], "n")
320 320 except IOError:
321 321 try:
322 322 del mm[f]
323 323 del mfm[f]
324 324 if update_dirstate:
325 325 self.dirstate.forget([f])
326 326 except:
327 327 # deleted from p2?
328 328 pass
329 329
330 330 mnode = self.manifest.add(mm, mfm, tr, linkrev, c1[0], c2[0])
331 331 user = user or self.ui.username()
332 332 n = self.changelog.add(mnode, changed, text, tr, p1, p2, user, date)
333 333 tr.close()
334 334 if update_dirstate:
335 335 self.dirstate.setparents(n, nullid)
336 336
337 337 def commit(self, files = None, text = "", user = None, date = None,
338 338 match = util.always, force=False):
339 339 commit = []
340 340 remove = []
341 341 changed = []
342 342
343 343 if files:
344 344 for f in files:
345 345 s = self.dirstate.state(f)
346 346 if s in 'nmai':
347 347 commit.append(f)
348 348 elif s == 'r':
349 349 remove.append(f)
350 350 else:
351 351 self.ui.warn(_("%s not tracked!\n") % f)
352 352 else:
353 353 (c, a, d, u) = self.changes(match=match)
354 354 commit = c + a
355 355 remove = d
356 356
357 357 p1, p2 = self.dirstate.parents()
358 358 c1 = self.changelog.read(p1)
359 359 c2 = self.changelog.read(p2)
360 360 m1 = self.manifest.read(c1[0])
361 361 mf1 = self.manifest.readflags(c1[0])
362 362 m2 = self.manifest.read(c2[0])
363 363
364 364 if not commit and not remove and not force and p2 == nullid:
365 365 self.ui.status(_("nothing changed\n"))
366 366 return None
367 367
368 368 if not self.hook("precommit"):
369 369 return None
370 370
371 371 wlock = self.wlock()
372 372 lock = self.lock()
373 373 tr = self.transaction()
374 374
375 375 # check in files
376 376 new = {}
377 377 linkrev = self.changelog.count()
378 378 commit.sort()
379 379 for f in commit:
380 380 self.ui.note(f + "\n")
381 381 try:
382 382 mf1[f] = util.is_exec(self.wjoin(f), mf1.get(f, False))
383 383 t = self.wread(f)
384 384 except IOError:
385 385 self.ui.warn(_("trouble committing %s!\n") % f)
386 386 raise
387 387
388 388 r = self.file(f)
389 389
390 390 meta = {}
391 391 cp = self.dirstate.copied(f)
392 392 if cp:
393 393 meta["copy"] = cp
394 394 meta["copyrev"] = hex(m1.get(cp, m2.get(cp, nullid)))
395 395 self.ui.debug(_(" %s: copy %s:%s\n") % (f, cp, meta["copyrev"]))
396 396 fp1, fp2 = nullid, nullid
397 397 else:
398 398 fp1 = m1.get(f, nullid)
399 399 fp2 = m2.get(f, nullid)
400 400
401 401 # is the same revision on two branches of a merge?
402 402 if fp2 == fp1:
403 403 fp2 = nullid
404 404
405 405 if fp2 != nullid:
406 406 # is one parent an ancestor of the other?
407 407 fpa = r.ancestor(fp1, fp2)
408 408 if fpa == fp1:
409 409 fp1, fp2 = fp2, nullid
410 410 elif fpa == fp2:
411 411 fp2 = nullid
412 412
413 413 # is the file unmodified from the parent?
414 414 if not meta and t == r.read(fp1):
415 415 # record the proper existing parent in manifest
416 416 # no need to add a revision
417 417 new[f] = fp1
418 418 continue
419 419
420 420 new[f] = r.add(t, meta, tr, linkrev, fp1, fp2)
421 421 # remember what we've added so that we can later calculate
422 422 # the files to pull from a set of changesets
423 423 changed.append(f)
424 424
425 425 # update manifest
426 426 m1.update(new)
427 427 for f in remove:
428 428 if f in m1:
429 429 del m1[f]
430 430 mn = self.manifest.add(m1, mf1, tr, linkrev, c1[0], c2[0],
431 431 (new, remove))
432 432
433 433 # add changeset
434 434 new = new.keys()
435 435 new.sort()
436 436
437 437 if not text:
438 438 edittext = ""
439 439 if p2 != nullid:
440 440 edittext += "HG: branch merge\n"
441 441 edittext += "\n" + "HG: manifest hash %s\n" % hex(mn)
442 442 edittext += "".join(["HG: changed %s\n" % f for f in changed])
443 443 edittext += "".join(["HG: removed %s\n" % f for f in remove])
444 444 if not changed and not remove:
445 445 edittext += "HG: no files changed\n"
446 446 edittext = self.ui.edit(edittext)
447 447 if not edittext.rstrip():
448 448 return None
449 449 text = edittext
450 450
451 451 user = user or self.ui.username()
452 452 n = self.changelog.add(mn, changed, text, tr, p1, p2, user, date)
453 453 tr.close()
454 454
455 455 self.dirstate.setparents(n)
456 456 self.dirstate.update(new, "n")
457 457 self.dirstate.forget(remove)
458 458
459 459 if not self.hook("commit", node=hex(n)):
460 460 return None
461 461 return n
462 462
463 463 def walk(self, node=None, files=[], match=util.always):
464 464 if node:
465 465 for fn in self.manifest.read(self.changelog.read(node)[0]):
466 466 if match(fn): yield 'm', fn
467 467 else:
468 468 for src, fn in self.dirstate.walk(files, match):
469 469 yield src, fn
470 470
471 471 def changes(self, node1 = None, node2 = None, files = [],
472 472 match = util.always):
473 473 mf2, u = None, []
474 474
475 475 def fcmp(fn, mf):
476 476 t1 = self.wread(fn)
477 477 t2 = self.file(fn).read(mf.get(fn, nullid))
478 478 return cmp(t1, t2)
479 479
480 480 def mfmatches(node):
481 481 mf = dict(self.manifest.read(node))
482 482 for fn in mf.keys():
483 483 if not match(fn):
484 484 del mf[fn]
485 485 return mf
486 486
487 487 # are we comparing the working directory?
488 488 if not node2:
489 489 try:
490 490 wlock = self.wlock(wait=0)
491 491 except lock.LockHeld:
492 492 wlock = None
493 493 l, c, a, d, u = self.dirstate.changes(files, match)
494 494
495 495 # are we comparing working dir against its parent?
496 496 if not node1:
497 497 if l:
498 498 # do a full compare of any files that might have changed
499 499 change = self.changelog.read(self.dirstate.parents()[0])
500 500 mf2 = mfmatches(change[0])
501 501 for f in l:
502 502 if fcmp(f, mf2):
503 503 c.append(f)
504 504 elif wlock is not None:
505 505 self.dirstate.update([f], "n")
506 506
507 507 for l in c, a, d, u:
508 508 l.sort()
509 509
510 510 return (c, a, d, u)
511 511
512 512 # are we comparing working dir against non-tip?
513 513 # generate a pseudo-manifest for the working dir
514 514 if not node2:
515 515 if not mf2:
516 516 change = self.changelog.read(self.dirstate.parents()[0])
517 517 mf2 = mfmatches(change[0])
518 518 for f in a + c + l:
519 519 mf2[f] = ""
520 520 for f in d:
521 521 if f in mf2: del mf2[f]
522 522 else:
523 523 change = self.changelog.read(node2)
524 524 mf2 = mfmatches(change[0])
525 525
526 526 # flush lists from dirstate before comparing manifests
527 527 c, a = [], []
528 528
529 529 change = self.changelog.read(node1)
530 530 mf1 = mfmatches(change[0])
531 531
532 532 for fn in mf2:
533 533 if mf1.has_key(fn):
534 534 if mf1[fn] != mf2[fn]:
535 535 if mf2[fn] != "" or fcmp(fn, mf1):
536 536 c.append(fn)
537 537 del mf1[fn]
538 538 else:
539 539 a.append(fn)
540 540
541 541 d = mf1.keys()
542 542
543 543 for l in c, a, d, u:
544 544 l.sort()
545 545
546 546 return (c, a, d, u)
547 547
548 548 def add(self, list):
549 549 wlock = self.wlock()
550 550 for f in list:
551 551 p = self.wjoin(f)
552 552 if not os.path.exists(p):
553 553 self.ui.warn(_("%s does not exist!\n") % f)
554 554 elif not os.path.isfile(p):
555 555 self.ui.warn(_("%s not added: only files supported currently\n") % f)
556 556 elif self.dirstate.state(f) in 'an':
557 557 self.ui.warn(_("%s already tracked!\n") % f)
558 558 else:
559 559 self.dirstate.update([f], "a")
560 560
561 561 def forget(self, list):
562 562 wlock = self.wlock()
563 563 for f in list:
564 564 if self.dirstate.state(f) not in 'ai':
565 565 self.ui.warn(_("%s not added!\n") % f)
566 566 else:
567 567 self.dirstate.forget([f])
568 568
569 569 def remove(self, list, unlink=False):
570 570 if unlink:
571 571 for f in list:
572 572 try:
573 573 util.unlink(self.wjoin(f))
574 574 except OSError, inst:
575 575 if inst.errno != errno.ENOENT: raise
576 576 wlock = self.wlock()
577 577 for f in list:
578 578 p = self.wjoin(f)
579 579 if os.path.exists(p):
580 580 self.ui.warn(_("%s still exists!\n") % f)
581 581 elif self.dirstate.state(f) == 'a':
582 582 self.ui.warn(_("%s never committed!\n") % f)
583 583 self.dirstate.forget([f])
584 584 elif f not in self.dirstate:
585 585 self.ui.warn(_("%s not tracked!\n") % f)
586 586 else:
587 587 self.dirstate.update([f], "r")
588 588
589 589 def undelete(self, list):
590 590 p = self.dirstate.parents()[0]
591 591 mn = self.changelog.read(p)[0]
592 592 mf = self.manifest.readflags(mn)
593 593 m = self.manifest.read(mn)
594 594 wlock = self.wlock()
595 595 for f in list:
596 596 if self.dirstate.state(f) not in "r":
597 597 self.ui.warn("%s not removed!\n" % f)
598 598 else:
599 599 t = self.file(f).read(m[f])
600 600 self.wwrite(f, t)
601 601 util.set_exec(self.wjoin(f), mf[f])
602 602 self.dirstate.update([f], "n")
603 603
604 604 def copy(self, source, dest):
605 605 p = self.wjoin(dest)
606 606 if not os.path.exists(p):
607 607 self.ui.warn(_("%s does not exist!\n") % dest)
608 608 elif not os.path.isfile(p):
609 609 self.ui.warn(_("copy failed: %s is not a file\n") % dest)
610 610 else:
611 611 wlock = self.wlock()
612 612 if self.dirstate.state(dest) == '?':
613 613 self.dirstate.update([dest], "a")
614 614 self.dirstate.copy(source, dest)
615 615
616 616 def heads(self, start=None):
617 617 heads = self.changelog.heads(start)
618 618 # sort the output in rev descending order
619 619 heads = [(-self.changelog.rev(h), h) for h in heads]
620 620 heads.sort()
621 621 return [n for (r, n) in heads]
622 622
623 623 # branchlookup returns a dict giving a list of branches for
624 624 # each head. A branch is defined as the tag of a node or
625 625 # the branch of the node's parents. If a node has multiple
626 626 # branch tags, tags are eliminated if they are visible from other
627 627 # branch tags.
628 628 #
629 629 # So, for this graph: a->b->c->d->e
630 630 # \ /
631 631 # aa -----/
632 632 # a has tag 2.6.12
633 633 # d has tag 2.6.13
634 634 # e would have branch tags for 2.6.12 and 2.6.13. Because the node
635 635 # for 2.6.12 can be reached from the node 2.6.13, that is eliminated
636 636 # from the list.
637 637 #
638 638 # It is possible that more than one head will have the same branch tag.
639 639 # callers need to check the result for multiple heads under the same
640 640 # branch tag if that is a problem for them (ie checkout of a specific
641 641 # branch).
642 642 #
643 643 # passing in a specific branch will limit the depth of the search
644 644 # through the parents. It won't limit the branches returned in the
645 645 # result though.
646 646 def branchlookup(self, heads=None, branch=None):
647 647 if not heads:
648 648 heads = self.heads()
649 649 headt = [ h for h in heads ]
650 650 chlog = self.changelog
651 651 branches = {}
652 652 merges = []
653 653 seenmerge = {}
654 654
655 655 # traverse the tree once for each head, recording in the branches
656 656 # dict which tags are visible from this head. The branches
657 657 # dict also records which tags are visible from each tag
658 658 # while we traverse.
659 659 while headt or merges:
660 660 if merges:
661 661 n, found = merges.pop()
662 662 visit = [n]
663 663 else:
664 664 h = headt.pop()
665 665 visit = [h]
666 666 found = [h]
667 667 seen = {}
668 668 while visit:
669 669 n = visit.pop()
670 670 if n in seen:
671 671 continue
672 672 pp = chlog.parents(n)
673 673 tags = self.nodetags(n)
674 674 if tags:
675 675 for x in tags:
676 676 if x == 'tip':
677 677 continue
678 678 for f in found:
679 679 branches.setdefault(f, {})[n] = 1
680 680 branches.setdefault(n, {})[n] = 1
681 681 break
682 682 if n not in found:
683 683 found.append(n)
684 684 if branch in tags:
685 685 continue
686 686 seen[n] = 1
687 687 if pp[1] != nullid and n not in seenmerge:
688 688 merges.append((pp[1], [x for x in found]))
689 689 seenmerge[n] = 1
690 690 if pp[0] != nullid:
691 691 visit.append(pp[0])
692 692 # traverse the branches dict, eliminating branch tags from each
693 693 # head that are visible from another branch tag for that head.
694 694 out = {}
695 695 viscache = {}
696 696 for h in heads:
697 697 def visible(node):
698 698 if node in viscache:
699 699 return viscache[node]
700 700 ret = {}
701 701 visit = [node]
702 702 while visit:
703 703 x = visit.pop()
704 704 if x in viscache:
705 705 ret.update(viscache[x])
706 706 elif x not in ret:
707 707 ret[x] = 1
708 708 if x in branches:
709 709 visit[len(visit):] = branches[x].keys()
710 710 viscache[node] = ret
711 711 return ret
712 712 if h not in branches:
713 713 continue
714 714 # O(n^2), but somewhat limited. This only searches the
715 715 # tags visible from a specific head, not all the tags in the
716 716 # whole repo.
717 717 for b in branches[h]:
718 718 vis = False
719 719 for bb in branches[h].keys():
720 720 if b != bb:
721 721 if b in visible(bb):
722 722 vis = True
723 723 break
724 724 if not vis:
725 725 l = out.setdefault(h, [])
726 726 l[len(l):] = self.nodetags(b)
727 727 return out
728 728
729 729 def branches(self, nodes):
730 730 if not nodes: nodes = [self.changelog.tip()]
731 731 b = []
732 732 for n in nodes:
733 733 t = n
734 734 while n:
735 735 p = self.changelog.parents(n)
736 736 if p[1] != nullid or p[0] == nullid:
737 737 b.append((t, n, p[0], p[1]))
738 738 break
739 739 n = p[0]
740 740 return b
741 741
742 742 def between(self, pairs):
743 743 r = []
744 744
745 745 for top, bottom in pairs:
746 746 n, l, i = top, [], 0
747 747 f = 1
748 748
749 749 while n != bottom:
750 750 p = self.changelog.parents(n)[0]
751 751 if i == f:
752 752 l.append(n)
753 753 f = f * 2
754 754 n = p
755 755 i += 1
756 756
757 757 r.append(l)
758 758
759 759 return r
760 760
761 761 def findincoming(self, remote, base=None, heads=None):
762 762 m = self.changelog.nodemap
763 763 search = []
764 764 fetch = {}
765 765 seen = {}
766 766 seenbranch = {}
767 767 if base == None:
768 768 base = {}
769 769
770 770 # assume we're closer to the tip than the root
771 771 # and start by examining the heads
772 772 self.ui.status(_("searching for changes\n"))
773 773
774 774 if not heads:
775 775 heads = remote.heads()
776 776
777 777 unknown = []
778 778 for h in heads:
779 779 if h not in m:
780 780 unknown.append(h)
781 781 else:
782 782 base[h] = 1
783 783
784 784 if not unknown:
785 785 return None
786 786
787 787 rep = {}
788 788 reqcnt = 0
789 789
790 790 # search through remote branches
791 791 # a 'branch' here is a linear segment of history, with four parts:
792 792 # head, root, first parent, second parent
793 793 # (a branch always has two parents (or none) by definition)
794 794 unknown = remote.branches(unknown)
795 795 while unknown:
796 796 r = []
797 797 while unknown:
798 798 n = unknown.pop(0)
799 799 if n[0] in seen:
800 800 continue
801 801
802 802 self.ui.debug(_("examining %s:%s\n") % (short(n[0]), short(n[1])))
803 803 if n[0] == nullid:
804 804 break
805 805 if n in seenbranch:
806 806 self.ui.debug(_("branch already found\n"))
807 807 continue
808 808 if n[1] and n[1] in m: # do we know the base?
809 809 self.ui.debug(_("found incomplete branch %s:%s\n")
810 810 % (short(n[0]), short(n[1])))
811 811 search.append(n) # schedule branch range for scanning
812 812 seenbranch[n] = 1
813 813 else:
814 814 if n[1] not in seen and n[1] not in fetch:
815 815 if n[2] in m and n[3] in m:
816 816 self.ui.debug(_("found new changeset %s\n") %
817 817 short(n[1]))
818 818 fetch[n[1]] = 1 # earliest unknown
819 819 base[n[2]] = 1 # latest known
820 820 continue
821 821
822 822 for a in n[2:4]:
823 823 if a not in rep:
824 824 r.append(a)
825 825 rep[a] = 1
826 826
827 827 seen[n[0]] = 1
828 828
829 829 if r:
830 830 reqcnt += 1
831 831 self.ui.debug(_("request %d: %s\n") %
832 832 (reqcnt, " ".join(map(short, r))))
833 833 for p in range(0, len(r), 10):
834 834 for b in remote.branches(r[p:p+10]):
835 835 self.ui.debug(_("received %s:%s\n") %
836 836 (short(b[0]), short(b[1])))
837 837 if b[0] in m:
838 838 self.ui.debug(_("found base node %s\n") % short(b[0]))
839 839 base[b[0]] = 1
840 840 elif b[0] not in seen:
841 841 unknown.append(b)
842 842
843 843 # do binary search on the branches we found
844 844 while search:
845 845 n = search.pop(0)
846 846 reqcnt += 1
847 847 l = remote.between([(n[0], n[1])])[0]
848 848 l.append(n[1])
849 849 p = n[0]
850 850 f = 1
851 851 for i in l:
852 852 self.ui.debug(_("narrowing %d:%d %s\n") % (f, len(l), short(i)))
853 853 if i in m:
854 854 if f <= 2:
855 855 self.ui.debug(_("found new branch changeset %s\n") %
856 856 short(p))
857 857 fetch[p] = 1
858 858 base[i] = 1
859 859 else:
860 860 self.ui.debug(_("narrowed branch search to %s:%s\n")
861 861 % (short(p), short(i)))
862 862 search.append((p, i))
863 863 break
864 864 p, f = i, f * 2
865 865
866 866 # sanity check our fetch list
867 867 for f in fetch.keys():
868 868 if f in m:
869 869 raise repo.RepoError(_("already have changeset ") + short(f[:4]))
870 870
871 871 if base.keys() == [nullid]:
872 872 self.ui.warn(_("warning: pulling from an unrelated repository!\n"))
873 873
874 874 self.ui.note(_("found new changesets starting at ") +
875 875 " ".join([short(f) for f in fetch]) + "\n")
876 876
877 877 self.ui.debug(_("%d total queries\n") % reqcnt)
878 878
879 879 return fetch.keys()
880 880
881 881 def findoutgoing(self, remote, base=None, heads=None):
882 882 if base == None:
883 883 base = {}
884 884 self.findincoming(remote, base, heads)
885 885
886 886 self.ui.debug(_("common changesets up to ")
887 887 + " ".join(map(short, base.keys())) + "\n")
888 888
889 889 remain = dict.fromkeys(self.changelog.nodemap)
890 890
891 891 # prune everything remote has from the tree
892 892 del remain[nullid]
893 893 remove = base.keys()
894 894 while remove:
895 895 n = remove.pop(0)
896 896 if n in remain:
897 897 del remain[n]
898 898 for p in self.changelog.parents(n):
899 899 remove.append(p)
900 900
901 901 # find every node whose parents have been pruned
902 902 subset = []
903 903 for n in remain:
904 904 p1, p2 = self.changelog.parents(n)
905 905 if p1 not in remain and p2 not in remain:
906 906 subset.append(n)
907 907
908 908 # this is the set of all roots we have to push
909 909 return subset
910 910
911 911 def pull(self, remote, heads = None):
912 912 lock = self.lock()
913 913
914 914 # if we have an empty repo, fetch everything
915 915 if self.changelog.tip() == nullid:
916 916 self.ui.status(_("requesting all changes\n"))
917 917 fetch = [nullid]
918 918 else:
919 919 fetch = self.findincoming(remote)
920 920
921 921 if not fetch:
922 922 self.ui.status(_("no changes found\n"))
923 923 return 1
924 924
925 925 if heads is None:
926 926 cg = remote.changegroup(fetch)
927 927 else:
928 928 cg = remote.changegroupsubset(fetch, heads)
929 929 return self.addchangegroup(cg)
930 930
931 931 def push(self, remote, force=False):
932 932 lock = remote.lock()
933 933
934 934 base = {}
935 935 heads = remote.heads()
936 936 inc = self.findincoming(remote, base, heads)
937 937 if not force and inc:
938 938 self.ui.warn(_("abort: unsynced remote changes!\n"))
939 939 self.ui.status(_("(did you forget to sync? use push -f to force)\n"))
940 940 return 1
941 941
942 942 update = self.findoutgoing(remote, base)
943 943 if not update:
944 944 self.ui.status(_("no changes found\n"))
945 945 return 1
946 946 elif not force:
947 947 if len(heads) < len(self.changelog.heads()):
948 948 self.ui.warn(_("abort: push creates new remote branches!\n"))
949 949 self.ui.status(_("(did you forget to merge?"
950 950 " use push -f to force)\n"))
951 951 return 1
952 952
953 953 cg = self.changegroup(update)
954 954 return remote.addchangegroup(cg)
955 955
956 956 def changegroupsubset(self, bases, heads):
957 957 """This function generates a changegroup consisting of all the nodes
958 958 that are descendents of any of the bases, and ancestors of any of
959 959 the heads.
960 960
961 961 It is fairly complex as determining which filenodes and which
962 962 manifest nodes need to be included for the changeset to be complete
963 963 is non-trivial.
964 964
965 965 Another wrinkle is doing the reverse, figuring out which changeset in
966 966 the changegroup a particular filenode or manifestnode belongs to."""
967 967
968 968 # Set up some initial variables
969 969 # Make it easy to refer to self.changelog
970 970 cl = self.changelog
971 971 # msng is short for missing - compute the list of changesets in this
972 972 # changegroup.
973 973 msng_cl_lst, bases, heads = cl.nodesbetween(bases, heads)
974 974 # Some bases may turn out to be superfluous, and some heads may be
975 975 # too. nodesbetween will return the minimal set of bases and heads
976 976 # necessary to re-create the changegroup.
977 977
978 978 # Known heads are the list of heads that it is assumed the recipient
979 979 # of this changegroup will know about.
980 980 knownheads = {}
981 981 # We assume that all parents of bases are known heads.
982 982 for n in bases:
983 983 for p in cl.parents(n):
984 984 if p != nullid:
985 985 knownheads[p] = 1
986 986 knownheads = knownheads.keys()
987 987 if knownheads:
988 988 # Now that we know what heads are known, we can compute which
989 989 # changesets are known. The recipient must know about all
990 990 # changesets required to reach the known heads from the null
991 991 # changeset.
992 992 has_cl_set, junk, junk = cl.nodesbetween(None, knownheads)
993 993 junk = None
994 994 # Transform the list into an ersatz set.
995 995 has_cl_set = dict.fromkeys(has_cl_set)
996 996 else:
997 997 # If there were no known heads, the recipient cannot be assumed to
998 998 # know about any changesets.
999 999 has_cl_set = {}
1000 1000
1001 1001 # Make it easy to refer to self.manifest
1002 1002 mnfst = self.manifest
1003 1003 # We don't know which manifests are missing yet
1004 1004 msng_mnfst_set = {}
1005 1005 # Nor do we know which filenodes are missing.
1006 1006 msng_filenode_set = {}
1007 1007
1008 1008 junk = mnfst.index[mnfst.count() - 1] # Get around a bug in lazyindex
1009 1009 junk = None
1010 1010
1011 1011 # A changeset always belongs to itself, so the changenode lookup
1012 1012 # function for a changenode is identity.
1013 1013 def identity(x):
1014 1014 return x
1015 1015
1016 1016 # A function generating function. Sets up an environment for the
1017 1017 # inner function.
1018 1018 def cmp_by_rev_func(revlog):
1019 1019 # Compare two nodes by their revision number in the environment's
1020 1020 # revision history. Since the revision number both represents the
1021 1021 # most efficient order to read the nodes in, and represents a
1022 1022 # topological sorting of the nodes, this function is often useful.
1023 1023 def cmp_by_rev(a, b):
1024 1024 return cmp(revlog.rev(a), revlog.rev(b))
1025 1025 return cmp_by_rev
1026 1026
1027 1027 # If we determine that a particular file or manifest node must be a
1028 1028 # node that the recipient of the changegroup will already have, we can
1029 1029 # also assume the recipient will have all the parents. This function
1030 1030 # prunes them from the set of missing nodes.
1031 1031 def prune_parents(revlog, hasset, msngset):
1032 1032 haslst = hasset.keys()
1033 1033 haslst.sort(cmp_by_rev_func(revlog))
1034 1034 for node in haslst:
1035 1035 parentlst = [p for p in revlog.parents(node) if p != nullid]
1036 1036 while parentlst:
1037 1037 n = parentlst.pop()
1038 1038 if n not in hasset:
1039 1039 hasset[n] = 1
1040 1040 p = [p for p in revlog.parents(n) if p != nullid]
1041 1041 parentlst.extend(p)
1042 1042 for n in hasset:
1043 1043 msngset.pop(n, None)
1044 1044
1045 1045 # This is a function generating function used to set up an environment
1046 1046 # for the inner function to execute in.
1047 1047 def manifest_and_file_collector(changedfileset):
1048 1048 # This is an information gathering function that gathers
1049 1049 # information from each changeset node that goes out as part of
1050 1050 # the changegroup. The information gathered is a list of which
1051 1051 # manifest nodes are potentially required (the recipient may
1052 1052 # already have them) and total list of all files which were
1053 1053 # changed in any changeset in the changegroup.
1054 1054 #
1055 1055 # We also remember the first changenode we saw any manifest
1056 1056 # referenced by so we can later determine which changenode 'owns'
1057 1057 # the manifest.
1058 1058 def collect_manifests_and_files(clnode):
1059 1059 c = cl.read(clnode)
1060 1060 for f in c[3]:
1061 1061 # This is to make sure we only have one instance of each
1062 1062 # filename string for each filename.
1063 1063 changedfileset.setdefault(f, f)
1064 1064 msng_mnfst_set.setdefault(c[0], clnode)
1065 1065 return collect_manifests_and_files
1066 1066
1067 1067 # Figure out which manifest nodes (of the ones we think might be part
1068 1068 # of the changegroup) the recipient must know about and remove them
1069 1069 # from the changegroup.
1070 1070 def prune_manifests():
1071 1071 has_mnfst_set = {}
1072 1072 for n in msng_mnfst_set:
1073 1073 # If a 'missing' manifest thinks it belongs to a changenode
1074 1074 # the recipient is assumed to have, obviously the recipient
1075 1075 # must have that manifest.
1076 1076 linknode = cl.node(mnfst.linkrev(n))
1077 1077 if linknode in has_cl_set:
1078 1078 has_mnfst_set[n] = 1
1079 1079 prune_parents(mnfst, has_mnfst_set, msng_mnfst_set)
1080 1080
1081 1081 # Use the information collected in collect_manifests_and_files to say
1082 1082 # which changenode any manifestnode belongs to.
1083 1083 def lookup_manifest_link(mnfstnode):
1084 1084 return msng_mnfst_set[mnfstnode]
1085 1085
1086 1086 # A function generating function that sets up the initial environment
1087 1087 # the inner function.
1088 1088 def filenode_collector(changedfiles):
1089 1089 next_rev = [0]
1090 1090 # This gathers information from each manifestnode included in the
1091 1091 # changegroup about which filenodes the manifest node references
1092 1092 # so we can include those in the changegroup too.
1093 1093 #
1094 1094 # It also remembers which changenode each filenode belongs to. It
1095 1095 # does this by assuming the a filenode belongs to the changenode
1096 1096 # the first manifest that references it belongs to.
1097 1097 def collect_msng_filenodes(mnfstnode):
1098 1098 r = mnfst.rev(mnfstnode)
1099 1099 if r == next_rev[0]:
1100 1100 # If the last rev we looked at was the one just previous,
1101 1101 # we only need to see a diff.
1102 1102 delta = mdiff.patchtext(mnfst.delta(mnfstnode))
1103 1103 # For each line in the delta
1104 1104 for dline in delta.splitlines():
1105 1105 # get the filename and filenode for that line
1106 1106 f, fnode = dline.split('\0')
1107 1107 fnode = bin(fnode[:40])
1108 1108 f = changedfiles.get(f, None)
1109 1109 # And if the file is in the list of files we care
1110 1110 # about.
1111 1111 if f is not None:
1112 1112 # Get the changenode this manifest belongs to
1113 1113 clnode = msng_mnfst_set[mnfstnode]
1114 1114 # Create the set of filenodes for the file if
1115 1115 # there isn't one already.
1116 1116 ndset = msng_filenode_set.setdefault(f, {})
1117 1117 # And set the filenode's changelog node to the
1118 1118 # manifest's if it hasn't been set already.
1119 1119 ndset.setdefault(fnode, clnode)
1120 1120 else:
1121 1121 # Otherwise we need a full manifest.
1122 1122 m = mnfst.read(mnfstnode)
1123 1123 # For every file in we care about.
1124 1124 for f in changedfiles:
1125 1125 fnode = m.get(f, None)
1126 1126 # If it's in the manifest
1127 1127 if fnode is not None:
1128 1128 # See comments above.
1129 1129 clnode = msng_mnfst_set[mnfstnode]
1130 1130 ndset = msng_filenode_set.setdefault(f, {})
1131 1131 ndset.setdefault(fnode, clnode)
1132 1132 # Remember the revision we hope to see next.
1133 1133 next_rev[0] = r + 1
1134 1134 return collect_msng_filenodes
1135 1135
1136 1136 # We have a list of filenodes we think we need for a file, lets remove
1137 1137 # all those we now the recipient must have.
1138 1138 def prune_filenodes(f, filerevlog):
1139 1139 msngset = msng_filenode_set[f]
1140 1140 hasset = {}
1141 1141 # If a 'missing' filenode thinks it belongs to a changenode we
1142 1142 # assume the recipient must have, then the recipient must have
1143 1143 # that filenode.
1144 1144 for n in msngset:
1145 1145 clnode = cl.node(filerevlog.linkrev(n))
1146 1146 if clnode in has_cl_set:
1147 1147 hasset[n] = 1
1148 1148 prune_parents(filerevlog, hasset, msngset)
1149 1149
1150 1150 # A function generator function that sets up the a context for the
1151 1151 # inner function.
1152 1152 def lookup_filenode_link_func(fname):
1153 1153 msngset = msng_filenode_set[fname]
1154 1154 # Lookup the changenode the filenode belongs to.
1155 1155 def lookup_filenode_link(fnode):
1156 1156 return msngset[fnode]
1157 1157 return lookup_filenode_link
1158 1158
1159 1159 # Now that we have all theses utility functions to help out and
1160 1160 # logically divide up the task, generate the group.
1161 1161 def gengroup():
1162 1162 # The set of changed files starts empty.
1163 1163 changedfiles = {}
1164 1164 # Create a changenode group generator that will call our functions
1165 1165 # back to lookup the owning changenode and collect information.
1166 1166 group = cl.group(msng_cl_lst, identity,
1167 1167 manifest_and_file_collector(changedfiles))
1168 1168 for chnk in group:
1169 1169 yield chnk
1170 1170
1171 1171 # The list of manifests has been collected by the generator
1172 1172 # calling our functions back.
1173 1173 prune_manifests()
1174 1174 msng_mnfst_lst = msng_mnfst_set.keys()
1175 1175 # Sort the manifestnodes by revision number.
1176 1176 msng_mnfst_lst.sort(cmp_by_rev_func(mnfst))
1177 1177 # Create a generator for the manifestnodes that calls our lookup
1178 1178 # and data collection functions back.
1179 1179 group = mnfst.group(msng_mnfst_lst, lookup_manifest_link,
1180 1180 filenode_collector(changedfiles))
1181 1181 for chnk in group:
1182 1182 yield chnk
1183 1183
1184 1184 # These are no longer needed, dereference and toss the memory for
1185 1185 # them.
1186 1186 msng_mnfst_lst = None
1187 1187 msng_mnfst_set.clear()
1188 1188
1189 1189 changedfiles = changedfiles.keys()
1190 1190 changedfiles.sort()
1191 1191 # Go through all our files in order sorted by name.
1192 1192 for fname in changedfiles:
1193 1193 filerevlog = self.file(fname)
1194 1194 # Toss out the filenodes that the recipient isn't really
1195 1195 # missing.
1196 1196 prune_filenodes(fname, filerevlog)
1197 1197 msng_filenode_lst = msng_filenode_set[fname].keys()
1198 1198 # If any filenodes are left, generate the group for them,
1199 1199 # otherwise don't bother.
1200 1200 if len(msng_filenode_lst) > 0:
1201 1201 yield struct.pack(">l", len(fname) + 4) + fname
1202 1202 # Sort the filenodes by their revision #
1203 1203 msng_filenode_lst.sort(cmp_by_rev_func(filerevlog))
1204 1204 # Create a group generator and only pass in a changenode
1205 1205 # lookup function as we need to collect no information
1206 1206 # from filenodes.
1207 1207 group = filerevlog.group(msng_filenode_lst,
1208 1208 lookup_filenode_link_func(fname))
1209 1209 for chnk in group:
1210 1210 yield chnk
1211 1211 # Don't need this anymore, toss it to free memory.
1212 1212 del msng_filenode_set[fname]
1213 1213 # Signal that no more groups are left.
1214 1214 yield struct.pack(">l", 0)
1215 1215
1216 1216 return util.chunkbuffer(gengroup())
1217 1217
1218 1218 def changegroup(self, basenodes):
1219 1219 """Generate a changegroup of all nodes that we have that a recipient
1220 1220 doesn't.
1221 1221
1222 1222 This is much easier than the previous function as we can assume that
1223 1223 the recipient has any changenode we aren't sending them."""
1224 1224 cl = self.changelog
1225 1225 nodes = cl.nodesbetween(basenodes, None)[0]
1226 1226 revset = dict.fromkeys([cl.rev(n) for n in nodes])
1227 1227
1228 1228 def identity(x):
1229 1229 return x
1230 1230
1231 1231 def gennodelst(revlog):
1232 1232 for r in xrange(0, revlog.count()):
1233 1233 n = revlog.node(r)
1234 1234 if revlog.linkrev(n) in revset:
1235 1235 yield n
1236 1236
1237 1237 def changed_file_collector(changedfileset):
1238 1238 def collect_changed_files(clnode):
1239 1239 c = cl.read(clnode)
1240 1240 for fname in c[3]:
1241 1241 changedfileset[fname] = 1
1242 1242 return collect_changed_files
1243 1243
1244 1244 def lookuprevlink_func(revlog):
1245 1245 def lookuprevlink(n):
1246 1246 return cl.node(revlog.linkrev(n))
1247 1247 return lookuprevlink
1248 1248
1249 1249 def gengroup():
1250 1250 # construct a list of all changed files
1251 1251 changedfiles = {}
1252 1252
1253 1253 for chnk in cl.group(nodes, identity,
1254 1254 changed_file_collector(changedfiles)):
1255 1255 yield chnk
1256 1256 changedfiles = changedfiles.keys()
1257 1257 changedfiles.sort()
1258 1258
1259 1259 mnfst = self.manifest
1260 1260 nodeiter = gennodelst(mnfst)
1261 1261 for chnk in mnfst.group(nodeiter, lookuprevlink_func(mnfst)):
1262 1262 yield chnk
1263 1263
1264 1264 for fname in changedfiles:
1265 1265 filerevlog = self.file(fname)
1266 1266 nodeiter = gennodelst(filerevlog)
1267 1267 nodeiter = list(nodeiter)
1268 1268 if nodeiter:
1269 1269 yield struct.pack(">l", len(fname) + 4) + fname
1270 1270 lookup = lookuprevlink_func(filerevlog)
1271 1271 for chnk in filerevlog.group(nodeiter, lookup):
1272 1272 yield chnk
1273 1273
1274 1274 yield struct.pack(">l", 0)
1275 1275
1276 1276 return util.chunkbuffer(gengroup())
1277 1277
1278 1278 def addchangegroup(self, source):
1279 1279
1280 1280 def getchunk():
1281 1281 d = source.read(4)
1282 1282 if not d: return ""
1283 1283 l = struct.unpack(">l", d)[0]
1284 1284 if l <= 4: return ""
1285 1285 d = source.read(l - 4)
1286 1286 if len(d) < l - 4:
1287 1287 raise repo.RepoError(_("premature EOF reading chunk"
1288 1288 " (got %d bytes, expected %d)")
1289 1289 % (len(d), l - 4))
1290 1290 return d
1291 1291
1292 1292 def getgroup():
1293 1293 while 1:
1294 1294 c = getchunk()
1295 1295 if not c: break
1296 1296 yield c
1297 1297
1298 1298 def csmap(x):
1299 1299 self.ui.debug(_("add changeset %s\n") % short(x))
1300 1300 return self.changelog.count()
1301 1301
1302 1302 def revmap(x):
1303 1303 return self.changelog.rev(x)
1304 1304
1305 1305 if not source: return
1306 1306 changesets = files = revisions = 0
1307 1307
1308 1308 tr = self.transaction()
1309 1309
1310 1310 oldheads = len(self.changelog.heads())
1311 1311
1312 1312 # pull off the changeset group
1313 1313 self.ui.status(_("adding changesets\n"))
1314 1314 co = self.changelog.tip()
1315 1315 cn = self.changelog.addgroup(getgroup(), csmap, tr, 1) # unique
1316 1316 cnr, cor = map(self.changelog.rev, (cn, co))
1317 1317 if cn == nullid:
1318 1318 cnr = cor
1319 1319 changesets = cnr - cor
1320 1320
1321 1321 # pull off the manifest group
1322 1322 self.ui.status(_("adding manifests\n"))
1323 1323 mm = self.manifest.tip()
1324 1324 mo = self.manifest.addgroup(getgroup(), revmap, tr)
1325 1325
1326 1326 # process the files
1327 1327 self.ui.status(_("adding file changes\n"))
1328 1328 while 1:
1329 1329 f = getchunk()
1330 1330 if not f: break
1331 1331 self.ui.debug(_("adding %s revisions\n") % f)
1332 1332 fl = self.file(f)
1333 1333 o = fl.count()
1334 1334 n = fl.addgroup(getgroup(), revmap, tr)
1335 1335 revisions += fl.count() - o
1336 1336 files += 1
1337 1337
1338 1338 newheads = len(self.changelog.heads())
1339 1339 heads = ""
1340 1340 if oldheads and newheads > oldheads:
1341 1341 heads = _(" (+%d heads)") % (newheads - oldheads)
1342 1342
1343 1343 self.ui.status(_("added %d changesets"
1344 1344 " with %d changes to %d files%s\n")
1345 1345 % (changesets, revisions, files, heads))
1346 1346
1347 1347 tr.close()
1348 1348
1349 1349 if changesets > 0:
1350 1350 if not self.hook("changegroup",
1351 1351 node=hex(self.changelog.node(cor+1))):
1352 1352 self.ui.warn(_("abort: changegroup hook returned failure!\n"))
1353 1353 return 1
1354 1354
1355 1355 for i in range(cor + 1, cnr + 1):
1356 1356 self.hook("commit", node=hex(self.changelog.node(i)))
1357 1357
1358 1358 return
1359 1359
1360 1360 def update(self, node, allow=False, force=False, choose=None,
1361 moddirstate=True):
1361 moddirstate=True, forcemerge=False):
1362 1362 pl = self.dirstate.parents()
1363 1363 if not force and pl[1] != nullid:
1364 1364 self.ui.warn(_("aborting: outstanding uncommitted merges\n"))
1365 1365 return 1
1366 1366
1367 1367 p1, p2 = pl[0], node
1368 1368 pa = self.changelog.ancestor(p1, p2)
1369 1369 m1n = self.changelog.read(p1)[0]
1370 1370 m2n = self.changelog.read(p2)[0]
1371 1371 man = self.manifest.ancestor(m1n, m2n)
1372 1372 m1 = self.manifest.read(m1n)
1373 1373 mf1 = self.manifest.readflags(m1n)
1374 1374 m2 = self.manifest.read(m2n)
1375 1375 mf2 = self.manifest.readflags(m2n)
1376 1376 ma = self.manifest.read(man)
1377 1377 mfa = self.manifest.readflags(man)
1378 1378
1379 1379 (c, a, d, u) = self.changes()
1380 1380
1381 if allow and not forcemerge:
1382 if c or a or d:
1383 raise util.Abort(_("outstanding uncommited changes"))
1384 if not forcemerge and not force:
1385 for f in u:
1386 if f in m2:
1387 t1 = self.wread(f)
1388 t2 = self.file(f).read(m2[f])
1389 if cmp(t1, t2) != 0:
1390 raise util.Abort(_("'%s' already exists in the working"
1391 " dir and differs from remote") % f)
1392
1381 1393 # is this a jump, or a merge? i.e. is there a linear path
1382 1394 # from p1 to p2?
1383 1395 linear_path = (pa == p1 or pa == p2)
1384 1396
1385 1397 # resolve the manifest to determine which files
1386 1398 # we care about merging
1387 1399 self.ui.note(_("resolving manifests\n"))
1388 1400 self.ui.debug(_(" force %s allow %s moddirstate %s linear %s\n") %
1389 1401 (force, allow, moddirstate, linear_path))
1390 1402 self.ui.debug(_(" ancestor %s local %s remote %s\n") %
1391 1403 (short(man), short(m1n), short(m2n)))
1392 1404
1393 1405 merge = {}
1394 1406 get = {}
1395 1407 remove = []
1396 1408
1397 1409 # construct a working dir manifest
1398 1410 mw = m1.copy()
1399 1411 mfw = mf1.copy()
1400 1412 umap = dict.fromkeys(u)
1401 1413
1402 1414 for f in a + c + u:
1403 1415 mw[f] = ""
1404 1416 mfw[f] = util.is_exec(self.wjoin(f), mfw.get(f, False))
1405 1417
1406 1418 if moddirstate:
1407 1419 wlock = self.wlock()
1408 1420
1409 1421 for f in d:
1410 1422 if f in mw: del mw[f]
1411 1423
1412 1424 # If we're jumping between revisions (as opposed to merging),
1413 1425 # and if neither the working directory nor the target rev has
1414 1426 # the file, then we need to remove it from the dirstate, to
1415 1427 # prevent the dirstate from listing the file when it is no
1416 1428 # longer in the manifest.
1417 1429 if moddirstate and linear_path and f not in m2:
1418 1430 self.dirstate.forget((f,))
1419 1431
1420 1432 # Compare manifests
1421 1433 for f, n in mw.iteritems():
1422 1434 if choose and not choose(f): continue
1423 1435 if f in m2:
1424 1436 s = 0
1425 1437
1426 1438 # is the wfile new since m1, and match m2?
1427 1439 if f not in m1:
1428 1440 t1 = self.wread(f)
1429 1441 t2 = self.file(f).read(m2[f])
1430 1442 if cmp(t1, t2) == 0:
1431 1443 n = m2[f]
1432 1444 del t1, t2
1433 1445
1434 1446 # are files different?
1435 1447 if n != m2[f]:
1436 1448 a = ma.get(f, nullid)
1437 1449 # are both different from the ancestor?
1438 1450 if n != a and m2[f] != a:
1439 1451 self.ui.debug(_(" %s versions differ, resolve\n") % f)
1440 1452 # merge executable bits
1441 1453 # "if we changed or they changed, change in merge"
1442 1454 a, b, c = mfa.get(f, 0), mfw[f], mf2[f]
1443 1455 mode = ((a^b) | (a^c)) ^ a
1444 1456 merge[f] = (m1.get(f, nullid), m2[f], mode)
1445 1457 s = 1
1446 1458 # are we clobbering?
1447 1459 # is remote's version newer?
1448 1460 # or are we going back in time?
1449 1461 elif force or m2[f] != a or (p2 == pa and mw[f] == m1[f]):
1450 1462 self.ui.debug(_(" remote %s is newer, get\n") % f)
1451 1463 get[f] = m2[f]
1452 1464 s = 1
1453 1465 elif f in umap:
1454 1466 # this unknown file is the same as the checkout
1455 1467 get[f] = m2[f]
1456 1468
1457 1469 if not s and mfw[f] != mf2[f]:
1458 1470 if force:
1459 1471 self.ui.debug(_(" updating permissions for %s\n") % f)
1460 1472 util.set_exec(self.wjoin(f), mf2[f])
1461 1473 else:
1462 1474 a, b, c = mfa.get(f, 0), mfw[f], mf2[f]
1463 1475 mode = ((a^b) | (a^c)) ^ a
1464 1476 if mode != b:
1465 1477 self.ui.debug(_(" updating permissions for %s\n") % f)
1466 1478 util.set_exec(self.wjoin(f), mode)
1467 1479 del m2[f]
1468 1480 elif f in ma:
1469 1481 if n != ma[f]:
1470 1482 r = _("d")
1471 1483 if not force and (linear_path or allow):
1472 1484 r = self.ui.prompt(
1473 1485 (_(" local changed %s which remote deleted\n") % f) +
1474 1486 _("(k)eep or (d)elete?"), _("[kd]"), _("k"))
1475 1487 if r == _("d"):
1476 1488 remove.append(f)
1477 1489 else:
1478 1490 self.ui.debug(_("other deleted %s\n") % f)
1479 1491 remove.append(f) # other deleted it
1480 1492 else:
1481 1493 # file is created on branch or in working directory
1482 1494 if force and f not in umap:
1483 1495 self.ui.debug(_("remote deleted %s, clobbering\n") % f)
1484 1496 remove.append(f)
1485 1497 elif n == m1.get(f, nullid): # same as parent
1486 1498 if p2 == pa: # going backwards?
1487 1499 self.ui.debug(_("remote deleted %s\n") % f)
1488 1500 remove.append(f)
1489 1501 else:
1490 1502 self.ui.debug(_("local modified %s, keeping\n") % f)
1491 1503 else:
1492 1504 self.ui.debug(_("working dir created %s, keeping\n") % f)
1493 1505
1494 1506 for f, n in m2.iteritems():
1495 1507 if choose and not choose(f): continue
1496 1508 if f[0] == "/": continue
1497 1509 if f in ma and n != ma[f]:
1498 1510 r = _("k")
1499 1511 if not force and (linear_path or allow):
1500 1512 r = self.ui.prompt(
1501 1513 (_("remote changed %s which local deleted\n") % f) +
1502 1514 _("(k)eep or (d)elete?"), _("[kd]"), _("k"))
1503 1515 if r == _("k"): get[f] = n
1504 1516 elif f not in ma:
1505 1517 self.ui.debug(_("remote created %s\n") % f)
1506 1518 get[f] = n
1507 1519 else:
1508 1520 if force or p2 == pa: # going backwards?
1509 1521 self.ui.debug(_("local deleted %s, recreating\n") % f)
1510 1522 get[f] = n
1511 1523 else:
1512 1524 self.ui.debug(_("local deleted %s\n") % f)
1513 1525
1514 1526 del mw, m1, m2, ma
1515 1527
1516 1528 if force:
1517 1529 for f in merge:
1518 1530 get[f] = merge[f][1]
1519 1531 merge = {}
1520 1532
1521 1533 if linear_path or force:
1522 1534 # we don't need to do any magic, just jump to the new rev
1523 1535 branch_merge = False
1524 1536 p1, p2 = p2, nullid
1525 1537 else:
1526 1538 if not allow:
1527 1539 self.ui.status(_("this update spans a branch"
1528 1540 " affecting the following files:\n"))
1529 1541 fl = merge.keys() + get.keys()
1530 1542 fl.sort()
1531 1543 for f in fl:
1532 1544 cf = ""
1533 1545 if f in merge: cf = _(" (resolve)")
1534 1546 self.ui.status(" %s%s\n" % (f, cf))
1535 1547 self.ui.warn(_("aborting update spanning branches!\n"))
1536 1548 self.ui.status(_("(use update -m to merge across branches"
1537 1549 " or -C to lose changes)\n"))
1538 1550 return 1
1539 1551 branch_merge = True
1540 1552
1541 1553 # get the files we don't need to change
1542 1554 files = get.keys()
1543 1555 files.sort()
1544 1556 for f in files:
1545 1557 if f[0] == "/": continue
1546 1558 self.ui.note(_("getting %s\n") % f)
1547 1559 t = self.file(f).read(get[f])
1548 1560 self.wwrite(f, t)
1549 1561 util.set_exec(self.wjoin(f), mf2[f])
1550 1562 if moddirstate:
1551 1563 if branch_merge:
1552 1564 self.dirstate.update([f], 'n', st_mtime=-1)
1553 1565 else:
1554 1566 self.dirstate.update([f], 'n')
1555 1567
1556 1568 # merge the tricky bits
1557 1569 files = merge.keys()
1558 1570 files.sort()
1559 1571 for f in files:
1560 1572 self.ui.status(_("merging %s\n") % f)
1561 1573 my, other, flag = merge[f]
1562 1574 self.merge3(f, my, other)
1563 1575 util.set_exec(self.wjoin(f), flag)
1564 1576 if moddirstate:
1565 1577 if branch_merge:
1566 1578 # We've done a branch merge, mark this file as merged
1567 1579 # so that we properly record the merger later
1568 1580 self.dirstate.update([f], 'm')
1569 1581 else:
1570 1582 # We've update-merged a locally modified file, so
1571 1583 # we set the dirstate to emulate a normal checkout
1572 1584 # of that file some time in the past. Thus our
1573 1585 # merge will appear as a normal local file
1574 1586 # modification.
1575 1587 f_len = len(self.file(f).read(other))
1576 1588 self.dirstate.update([f], 'n', st_size=f_len, st_mtime=-1)
1577 1589
1578 1590 remove.sort()
1579 1591 for f in remove:
1580 1592 self.ui.note(_("removing %s\n") % f)
1581 1593 try:
1582 1594 util.unlink(self.wjoin(f))
1583 1595 except OSError, inst:
1584 1596 if inst.errno != errno.ENOENT:
1585 1597 self.ui.warn(_("update failed to remove %s: %s!\n") %
1586 1598 (f, inst.strerror))
1587 1599 if moddirstate:
1588 1600 if branch_merge:
1589 1601 self.dirstate.update(remove, 'r')
1590 1602 else:
1591 1603 self.dirstate.forget(remove)
1592 1604
1593 1605 if moddirstate:
1594 1606 self.dirstate.setparents(p1, p2)
1595 1607
1596 1608 def merge3(self, fn, my, other):
1597 1609 """perform a 3-way merge in the working directory"""
1598 1610
1599 1611 def temp(prefix, node):
1600 1612 pre = "%s~%s." % (os.path.basename(fn), prefix)
1601 1613 (fd, name) = tempfile.mkstemp("", pre)
1602 1614 f = os.fdopen(fd, "wb")
1603 1615 self.wwrite(fn, fl.read(node), f)
1604 1616 f.close()
1605 1617 return name
1606 1618
1607 1619 fl = self.file(fn)
1608 1620 base = fl.ancestor(my, other)
1609 1621 a = self.wjoin(fn)
1610 1622 b = temp("base", base)
1611 1623 c = temp("other", other)
1612 1624
1613 1625 self.ui.note(_("resolving %s\n") % fn)
1614 1626 self.ui.debug(_("file %s: my %s other %s ancestor %s\n") %
1615 1627 (fn, short(my), short(other), short(base)))
1616 1628
1617 1629 cmd = (os.environ.get("HGMERGE") or self.ui.config("ui", "merge")
1618 1630 or "hgmerge")
1619 1631 r = os.system('%s "%s" "%s" "%s"' % (cmd, a, b, c))
1620 1632 if r:
1621 1633 self.ui.warn(_("merging %s failed!\n") % fn)
1622 1634
1623 1635 os.unlink(b)
1624 1636 os.unlink(c)
1625 1637
1626 1638 def verify(self):
1627 1639 filelinkrevs = {}
1628 1640 filenodes = {}
1629 1641 changesets = revisions = files = 0
1630 1642 errors = [0]
1631 1643 neededmanifests = {}
1632 1644
1633 1645 def err(msg):
1634 1646 self.ui.warn(msg + "\n")
1635 1647 errors[0] += 1
1636 1648
1637 1649 seen = {}
1638 1650 self.ui.status(_("checking changesets\n"))
1639 1651 d = self.changelog.checksize()
1640 1652 if d:
1641 1653 err(_("changeset data short %d bytes") % d)
1642 1654 for i in range(self.changelog.count()):
1643 1655 changesets += 1
1644 1656 n = self.changelog.node(i)
1645 1657 l = self.changelog.linkrev(n)
1646 1658 if l != i:
1647 1659 err(_("incorrect link (%d) for changeset revision %d") %(l, i))
1648 1660 if n in seen:
1649 1661 err(_("duplicate changeset at revision %d") % i)
1650 1662 seen[n] = 1
1651 1663
1652 1664 for p in self.changelog.parents(n):
1653 1665 if p not in self.changelog.nodemap:
1654 1666 err(_("changeset %s has unknown parent %s") %
1655 1667 (short(n), short(p)))
1656 1668 try:
1657 1669 changes = self.changelog.read(n)
1658 1670 except KeyboardInterrupt:
1659 1671 self.ui.warn(_("interrupted"))
1660 1672 raise
1661 1673 except Exception, inst:
1662 1674 err(_("unpacking changeset %s: %s") % (short(n), inst))
1663 1675
1664 1676 neededmanifests[changes[0]] = n
1665 1677
1666 1678 for f in changes[3]:
1667 1679 filelinkrevs.setdefault(f, []).append(i)
1668 1680
1669 1681 seen = {}
1670 1682 self.ui.status(_("checking manifests\n"))
1671 1683 d = self.manifest.checksize()
1672 1684 if d:
1673 1685 err(_("manifest data short %d bytes") % d)
1674 1686 for i in range(self.manifest.count()):
1675 1687 n = self.manifest.node(i)
1676 1688 l = self.manifest.linkrev(n)
1677 1689
1678 1690 if l < 0 or l >= self.changelog.count():
1679 1691 err(_("bad manifest link (%d) at revision %d") % (l, i))
1680 1692
1681 1693 if n in neededmanifests:
1682 1694 del neededmanifests[n]
1683 1695
1684 1696 if n in seen:
1685 1697 err(_("duplicate manifest at revision %d") % i)
1686 1698
1687 1699 seen[n] = 1
1688 1700
1689 1701 for p in self.manifest.parents(n):
1690 1702 if p not in self.manifest.nodemap:
1691 1703 err(_("manifest %s has unknown parent %s") %
1692 1704 (short(n), short(p)))
1693 1705
1694 1706 try:
1695 1707 delta = mdiff.patchtext(self.manifest.delta(n))
1696 1708 except KeyboardInterrupt:
1697 1709 self.ui.warn(_("interrupted"))
1698 1710 raise
1699 1711 except Exception, inst:
1700 1712 err(_("unpacking manifest %s: %s") % (short(n), inst))
1701 1713
1702 1714 ff = [ l.split('\0') for l in delta.splitlines() ]
1703 1715 for f, fn in ff:
1704 1716 filenodes.setdefault(f, {})[bin(fn[:40])] = 1
1705 1717
1706 1718 self.ui.status(_("crosschecking files in changesets and manifests\n"))
1707 1719
1708 1720 for m,c in neededmanifests.items():
1709 1721 err(_("Changeset %s refers to unknown manifest %s") %
1710 1722 (short(m), short(c)))
1711 1723 del neededmanifests
1712 1724
1713 1725 for f in filenodes:
1714 1726 if f not in filelinkrevs:
1715 1727 err(_("file %s in manifest but not in changesets") % f)
1716 1728
1717 1729 for f in filelinkrevs:
1718 1730 if f not in filenodes:
1719 1731 err(_("file %s in changeset but not in manifest") % f)
1720 1732
1721 1733 self.ui.status(_("checking files\n"))
1722 1734 ff = filenodes.keys()
1723 1735 ff.sort()
1724 1736 for f in ff:
1725 1737 if f == "/dev/null": continue
1726 1738 files += 1
1727 1739 fl = self.file(f)
1728 1740 d = fl.checksize()
1729 1741 if d:
1730 1742 err(_("%s file data short %d bytes") % (f, d))
1731 1743
1732 1744 nodes = { nullid: 1 }
1733 1745 seen = {}
1734 1746 for i in range(fl.count()):
1735 1747 revisions += 1
1736 1748 n = fl.node(i)
1737 1749
1738 1750 if n in seen:
1739 1751 err(_("%s: duplicate revision %d") % (f, i))
1740 1752 if n not in filenodes[f]:
1741 1753 err(_("%s: %d:%s not in manifests") % (f, i, short(n)))
1742 1754 else:
1743 1755 del filenodes[f][n]
1744 1756
1745 1757 flr = fl.linkrev(n)
1746 1758 if flr not in filelinkrevs[f]:
1747 1759 err(_("%s:%s points to unexpected changeset %d")
1748 1760 % (f, short(n), flr))
1749 1761 else:
1750 1762 filelinkrevs[f].remove(flr)
1751 1763
1752 1764 # verify contents
1753 1765 try:
1754 1766 t = fl.read(n)
1755 1767 except KeyboardInterrupt:
1756 1768 self.ui.warn(_("interrupted"))
1757 1769 raise
1758 1770 except Exception, inst:
1759 1771 err(_("unpacking file %s %s: %s") % (f, short(n), inst))
1760 1772
1761 1773 # verify parents
1762 1774 (p1, p2) = fl.parents(n)
1763 1775 if p1 not in nodes:
1764 1776 err(_("file %s:%s unknown parent 1 %s") %
1765 1777 (f, short(n), short(p1)))
1766 1778 if p2 not in nodes:
1767 1779 err(_("file %s:%s unknown parent 2 %s") %
1768 1780 (f, short(n), short(p1)))
1769 1781 nodes[n] = 1
1770 1782
1771 1783 # cross-check
1772 1784 for node in filenodes[f]:
1773 1785 err(_("node %s in manifests not in %s") % (hex(node), f))
1774 1786
1775 1787 self.ui.status(_("%d files, %d changesets, %d total revisions\n") %
1776 1788 (files, changesets, revisions))
1777 1789
1778 1790 if errors[0]:
1779 1791 self.ui.warn(_("%d integrity errors encountered!\n") % errors[0])
1780 1792 return 1
@@ -1,90 +1,96 b''
1 1 #!/bin/sh
2 2
3 3 cat <<'EOF' > merge
4 4 #!/bin/sh
5 5 echo merging for `basename $1`
6 6 EOF
7 7 chmod +x merge
8 8
9 9 mkdir t
10 10 cd t
11 11 hg init
12 12 echo This is file a1 > a
13 13 hg add a
14 14 hg commit -m "commit #0" -d "0 0"
15 15 echo This is file b1 > b
16 16 hg add b
17 17 hg commit -m "commit #1" -d "0 0"
18 18
19 19 hg update 0
20 20 echo This is file c1 > c
21 21 hg add c
22 22 hg commit -m "commit #2" -d "0 0"
23 23 echo This is file b1 > b
24 24 echo %% no merges expected
25 25 env HGMERGE=../merge hg update -m 1
26 26 cd ..; /bin/rm -rf t
27 27
28 28 mkdir t
29 29 cd t
30 30 hg init
31 31 echo This is file a1 > a
32 32 hg add a
33 33 hg commit -m "commit #0" -d "0 0"
34 34 echo This is file b1 > b
35 35 hg add b
36 36 hg commit -m "commit #1" -d "0 0"
37 37
38 38 hg update 0
39 39 echo This is file c1 > c
40 40 hg add c
41 41 hg commit -m "commit #2" -d "0 0"
42 42 echo This is file b2 > b
43 echo %% merge should fail
44 env HGMERGE=../merge hg update -m 1
43 45 echo %% merge of b expected
44 env HGMERGE=../merge hg update -m 1
46 env HGMERGE=../merge hg update -f -m 1
45 47 cd ..; /bin/rm -rf t
46 48 echo %%
47 49
48 50 mkdir t
49 51 cd t
50 52 hg init
51 53 echo This is file a1 > a
52 54 hg add a
53 55 hg commit -m "commit #0" -d "0 0"
54 56 echo This is file b1 > b
55 57 hg add b
56 58 hg commit -m "commit #1" -d "0 0"
57 59 echo This is file b22 > b
58 60 hg commit -m "commit #2" -d "0 0"
59 61 hg update 1
60 62 echo This is file c1 > c
61 63 hg add c
62 64 hg commit -m "commit #3" -d "0 0"
63 65
64 66 echo 'Contents of b should be "this is file b1"'
65 67 cat b
66 68
67 69 echo This is file b22 > b
70 echo %% merge fails
71 env HGMERGE=../merge hg update -m 2
68 72 echo %% merge expected!
69 env HGMERGE=../merge hg update -m 2
73 env HGMERGE=../merge hg update -f -m 2
70 74 cd ..; /bin/rm -rf t
71 75
72 76 mkdir t
73 77 cd t
74 78 hg init
75 79 echo This is file a1 > a
76 80 hg add a
77 81 hg commit -m "commit #0" -d "0 0"
78 82 echo This is file b1 > b
79 83 hg add b
80 84 hg commit -m "commit #1" -d "0 0"
81 85 echo This is file b22 > b
82 86 hg commit -m "commit #2" -d "0 0"
83 87 hg update 1
84 88 echo This is file c1 > c
85 89 hg add c
86 90 hg commit -m "commit #3" -d "0 0"
87 91 echo This is file b33 > b
88 echo %% merge of b expected
92 echo %% merge of b should fail
89 93 env HGMERGE=../merge hg update -m 2
94 echo %% merge of b expected
95 env HGMERGE=../merge hg update -f -m 2
90 96 cd ..; /bin/rm -rf t
@@ -1,13 +1,19 b''
1 1 %% no merges expected
2 %% merge should fail
3 abort: 'b' already exists in the working dir and differs from remote
2 4 %% merge of b expected
3 5 merging for b
4 6 merging b
5 7 %%
6 8 Contents of b should be "this is file b1"
7 9 This is file b1
10 %% merge fails
11 abort: outstanding uncommited changes
8 12 %% merge expected!
9 13 merging for b
10 14 merging b
15 %% merge of b should fail
16 abort: outstanding uncommited changes
11 17 %% merge of b expected
12 18 merging for b
13 19 merging b
@@ -1,33 +1,34 b''
1 1 #!/bin/sh
2 2
3 3 set -e
4 4 mkdir r1
5 5 cd r1
6 6 hg init
7 7 echo a > a
8 8 hg addremove
9 9 hg commit -m "1" -d "0 0"
10 10
11 11 hg clone . ../r2
12 12 cd ../r2
13 13 hg up
14 14 echo abc > a
15 15 hg diff | sed -e "s/\(+++ [a-zA-Z0-9_/.-]*\).*/\1/" \
16 16 -e "s/\(--- [a-zA-Z0-9_/.-]*\).*/\1/"
17 17
18 18 cd ../r1
19 19 echo b > b
20 20 echo a2 > a
21 21 hg addremove
22 22 hg commit -m "2" -d "0 0"
23 23
24 24 cd ../r2
25 25 hg -q pull ../r1
26 26 hg status
27 27 hg --debug up
28 hg --debug up -m
28 hg --debug up -m || echo failed
29 hg --debug up -f -m
29 30 hg parents
30 31 hg -v history
31 32 hg diff | sed -e "s/\(+++ [a-zA-Z0-9_/.-]*\).*/\1/" \
32 33 -e "s/\(--- [a-zA-Z0-9_/.-]*\).*/\1/"
33 34
@@ -1,50 +1,52 b''
1 1 adding a
2 2 diff -r c19d34741b0a a
3 3 --- a/a
4 4 +++ b/a
5 5 @@ -1,1 +1,1 @@
6 6 -a
7 7 +abc
8 8 adding b
9 9 M a
10 10 resolving manifests
11 11 force None allow None moddirstate True linear True
12 12 ancestor a0c8bcbbb45c local a0c8bcbbb45c remote 1165e8bd193e
13 13 a versions differ, resolve
14 14 remote created b
15 15 getting b
16 16 merging a
17 17 resolving a
18 18 file a: my b789fdd96dc2 other d730145abbf9 ancestor b789fdd96dc2
19 abort: outstanding uncommited changes
20 failed
19 21 resolving manifests
20 22 force None allow 1 moddirstate True linear True
21 23 ancestor 1165e8bd193e local 1165e8bd193e remote 1165e8bd193e
22 24 changeset: 1:1e71731e6fbb
23 25 tag: tip
24 26 user: test
25 27 date: Thu Jan 1 00:00:00 1970 +0000
26 28 summary: 2
27 29
28 30 changeset: 1:1e71731e6fbb5b35fae293120dea6964371c13c6
29 31 tag: tip
30 32 user: test
31 33 date: Thu Jan 1 00:00:00 1970 +0000
32 34 files: a b
33 35 description:
34 36 2
35 37
36 38
37 39 changeset: 0:c19d34741b0a4ced8e4ba74bb834597d5193851e
38 40 user: test
39 41 date: Thu Jan 1 00:00:00 1970 +0000
40 42 files: a
41 43 description:
42 44 1
43 45
44 46
45 47 diff -r 1e71731e6fbb a
46 48 --- a/a
47 49 +++ b/a
48 50 @@ -1,1 +1,1 @@
49 51 -a2
50 52 +abc
General Comments 0
You need to be logged in to leave comments. Login now