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