##// END OF EJS Templates
Merge with stable
Wagner Bruna -
r11721:0b8d17bb merge stable
parent child Browse files
Show More

The requested changes are too big and content was truncated. Show full diff

@@ -1,42 +1,47 b''
1 1 #!/usr/bin/env python
2 2 #
3 3 # runrst - register custom roles and run correct writer
4 4 #
5 5 # Copyright 2010 Matt Mackall <mpm@selenic.com> and others
6 6 #
7 7 # This software may be used and distributed according to the terms of the
8 8 # GNU General Public License version 2 or any later version.
9 9
10 10 """usage: %s WRITER args...
11 11
12 12 where WRITER is the name of a Docutils writer such as 'html' or 'manpage'
13 13 """
14 14
15 15 import sys
16 from docutils.parsers.rst import roles
17 from docutils.core import publish_cmdline
18 from docutils import nodes, utils
16 try:
17 from docutils.parsers.rst import roles
18 from docutils.core import publish_cmdline
19 from docutils import nodes, utils
20 except ImportError:
21 sys.stderr.write("abort: couldn't generate documentation: docutils "
22 "module is missing\n")
23 sys.exit(-1)
19 24
20 25 def role_hg(name, rawtext, text, lineno, inliner,
21 26 options={}, content=[]):
22 27 text = "hg " + utils.unescape(text)
23 28 linktext = nodes.literal(rawtext, text)
24 29 parts = text.split()
25 30 cmd, args = parts[1], parts[2:]
26 31 if cmd == 'help' and args:
27 32 cmd = args[0] # link to 'dates' for 'hg help dates'
28 33 node = nodes.reference(rawtext, '', linktext,
29 34 refuri="hg.1.html#%s" % cmd)
30 35 return [node], []
31 36
32 37 roles.register_local_role("hg", role_hg)
33 38
34 39 if __name__ == "__main__":
35 40 if len(sys.argv) < 2:
36 41 sys.stderr.write(__doc__ % sys.argv[0])
37 42 sys.exit(1)
38 43
39 44 writer = sys.argv[1]
40 45 del sys.argv[1]
41 46
42 47 publish_cmdline(writer_name=writer)
@@ -1,531 +1,534 b''
1 1 # Mercurial extension to provide the 'hg bookmark' command
2 2 #
3 3 # Copyright 2008 David Soria Parra <dsp@php.net>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 '''track a line of development with movable markers
9 9
10 10 Bookmarks are local movable markers to changesets. Every bookmark
11 11 points to a changeset identified by its hash. If you commit a
12 12 changeset that is based on a changeset that has a bookmark on it, the
13 13 bookmark shifts to the new changeset.
14 14
15 15 It is possible to use bookmark names in every revision lookup (e.g.
16 16 :hg:`merge`, :hg:`update`).
17 17
18 18 By default, when several bookmarks point to the same changeset, they
19 19 will all move forward together. It is possible to obtain a more
20 20 git-like experience by adding the following configuration option to
21 21 your .hgrc::
22 22
23 23 [bookmarks]
24 24 track.current = True
25 25
26 26 This will cause Mercurial to track the bookmark that you are currently
27 27 using, and only update it. This is similar to git's approach to
28 28 branching.
29 29 '''
30 30
31 31 from mercurial.i18n import _
32 32 from mercurial.node import nullid, nullrev, hex, short
33 33 from mercurial import util, commands, repair, extensions, pushkey, hg, url
34 34 import os
35 35
36 36 def write(repo):
37 37 '''Write bookmarks
38 38
39 39 Write the given bookmark => hash dictionary to the .hg/bookmarks file
40 40 in a format equal to those of localtags.
41 41
42 42 We also store a backup of the previous state in undo.bookmarks that
43 43 can be copied back on rollback.
44 44 '''
45 45 refs = repo._bookmarks
46 46 if os.path.exists(repo.join('bookmarks')):
47 47 util.copyfile(repo.join('bookmarks'), repo.join('undo.bookmarks'))
48 48 if repo._bookmarkcurrent not in refs:
49 49 setcurrent(repo, None)
50 50 wlock = repo.wlock()
51 51 try:
52 52 file = repo.opener('bookmarks', 'w', atomictemp=True)
53 53 for refspec, node in refs.iteritems():
54 54 file.write("%s %s\n" % (hex(node), refspec))
55 55 file.rename()
56 56
57 57 # touch 00changelog.i so hgweb reloads bookmarks (no lock needed)
58 58 try:
59 59 os.utime(repo.sjoin('00changelog.i'), None)
60 60 except OSError:
61 61 pass
62 62
63 63 finally:
64 64 wlock.release()
65 65
66 66 def setcurrent(repo, mark):
67 67 '''Set the name of the bookmark that we are currently on
68 68
69 69 Set the name of the bookmark that we are on (hg update <bookmark>).
70 70 The name is recorded in .hg/bookmarks.current
71 71 '''
72 72 current = repo._bookmarkcurrent
73 73 if current == mark:
74 74 return
75 75
76 76 refs = repo._bookmarks
77 77
78 78 # do not update if we do update to a rev equal to the current bookmark
79 79 if (mark and mark not in refs and
80 80 current and refs[current] == repo.changectx('.').node()):
81 81 return
82 82 if mark not in refs:
83 83 mark = ''
84 84 wlock = repo.wlock()
85 85 try:
86 86 file = repo.opener('bookmarks.current', 'w', atomictemp=True)
87 87 file.write(mark)
88 88 file.rename()
89 89 finally:
90 90 wlock.release()
91 91 repo._bookmarkcurrent = mark
92 92
93 93 def bookmark(ui, repo, mark=None, rev=None, force=False, delete=False, rename=None):
94 94 '''track a line of development with movable markers
95 95
96 96 Bookmarks are pointers to certain commits that move when
97 97 committing. Bookmarks are local. They can be renamed, copied and
98 98 deleted. It is possible to use bookmark names in :hg:`merge` and
99 99 :hg:`update` to merge and update respectively to a given bookmark.
100 100
101 101 You can use :hg:`bookmark NAME` to set a bookmark on the working
102 102 directory's parent revision with the given name. If you specify
103 103 a revision using -r REV (where REV may be an existing bookmark),
104 104 the bookmark is assigned to that revision.
105 105 '''
106 106 hexfn = ui.debugflag and hex or short
107 107 marks = repo._bookmarks
108 108 cur = repo.changectx('.').node()
109 109
110 110 if rename:
111 111 if rename not in marks:
112 112 raise util.Abort(_("a bookmark of this name does not exist"))
113 113 if mark in marks and not force:
114 114 raise util.Abort(_("a bookmark of the same name already exists"))
115 115 if mark is None:
116 116 raise util.Abort(_("new bookmark name required"))
117 117 marks[mark] = marks[rename]
118 118 del marks[rename]
119 119 if repo._bookmarkcurrent == rename:
120 120 setcurrent(repo, mark)
121 121 write(repo)
122 122 return
123 123
124 124 if delete:
125 125 if mark is None:
126 126 raise util.Abort(_("bookmark name required"))
127 127 if mark not in marks:
128 128 raise util.Abort(_("a bookmark of this name does not exist"))
129 129 if mark == repo._bookmarkcurrent:
130 130 setcurrent(repo, None)
131 131 del marks[mark]
132 132 write(repo)
133 133 return
134 134
135 135 if mark != None:
136 136 if "\n" in mark:
137 137 raise util.Abort(_("bookmark name cannot contain newlines"))
138 138 mark = mark.strip()
139 if not mark:
140 raise util.Abort(_("bookmark names cannot consist entirely of "
141 "whitespace"))
139 142 if mark in marks and not force:
140 143 raise util.Abort(_("a bookmark of the same name already exists"))
141 144 if ((mark in repo.branchtags() or mark == repo.dirstate.branch())
142 145 and not force):
143 146 raise util.Abort(
144 147 _("a bookmark cannot have the name of an existing branch"))
145 148 if rev:
146 149 marks[mark] = repo.lookup(rev)
147 150 else:
148 151 marks[mark] = repo.changectx('.').node()
149 152 setcurrent(repo, mark)
150 153 write(repo)
151 154 return
152 155
153 156 if mark is None:
154 157 if rev:
155 158 raise util.Abort(_("bookmark name required"))
156 159 if len(marks) == 0:
157 160 ui.status(_("no bookmarks set\n"))
158 161 else:
159 162 for bmark, n in marks.iteritems():
160 163 if ui.configbool('bookmarks', 'track.current'):
161 164 current = repo._bookmarkcurrent
162 165 if bmark == current and n == cur:
163 166 prefix, label = '*', 'bookmarks.current'
164 167 else:
165 168 prefix, label = ' ', ''
166 169 else:
167 170 if n == cur:
168 171 prefix, label = '*', 'bookmarks.current'
169 172 else:
170 173 prefix, label = ' ', ''
171 174
172 175 if ui.quiet:
173 176 ui.write("%s\n" % bmark, label=label)
174 177 else:
175 178 ui.write(" %s %-25s %d:%s\n" % (
176 179 prefix, bmark, repo.changelog.rev(n), hexfn(n)),
177 180 label=label)
178 181 return
179 182
180 183 def _revstostrip(changelog, node):
181 184 srev = changelog.rev(node)
182 185 tostrip = [srev]
183 186 saveheads = []
184 187 for r in xrange(srev, len(changelog)):
185 188 parents = changelog.parentrevs(r)
186 189 if parents[0] in tostrip or parents[1] in tostrip:
187 190 tostrip.append(r)
188 191 if parents[1] != nullrev:
189 192 for p in parents:
190 193 if p not in tostrip and p > srev:
191 194 saveheads.append(p)
192 195 return [r for r in tostrip if r not in saveheads]
193 196
194 197 def strip(oldstrip, ui, repo, node, backup="all"):
195 198 """Strip bookmarks if revisions are stripped using
196 199 the mercurial.strip method. This usually happens during
197 200 qpush and qpop"""
198 201 revisions = _revstostrip(repo.changelog, node)
199 202 marks = repo._bookmarks
200 203 update = []
201 204 for mark, n in marks.iteritems():
202 205 if repo.changelog.rev(n) in revisions:
203 206 update.append(mark)
204 207 oldstrip(ui, repo, node, backup)
205 208 if len(update) > 0:
206 209 for m in update:
207 210 marks[m] = repo.changectx('.').node()
208 211 write(repo)
209 212
210 213 def reposetup(ui, repo):
211 214 if not repo.local():
212 215 return
213 216
214 217 class bookmark_repo(repo.__class__):
215 218
216 219 @util.propertycache
217 220 def _bookmarks(self):
218 221 '''Parse .hg/bookmarks file and return a dictionary
219 222
220 223 Bookmarks are stored as {HASH}\\s{NAME}\\n (localtags format) values
221 224 in the .hg/bookmarks file.
222 225 Read the file and return a (name=>nodeid) dictionary
223 226 '''
224 227 try:
225 228 bookmarks = {}
226 229 for line in self.opener('bookmarks'):
227 230 sha, refspec = line.strip().split(' ', 1)
228 231 bookmarks[refspec] = super(bookmark_repo, self).lookup(sha)
229 232 except:
230 233 pass
231 234 return bookmarks
232 235
233 236 @util.propertycache
234 237 def _bookmarkcurrent(self):
235 238 '''Get the current bookmark
236 239
237 240 If we use gittishsh branches we have a current bookmark that
238 241 we are on. This function returns the name of the bookmark. It
239 242 is stored in .hg/bookmarks.current
240 243 '''
241 244 mark = None
242 245 if os.path.exists(self.join('bookmarks.current')):
243 246 file = self.opener('bookmarks.current')
244 247 # No readline() in posixfile_nt, reading everything is cheap
245 248 mark = (file.readlines() or [''])[0]
246 249 if mark == '':
247 250 mark = None
248 251 file.close()
249 252 return mark
250 253
251 254 def rollback(self, *args):
252 255 if os.path.exists(self.join('undo.bookmarks')):
253 256 util.rename(self.join('undo.bookmarks'), self.join('bookmarks'))
254 257 return super(bookmark_repo, self).rollback(*args)
255 258
256 259 def lookup(self, key):
257 260 if key in self._bookmarks:
258 261 key = self._bookmarks[key]
259 262 return super(bookmark_repo, self).lookup(key)
260 263
261 264 def _bookmarksupdate(self, parents, node):
262 265 marks = self._bookmarks
263 266 update = False
264 267 if ui.configbool('bookmarks', 'track.current'):
265 268 mark = self._bookmarkcurrent
266 269 if mark and marks[mark] in parents:
267 270 marks[mark] = node
268 271 update = True
269 272 else:
270 273 for mark, n in marks.items():
271 274 if n in parents:
272 275 marks[mark] = node
273 276 update = True
274 277 if update:
275 278 write(self)
276 279
277 280 def commitctx(self, ctx, error=False):
278 281 """Add a revision to the repository and
279 282 move the bookmark"""
280 283 wlock = self.wlock() # do both commit and bookmark with lock held
281 284 try:
282 285 node = super(bookmark_repo, self).commitctx(ctx, error)
283 286 if node is None:
284 287 return None
285 288 parents = self.changelog.parents(node)
286 289 if parents[1] == nullid:
287 290 parents = (parents[0],)
288 291
289 292 self._bookmarksupdate(parents, node)
290 293 return node
291 294 finally:
292 295 wlock.release()
293 296
294 297 def pull(self, remote, heads=None, force=False):
295 298 result = super(bookmark_repo, self).pull(remote, heads, force)
296 299
297 300 self.ui.debug("checking for updated bookmarks\n")
298 301 rb = remote.listkeys('bookmarks')
299 302 changes = 0
300 303 for k in rb.keys():
301 304 if k in self._bookmarks:
302 305 nr, nl = rb[k], self._bookmarks[k]
303 306 if nr in self:
304 307 cr = self[nr]
305 308 cl = self[nl]
306 309 if cl.rev() >= cr.rev():
307 310 continue
308 311 if cr in cl.descendants():
309 312 self._bookmarks[k] = cr.node()
310 313 changes += 1
311 314 self.ui.status(_("updating bookmark %s\n") % k)
312 315 else:
313 316 self.ui.warn(_("not updating divergent"
314 317 " bookmark %s\n") % k)
315 318 if changes:
316 319 write(repo)
317 320
318 321 return result
319 322
320 323 def push(self, remote, force=False, revs=None, newbranch=False):
321 324 result = super(bookmark_repo, self).push(remote, force, revs,
322 325 newbranch)
323 326
324 327 self.ui.debug("checking for updated bookmarks\n")
325 328 rb = remote.listkeys('bookmarks')
326 329 for k in rb.keys():
327 330 if k in self._bookmarks:
328 331 nr, nl = rb[k], self._bookmarks[k]
329 332 if nr in self:
330 333 cr = self[nr]
331 334 cl = self[nl]
332 335 if cl in cr.descendants():
333 336 r = remote.pushkey('bookmarks', k, nr, nl)
334 337 if r:
335 338 self.ui.status(_("updating bookmark %s\n") % k)
336 339 else:
337 340 self.ui.warn(_('updating bookmark %s'
338 341 ' failed!\n') % k)
339 342
340 343 return result
341 344
342 345 def addchangegroup(self, *args, **kwargs):
343 346 parents = self.dirstate.parents()
344 347
345 348 result = super(bookmark_repo, self).addchangegroup(*args, **kwargs)
346 349 if result > 1:
347 350 # We have more heads than before
348 351 return result
349 352 node = self.changelog.tip()
350 353
351 354 self._bookmarksupdate(parents, node)
352 355 return result
353 356
354 357 def _findtags(self):
355 358 """Merge bookmarks with normal tags"""
356 359 (tags, tagtypes) = super(bookmark_repo, self)._findtags()
357 360 tags.update(self._bookmarks)
358 361 return (tags, tagtypes)
359 362
360 363 if hasattr(repo, 'invalidate'):
361 364 def invalidate(self):
362 365 super(bookmark_repo, self).invalidate()
363 366 for attr in ('_bookmarks', '_bookmarkcurrent'):
364 367 if attr in self.__dict__:
365 368 delattr(self, attr)
366 369
367 370 repo.__class__ = bookmark_repo
368 371
369 372 def listbookmarks(repo):
370 373 d = {}
371 374 for k, v in repo._bookmarks.iteritems():
372 375 d[k] = hex(v)
373 376 return d
374 377
375 378 def pushbookmark(repo, key, old, new):
376 379 w = repo.wlock()
377 380 try:
378 381 marks = repo._bookmarks
379 382 if hex(marks.get(key, '')) != old:
380 383 return False
381 384 if new == '':
382 385 del marks[key]
383 386 else:
384 387 if new not in repo:
385 388 return False
386 389 marks[key] = repo[new].node()
387 390 write(repo)
388 391 return True
389 392 finally:
390 393 w.release()
391 394
392 395 def pull(oldpull, ui, repo, source="default", **opts):
393 396 # translate bookmark args to rev args for actual pull
394 397 if opts.get('bookmark'):
395 398 # this is an unpleasant hack as pull will do this internally
396 399 source, branches = hg.parseurl(ui.expandpath(source),
397 400 opts.get('branch'))
398 401 other = hg.repository(hg.remoteui(repo, opts), source)
399 402 rb = other.listkeys('bookmarks')
400 403
401 404 for b in opts['bookmark']:
402 405 if b not in rb:
403 406 raise util.Abort(_('remote bookmark %s not found!') % b)
404 407 opts.setdefault('rev', []).append(b)
405 408
406 409 result = oldpull(ui, repo, source, **opts)
407 410
408 411 # update specified bookmarks
409 412 if opts.get('bookmark'):
410 413 for b in opts['bookmark']:
411 414 # explicit pull overrides local bookmark if any
412 415 ui.status(_("importing bookmark %s\n") % b)
413 416 repo._bookmarks[b] = repo[rb[b]].node()
414 417 write(repo)
415 418
416 419 return result
417 420
418 421 def push(oldpush, ui, repo, dest=None, **opts):
419 422 dopush = True
420 423 if opts.get('bookmark'):
421 424 dopush = False
422 425 for b in opts['bookmark']:
423 426 if b in repo._bookmarks:
424 427 dopush = True
425 428 opts.setdefault('rev', []).append(b)
426 429
427 430 result = 0
428 431 if dopush:
429 432 result = oldpush(ui, repo, dest, **opts)
430 433
431 434 if opts.get('bookmark'):
432 435 # this is an unpleasant hack as push will do this internally
433 436 dest = ui.expandpath(dest or 'default-push', dest or 'default')
434 437 dest, branches = hg.parseurl(dest, opts.get('branch'))
435 438 other = hg.repository(hg.remoteui(repo, opts), dest)
436 439 rb = other.listkeys('bookmarks')
437 440 for b in opts['bookmark']:
438 441 # explicit push overrides remote bookmark if any
439 442 if b in repo._bookmarks:
440 443 ui.status(_("exporting bookmark %s\n") % b)
441 444 new = repo[b].hex()
442 445 else:
443 446 ui.status(_("deleting remote bookmark %s\n") % b)
444 447 new = '' # delete
445 448 old = rb.get(b, '')
446 449 r = other.pushkey('bookmarks', b, old, new)
447 450 if not r:
448 451 ui.warn(_('updating bookmark %s failed!\n') % b)
449 452 if not result:
450 453 result = 2
451 454
452 455 return result
453 456
454 457 def diffbookmarks(ui, repo, remote):
455 458 ui.status(_("searching for changes\n"))
456 459
457 460 lmarks = repo.listkeys('bookmarks')
458 461 rmarks = remote.listkeys('bookmarks')
459 462
460 463 diff = set(rmarks) - set(lmarks)
461 464 for k in diff:
462 465 ui.write(" %-25s %s\n" % (k, rmarks[k][:12]))
463 466
464 467 if len(diff) <= 0:
465 468 ui.status(_("no changes found\n"))
466 469 return 1
467 470 return 0
468 471
469 472 def incoming(oldincoming, ui, repo, source="default", **opts):
470 473 if opts.get('bookmarks'):
471 474 source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch'))
472 475 other = hg.repository(hg.remoteui(repo, opts), source)
473 476 ui.status(_('comparing with %s\n') % url.hidepassword(source))
474 477 return diffbookmarks(ui, repo, other)
475 478 else:
476 479 return oldincoming(ui, repo, source, **opts)
477 480
478 481 def outgoing(oldoutgoing, ui, repo, dest=None, **opts):
479 482 if opts.get('bookmarks'):
480 483 dest = ui.expandpath(dest or 'default-push', dest or 'default')
481 484 dest, branches = hg.parseurl(dest, opts.get('branch'))
482 485 other = hg.repository(hg.remoteui(repo, opts), dest)
483 486 ui.status(_('comparing with %s\n') % url.hidepassword(dest))
484 487 return diffbookmarks(ui, other, repo)
485 488 else:
486 489 return oldoutgoing(ui, repo, dest, **opts)
487 490
488 491 def uisetup(ui):
489 492 extensions.wrapfunction(repair, "strip", strip)
490 493 if ui.configbool('bookmarks', 'track.current'):
491 494 extensions.wrapcommand(commands.table, 'update', updatecurbookmark)
492 495
493 496 entry = extensions.wrapcommand(commands.table, 'pull', pull)
494 497 entry[1].append(('B', 'bookmark', [],
495 498 _("bookmark to import")))
496 499 entry = extensions.wrapcommand(commands.table, 'push', push)
497 500 entry[1].append(('B', 'bookmark', [],
498 501 _("bookmark to export")))
499 502 entry = extensions.wrapcommand(commands.table, 'incoming', incoming)
500 503 entry[1].append(('B', 'bookmarks', False,
501 504 _("compare bookmark")))
502 505 entry = extensions.wrapcommand(commands.table, 'outgoing', outgoing)
503 506 entry[1].append(('B', 'bookmarks', False,
504 507 _("compare bookmark")))
505 508
506 509 pushkey.register('bookmarks', pushbookmark, listbookmarks)
507 510
508 511 def updatecurbookmark(orig, ui, repo, *args, **opts):
509 512 '''Set the current bookmark
510 513
511 514 If the user updates to a bookmark we update the .hg/bookmarks.current
512 515 file.
513 516 '''
514 517 res = orig(ui, repo, *args, **opts)
515 518 rev = opts['rev']
516 519 if not rev and len(args) > 0:
517 520 rev = args[0]
518 521 setcurrent(repo, rev)
519 522 return res
520 523
521 524 cmdtable = {
522 525 "bookmarks":
523 526 (bookmark,
524 527 [('f', 'force', False, _('force')),
525 528 ('r', 'rev', '', _('revision'), _('REV')),
526 529 ('d', 'delete', False, _('delete a given bookmark')),
527 530 ('m', 'rename', '', _('rename a given bookmark'), _('NAME'))],
528 531 _('hg bookmarks [-f] [-d] [-m NAME] [-r REV] [NAME]')),
529 532 }
530 533
531 534 colortable = {'bookmarks.current': 'green'}
@@ -1,576 +1,575 b''
1 1 # keyword.py - $Keyword$ expansion for Mercurial
2 2 #
3 3 # Copyright 2007-2010 Christian Ebert <blacktrash@gmx.net>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7 #
8 8 # $Id$
9 9 #
10 10 # Keyword expansion hack against the grain of a DSCM
11 11 #
12 12 # There are many good reasons why this is not needed in a distributed
13 13 # SCM, still it may be useful in very small projects based on single
14 14 # files (like LaTeX packages), that are mostly addressed to an
15 15 # audience not running a version control system.
16 16 #
17 17 # For in-depth discussion refer to
18 18 # <http://mercurial.selenic.com/wiki/KeywordPlan>.
19 19 #
20 20 # Keyword expansion is based on Mercurial's changeset template mappings.
21 21 #
22 22 # Binary files are not touched.
23 23 #
24 24 # Files to act upon/ignore are specified in the [keyword] section.
25 25 # Customized keyword template mappings in the [keywordmaps] section.
26 26 #
27 27 # Run "hg help keyword" and "hg kwdemo" to get info on configuration.
28 28
29 29 '''expand keywords in tracked files
30 30
31 31 This extension expands RCS/CVS-like or self-customized $Keywords$ in
32 32 tracked text files selected by your configuration.
33 33
34 34 Keywords are only expanded in local repositories and not stored in the
35 35 change history. The mechanism can be regarded as a convenience for the
36 36 current user or for archive distribution.
37 37
38 38 Configuration is done in the [keyword], [keywordset] and [keywordmaps]
39 39 sections of hgrc files.
40 40
41 41 Example::
42 42
43 43 [keyword]
44 44 # expand keywords in every python file except those matching "x*"
45 45 **.py =
46 46 x* = ignore
47 47
48 48 [keywordset]
49 49 # prefer svn- over cvs-like default keywordmaps
50 50 svn = True
51 51
52 52 NOTE: the more specific you are in your filename patterns the less you
53 53 lose speed in huge repositories.
54 54
55 55 For [keywordmaps] template mapping and expansion demonstration and
56 56 control run :hg:`kwdemo`. See :hg:`help templates` for a list of
57 57 available templates and filters.
58 58
59 59 Three additional date template filters are provided::
60 60
61 61 utcdate "2006/09/18 15:13:13"
62 62 svnutcdate "2006-09-18 15:13:13Z"
63 63 svnisodate "2006-09-18 08:13:13 -700 (Mon, 18 Sep 2006)"
64 64
65 65 The default template mappings (view with :hg:`kwdemo -d`) can be
66 66 replaced with customized keywords and templates. Again, run
67 67 :hg:`kwdemo` to control the results of your config changes.
68 68
69 69 Before changing/disabling active keywords, run :hg:`kwshrink` to avoid
70 70 the risk of inadvertently storing expanded keywords in the change
71 71 history.
72 72
73 73 To force expansion after enabling it, or a configuration change, run
74 74 :hg:`kwexpand`.
75 75
76 76 Expansions spanning more than one line and incremental expansions,
77 77 like CVS' $Log$, are not supported. A keyword template map "Log =
78 78 {desc}" expands to the first line of the changeset description.
79 79 '''
80 80
81 81 from mercurial import commands, cmdutil, dispatch, filelog, revlog, extensions
82 82 from mercurial import patch, localrepo, templater, templatefilters, util, match
83 83 from mercurial.hgweb import webcommands
84 84 from mercurial.i18n import _
85 85 import re, shutil, tempfile
86 86
87 87 commands.optionalrepo += ' kwdemo'
88 88
89 89 # hg commands that do not act on keywords
90 90 nokwcommands = ('add addremove annotate bundle copy export grep incoming init'
91 91 ' log outgoing push rename rollback tip verify'
92 92 ' convert email glog')
93 93
94 94 # hg commands that trigger expansion only when writing to working dir,
95 95 # not when reading filelog, and unexpand when reading from working dir
96 96 restricted = 'merge record qrecord resolve transplant'
97 97
98 98 # commands using dorecord
99 99 recordcommands = 'record qrecord'
100 100 # names of extensions using dorecord
101 101 recordextensions = 'record'
102 102
103 103 # date like in cvs' $Date
104 104 utcdate = lambda x: util.datestr((x[0], 0), '%Y/%m/%d %H:%M:%S')
105 105 # date like in svn's $Date
106 106 svnisodate = lambda x: util.datestr(x, '%Y-%m-%d %H:%M:%S %1%2 (%a, %d %b %Y)')
107 107 # date like in svn's $Id
108 108 svnutcdate = lambda x: util.datestr((x[0], 0), '%Y-%m-%d %H:%M:%SZ')
109 109
110 110 # make keyword tools accessible
111 kwtools = {'templater': None, 'hgcmd': '', 'inc': [], 'exc': ['.hg*']}
111 kwtools = {'templater': None, 'hgcmd': ''}
112 112
113 113
114 114 def _defaultkwmaps(ui):
115 115 '''Returns default keywordmaps according to keywordset configuration.'''
116 116 templates = {
117 117 'Revision': '{node|short}',
118 118 'Author': '{author|user}',
119 119 }
120 120 kwsets = ({
121 121 'Date': '{date|utcdate}',
122 122 'RCSfile': '{file|basename},v',
123 123 'RCSFile': '{file|basename},v', # kept for backwards compatibility
124 124 # with hg-keyword
125 125 'Source': '{root}/{file},v',
126 126 'Id': '{file|basename},v {node|short} {date|utcdate} {author|user}',
127 127 'Header': '{root}/{file},v {node|short} {date|utcdate} {author|user}',
128 128 }, {
129 129 'Date': '{date|svnisodate}',
130 130 'Id': '{file|basename},v {node|short} {date|svnutcdate} {author|user}',
131 131 'LastChangedRevision': '{node|short}',
132 132 'LastChangedBy': '{author|user}',
133 133 'LastChangedDate': '{date|svnisodate}',
134 134 })
135 135 templates.update(kwsets[ui.configbool('keywordset', 'svn')])
136 136 return templates
137 137
138 138 class kwtemplater(object):
139 139 '''
140 140 Sets up keyword templates, corresponding keyword regex, and
141 141 provides keyword substitution functions.
142 142 '''
143 143
144 def __init__(self, ui, repo):
144 def __init__(self, ui, repo, inc, exc):
145 145 self.ui = ui
146 146 self.repo = repo
147 self.match = match.match(repo.root, '', [],
148 kwtools['inc'], kwtools['exc'])
147 self.match = match.match(repo.root, '', [], inc, exc)
149 148 self.restrict = kwtools['hgcmd'] in restricted.split()
150 149 self.record = kwtools['hgcmd'] in recordcommands.split()
151 150
152 151 kwmaps = self.ui.configitems('keywordmaps')
153 152 if kwmaps: # override default templates
154 153 self.templates = dict((k, templater.parsestring(v, False))
155 154 for k, v in kwmaps)
156 155 else:
157 156 self.templates = _defaultkwmaps(self.ui)
158 157 escaped = map(re.escape, self.templates.keys())
159 158 kwpat = r'\$(%s)(: [^$\n\r]*? )??\$' % '|'.join(escaped)
160 159 self.re_kw = re.compile(kwpat)
161 160
162 161 templatefilters.filters['utcdate'] = utcdate
163 162 templatefilters.filters['svnisodate'] = svnisodate
164 163 templatefilters.filters['svnutcdate'] = svnutcdate
165 164
166 165 def substitute(self, data, path, ctx, subfunc):
167 166 '''Replaces keywords in data with expanded template.'''
168 167 def kwsub(mobj):
169 168 kw = mobj.group(1)
170 169 ct = cmdutil.changeset_templater(self.ui, self.repo,
171 170 False, None, '', False)
172 171 ct.use_template(self.templates[kw])
173 172 self.ui.pushbuffer()
174 173 ct.show(ctx, root=self.repo.root, file=path)
175 174 ekw = templatefilters.firstline(self.ui.popbuffer())
176 175 return '$%s: %s $' % (kw, ekw)
177 176 return subfunc(kwsub, data)
178 177
179 178 def expand(self, path, node, data):
180 179 '''Returns data with keywords expanded.'''
181 180 if not self.restrict and self.match(path) and not util.binary(data):
182 181 ctx = self.repo.filectx(path, fileid=node).changectx()
183 182 return self.substitute(data, path, ctx, self.re_kw.sub)
184 183 return data
185 184
186 185 def iskwfile(self, path, flagfunc):
187 186 '''Returns true if path matches [keyword] pattern
188 187 and is not a symbolic link.
189 188 Caveat: localrepository._link fails on Windows.'''
190 189 return self.match(path) and not 'l' in flagfunc(path)
191 190
192 191 def overwrite(self, ctx, candidates, iswctx, expand):
193 192 '''Overwrites selected files expanding/shrinking keywords.'''
194 193 if self.record:
195 194 candidates = [f for f in ctx.files() if f in ctx]
196 195 candidates = [f for f in candidates if self.iskwfile(f, ctx.flags)]
197 196 if candidates:
198 197 self.restrict = True # do not expand when reading
199 198 mf = ctx.manifest()
200 199 msg = (expand and _('overwriting %s expanding keywords\n')
201 200 or _('overwriting %s shrinking keywords\n'))
202 201 for f in candidates:
203 202 if not self.record:
204 203 data = self.repo.file(f).read(mf[f])
205 204 else:
206 205 data = self.repo.wread(f)
207 206 if util.binary(data):
208 207 continue
209 208 if expand:
210 209 if iswctx:
211 210 ctx = self.repo.filectx(f, fileid=mf[f]).changectx()
212 211 data, found = self.substitute(data, f, ctx,
213 212 self.re_kw.subn)
214 213 else:
215 214 found = self.re_kw.search(data)
216 215 if found:
217 216 self.ui.note(msg % f)
218 217 self.repo.wwrite(f, data, mf.flags(f))
219 218 if iswctx:
220 219 self.repo.dirstate.normal(f)
221 220 elif self.record:
222 221 self.repo.dirstate.normallookup(f)
223 222 self.restrict = False
224 223
225 224 def shrinktext(self, text):
226 225 '''Unconditionally removes all keyword substitutions from text.'''
227 226 return self.re_kw.sub(r'$\1$', text)
228 227
229 228 def shrink(self, fname, text):
230 229 '''Returns text with all keyword substitutions removed.'''
231 230 if self.match(fname) and not util.binary(text):
232 231 return self.shrinktext(text)
233 232 return text
234 233
235 234 def shrinklines(self, fname, lines):
236 235 '''Returns lines with keyword substitutions removed.'''
237 236 if self.match(fname):
238 237 text = ''.join(lines)
239 238 if not util.binary(text):
240 239 return self.shrinktext(text).splitlines(True)
241 240 return lines
242 241
243 242 def wread(self, fname, data):
244 243 '''If in restricted mode returns data read from wdir with
245 244 keyword substitutions removed.'''
246 245 return self.restrict and self.shrink(fname, data) or data
247 246
248 247 class kwfilelog(filelog.filelog):
249 248 '''
250 249 Subclass of filelog to hook into its read, add, cmp methods.
251 250 Keywords are "stored" unexpanded, and processed on reading.
252 251 '''
253 252 def __init__(self, opener, kwt, path):
254 253 super(kwfilelog, self).__init__(opener, path)
255 254 self.kwt = kwt
256 255 self.path = path
257 256
258 257 def read(self, node):
259 258 '''Expands keywords when reading filelog.'''
260 259 data = super(kwfilelog, self).read(node)
261 260 return self.kwt.expand(self.path, node, data)
262 261
263 262 def add(self, text, meta, tr, link, p1=None, p2=None):
264 263 '''Removes keyword substitutions when adding to filelog.'''
265 264 text = self.kwt.shrink(self.path, text)
266 265 return super(kwfilelog, self).add(text, meta, tr, link, p1, p2)
267 266
268 267 def cmp(self, node, text):
269 268 '''Removes keyword substitutions for comparison.'''
270 269 text = self.kwt.shrink(self.path, text)
271 270 if self.renamed(node):
272 271 t2 = super(kwfilelog, self).read(node)
273 272 return t2 != text
274 273 return revlog.revlog.cmp(self, node, text)
275 274
276 275 def _status(ui, repo, kwt, *pats, **opts):
277 276 '''Bails out if [keyword] configuration is not active.
278 277 Returns status of working directory.'''
279 278 if kwt:
280 279 return repo.status(match=cmdutil.match(repo, pats, opts), clean=True,
281 280 unknown=opts.get('unknown') or opts.get('all'))
282 281 if ui.configitems('keyword'):
283 282 raise util.Abort(_('[keyword] patterns cannot match'))
284 283 raise util.Abort(_('no [keyword] patterns configured'))
285 284
286 285 def _kwfwrite(ui, repo, expand, *pats, **opts):
287 286 '''Selects files and passes them to kwtemplater.overwrite.'''
288 287 wctx = repo[None]
289 288 if len(wctx.parents()) > 1:
290 289 raise util.Abort(_('outstanding uncommitted merge'))
291 290 kwt = kwtools['templater']
292 291 wlock = repo.wlock()
293 292 try:
294 293 status = _status(ui, repo, kwt, *pats, **opts)
295 294 modified, added, removed, deleted, unknown, ignored, clean = status
296 295 if modified or added or removed or deleted:
297 296 raise util.Abort(_('outstanding uncommitted changes'))
298 297 kwt.overwrite(wctx, clean, True, expand)
299 298 finally:
300 299 wlock.release()
301 300
302 301 def demo(ui, repo, *args, **opts):
303 302 '''print [keywordmaps] configuration and an expansion example
304 303
305 304 Show current, custom, or default keyword template maps and their
306 305 expansions.
307 306
308 307 Extend the current configuration by specifying maps as arguments
309 308 and using -f/--rcfile to source an external hgrc file.
310 309
311 310 Use -d/--default to disable current configuration.
312 311
313 312 See :hg:`help templates` for information on templates and filters.
314 313 '''
315 314 def demoitems(section, items):
316 315 ui.write('[%s]\n' % section)
317 316 for k, v in sorted(items):
318 317 ui.write('%s = %s\n' % (k, v))
319 318
320 319 fn = 'demo.txt'
321 320 tmpdir = tempfile.mkdtemp('', 'kwdemo.')
322 321 ui.note(_('creating temporary repository at %s\n') % tmpdir)
323 322 repo = localrepo.localrepository(ui, tmpdir, True)
324 323 ui.setconfig('keyword', fn, '')
325 324
326 325 uikwmaps = ui.configitems('keywordmaps')
327 326 if args or opts.get('rcfile'):
328 327 ui.status(_('\n\tconfiguration using custom keyword template maps\n'))
329 328 if uikwmaps:
330 329 ui.status(_('\textending current template maps\n'))
331 330 if opts.get('default') or not uikwmaps:
332 331 ui.status(_('\toverriding default template maps\n'))
333 332 if opts.get('rcfile'):
334 333 ui.readconfig(opts.get('rcfile'))
335 334 if args:
336 335 # simulate hgrc parsing
337 336 rcmaps = ['[keywordmaps]\n'] + [a + '\n' for a in args]
338 337 fp = repo.opener('hgrc', 'w')
339 338 fp.writelines(rcmaps)
340 339 fp.close()
341 340 ui.readconfig(repo.join('hgrc'))
342 341 kwmaps = dict(ui.configitems('keywordmaps'))
343 342 elif opts.get('default'):
344 343 ui.status(_('\n\tconfiguration using default keyword template maps\n'))
345 344 kwmaps = _defaultkwmaps(ui)
346 345 if uikwmaps:
347 346 ui.status(_('\tdisabling current template maps\n'))
348 347 for k, v in kwmaps.iteritems():
349 348 ui.setconfig('keywordmaps', k, v)
350 349 else:
351 350 ui.status(_('\n\tconfiguration using current keyword template maps\n'))
352 351 kwmaps = dict(uikwmaps) or _defaultkwmaps(ui)
353 352
354 353 uisetup(ui)
355 354 reposetup(ui, repo)
356 355 ui.write('[extensions]\nkeyword =\n')
357 356 demoitems('keyword', ui.configitems('keyword'))
358 357 demoitems('keywordmaps', kwmaps.iteritems())
359 358 keywords = '$' + '$\n$'.join(sorted(kwmaps.keys())) + '$\n'
360 359 repo.wopener(fn, 'w').write(keywords)
361 360 repo[None].add([fn])
362 361 ui.note(_('\nkeywords written to %s:\n') % fn)
363 362 ui.note(keywords)
364 363 repo.dirstate.setbranch('demobranch')
365 364 for name, cmd in ui.configitems('hooks'):
366 365 if name.split('.', 1)[0].find('commit') > -1:
367 366 repo.ui.setconfig('hooks', name, '')
368 367 msg = _('hg keyword configuration and expansion example')
369 368 ui.note("hg ci -m '%s'\n" % msg)
370 369 repo.commit(text=msg)
371 370 ui.status(_('\n\tkeywords expanded\n'))
372 371 ui.write(repo.wread(fn))
373 372 shutil.rmtree(tmpdir, ignore_errors=True)
374 373
375 374 def expand(ui, repo, *pats, **opts):
376 375 '''expand keywords in the working directory
377 376
378 377 Run after (re)enabling keyword expansion.
379 378
380 379 kwexpand refuses to run if given files contain local changes.
381 380 '''
382 381 # 3rd argument sets expansion to True
383 382 _kwfwrite(ui, repo, True, *pats, **opts)
384 383
385 384 def files(ui, repo, *pats, **opts):
386 385 '''show files configured for keyword expansion
387 386
388 387 List which files in the working directory are matched by the
389 388 [keyword] configuration patterns.
390 389
391 390 Useful to prevent inadvertent keyword expansion and to speed up
392 391 execution by including only files that are actual candidates for
393 392 expansion.
394 393
395 394 See :hg:`help keyword` on how to construct patterns both for
396 395 inclusion and exclusion of files.
397 396
398 397 With -A/--all and -v/--verbose the codes used to show the status
399 398 of files are::
400 399
401 400 K = keyword expansion candidate
402 401 k = keyword expansion candidate (not tracked)
403 402 I = ignored
404 403 i = ignored (not tracked)
405 404 '''
406 405 kwt = kwtools['templater']
407 406 status = _status(ui, repo, kwt, *pats, **opts)
408 407 cwd = pats and repo.getcwd() or ''
409 408 modified, added, removed, deleted, unknown, ignored, clean = status
410 409 files = []
411 410 if not opts.get('unknown') or opts.get('all'):
412 411 files = sorted(modified + added + clean)
413 412 wctx = repo[None]
414 413 kwfiles = [f for f in files if kwt.iskwfile(f, wctx.flags)]
415 414 kwunknown = [f for f in unknown if kwt.iskwfile(f, wctx.flags)]
416 415 if not opts.get('ignore') or opts.get('all'):
417 416 showfiles = kwfiles, kwunknown
418 417 else:
419 418 showfiles = [], []
420 419 if opts.get('all') or opts.get('ignore'):
421 420 showfiles += ([f for f in files if f not in kwfiles],
422 421 [f for f in unknown if f not in kwunknown])
423 422 for char, filenames in zip('KkIi', showfiles):
424 423 fmt = (opts.get('all') or ui.verbose) and '%s %%s\n' % char or '%s\n'
425 424 for f in filenames:
426 425 ui.write(fmt % repo.pathto(f, cwd))
427 426
428 427 def shrink(ui, repo, *pats, **opts):
429 428 '''revert expanded keywords in the working directory
430 429
431 430 Run before changing/disabling active keywords or if you experience
432 431 problems with :hg:`import` or :hg:`merge`.
433 432
434 433 kwshrink refuses to run if given files contain local changes.
435 434 '''
436 435 # 3rd argument sets expansion to False
437 436 _kwfwrite(ui, repo, False, *pats, **opts)
438 437
439 438
440 439 def uisetup(ui):
441 '''Collects [keyword] config in kwtools.
442 Monkeypatches dispatch._parse if needed.'''
443
444 for pat, opt in ui.configitems('keyword'):
445 if opt != 'ignore':
446 kwtools['inc'].append(pat)
447 else:
448 kwtools['exc'].append(pat)
440 ''' Monkeypatches dispatch._parse to retrieve user command.'''
449 441
450 if kwtools['inc']:
451 def kwdispatch_parse(orig, ui, args):
452 '''Monkeypatch dispatch._parse to obtain running hg command.'''
453 cmd, func, args, options, cmdoptions = orig(ui, args)
454 kwtools['hgcmd'] = cmd
455 return cmd, func, args, options, cmdoptions
442 def kwdispatch_parse(orig, ui, args):
443 '''Monkeypatch dispatch._parse to obtain running hg command.'''
444 cmd, func, args, options, cmdoptions = orig(ui, args)
445 kwtools['hgcmd'] = cmd
446 return cmd, func, args, options, cmdoptions
456 447
457 extensions.wrapfunction(dispatch, '_parse', kwdispatch_parse)
448 extensions.wrapfunction(dispatch, '_parse', kwdispatch_parse)
458 449
459 450 def reposetup(ui, repo):
460 451 '''Sets up repo as kwrepo for keyword substitution.
461 452 Overrides file method to return kwfilelog instead of filelog
462 453 if file matches user configuration.
463 454 Wraps commit to overwrite configured files with updated
464 455 keyword substitutions.
465 456 Monkeypatches patch and webcommands.'''
466 457
467 458 try:
468 if (not repo.local() or not kwtools['inc']
469 or kwtools['hgcmd'] in nokwcommands.split()
459 if (not repo.local() or kwtools['hgcmd'] in nokwcommands.split()
470 460 or '.hg' in util.splitpath(repo.root)
471 461 or repo._url.startswith('bundle:')):
472 462 return
473 463 except AttributeError:
474 464 pass
475 465
476 kwtools['templater'] = kwt = kwtemplater(ui, repo)
466 inc, exc = [], ['.hg*']
467 for pat, opt in ui.configitems('keyword'):
468 if opt != 'ignore':
469 inc.append(pat)
470 else:
471 exc.append(pat)
472 if not inc:
473 return
474
475 kwtools['templater'] = kwt = kwtemplater(ui, repo, inc, exc)
477 476
478 477 class kwrepo(repo.__class__):
479 478 def file(self, f):
480 479 if f[0] == '/':
481 480 f = f[1:]
482 481 return kwfilelog(self.sopener, kwt, f)
483 482
484 483 def wread(self, filename):
485 484 data = super(kwrepo, self).wread(filename)
486 485 return kwt.wread(filename, data)
487 486
488 487 def commit(self, *args, **opts):
489 488 # use custom commitctx for user commands
490 489 # other extensions can still wrap repo.commitctx directly
491 490 self.commitctx = self.kwcommitctx
492 491 try:
493 492 return super(kwrepo, self).commit(*args, **opts)
494 493 finally:
495 494 del self.commitctx
496 495
497 496 def kwcommitctx(self, ctx, error=False):
498 497 n = super(kwrepo, self).commitctx(ctx, error)
499 498 # no lock needed, only called from repo.commit() which already locks
500 499 if not kwt.record:
501 500 kwt.overwrite(self[n], sorted(ctx.added() + ctx.modified()),
502 501 False, True)
503 502 return n
504 503
505 504 # monkeypatches
506 505 def kwpatchfile_init(orig, self, ui, fname, opener,
507 506 missing=False, eolmode=None):
508 507 '''Monkeypatch/wrap patch.patchfile.__init__ to avoid
509 508 rejects or conflicts due to expanded keywords in working dir.'''
510 509 orig(self, ui, fname, opener, missing, eolmode)
511 510 # shrink keywords read from working dir
512 511 self.lines = kwt.shrinklines(self.fname, self.lines)
513 512
514 513 def kw_diff(orig, repo, node1=None, node2=None, match=None, changes=None,
515 514 opts=None):
516 515 '''Monkeypatch patch.diff to avoid expansion except when
517 516 comparing against working dir.'''
518 517 if node2 is not None:
519 518 kwt.match = util.never
520 519 elif node1 is not None and node1 != repo['.'].node():
521 520 kwt.restrict = True
522 521 return orig(repo, node1, node2, match, changes, opts)
523 522
524 523 def kwweb_skip(orig, web, req, tmpl):
525 524 '''Wraps webcommands.x turning off keyword expansion.'''
526 525 kwt.match = util.never
527 526 return orig(web, req, tmpl)
528 527
529 528 def kw_dorecord(orig, ui, repo, commitfunc, *pats, **opts):
530 529 '''Wraps record.dorecord expanding keywords after recording.'''
531 530 wlock = repo.wlock()
532 531 try:
533 532 # record returns 0 even when nothing has changed
534 533 # therefore compare nodes before and after
535 534 ctx = repo['.']
536 535 ret = orig(ui, repo, commitfunc, *pats, **opts)
537 536 recordctx = repo['.']
538 537 if ctx != recordctx:
539 538 kwt.overwrite(recordctx, None, False, True)
540 539 return ret
541 540 finally:
542 541 wlock.release()
543 542
544 543 repo.__class__ = kwrepo
545 544
546 545 extensions.wrapfunction(patch.patchfile, '__init__', kwpatchfile_init)
547 546 if not kwt.restrict:
548 547 extensions.wrapfunction(patch, 'diff', kw_diff)
549 548 for c in 'annotate changeset rev filediff diff'.split():
550 549 extensions.wrapfunction(webcommands, c, kwweb_skip)
551 550 for name in recordextensions.split():
552 551 try:
553 552 record = extensions.find(name)
554 553 extensions.wrapfunction(record, 'dorecord', kw_dorecord)
555 554 except KeyError:
556 555 pass
557 556
558 557 cmdtable = {
559 558 'kwdemo':
560 559 (demo,
561 560 [('d', 'default', None, _('show default keyword template maps')),
562 561 ('f', 'rcfile', '',
563 562 _('read maps from rcfile'), _('FILE'))],
564 563 _('hg kwdemo [-d] [-f RCFILE] [TEMPLATEMAP]...')),
565 564 'kwexpand': (expand, commands.walkopts,
566 565 _('hg kwexpand [OPTION]... [FILE]...')),
567 566 'kwfiles':
568 567 (files,
569 568 [('A', 'all', None, _('show keyword status flags of all files')),
570 569 ('i', 'ignore', None, _('show files excluded from expansion')),
571 570 ('u', 'unknown', None, _('only show unknown (not tracked) files')),
572 571 ] + commands.walkopts,
573 572 _('hg kwfiles [OPTION]... [FILE]...')),
574 573 'kwshrink': (shrink, commands.walkopts,
575 574 _('hg kwshrink [OPTION]... [FILE]...')),
576 575 }
@@ -1,3018 +1,3022 b''
1 1 # mq.py - patch queues for mercurial
2 2 #
3 3 # Copyright 2005, 2006 Chris Mason <mason@suse.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 '''manage a stack of patches
9 9
10 10 This extension lets you work with a stack of patches in a Mercurial
11 11 repository. It manages two stacks of patches - all known patches, and
12 12 applied patches (subset of known patches).
13 13
14 14 Known patches are represented as patch files in the .hg/patches
15 15 directory. Applied patches are both patch files and changesets.
16 16
17 17 Common tasks (use :hg:`help command` for more details)::
18 18
19 19 create new patch qnew
20 20 import existing patch qimport
21 21
22 22 print patch series qseries
23 23 print applied patches qapplied
24 24
25 25 add known patch to applied stack qpush
26 26 remove patch from applied stack qpop
27 27 refresh contents of top applied patch qrefresh
28 28
29 29 By default, mq will automatically use git patches when required to
30 30 avoid losing file mode changes, copy records, binary files or empty
31 31 files creations or deletions. This behaviour can be configured with::
32 32
33 33 [mq]
34 34 git = auto/keep/yes/no
35 35
36 36 If set to 'keep', mq will obey the [diff] section configuration while
37 37 preserving existing git patches upon qrefresh. If set to 'yes' or
38 38 'no', mq will override the [diff] section and always generate git or
39 39 regular patches, possibly losing data in the second case.
40 40
41 41 You will by default be managing a patch queue named "patches". You can
42 42 create other, independent patch queues with the :hg:`qqueue` command.
43 43 '''
44 44
45 45 from mercurial.i18n import _
46 46 from mercurial.node import bin, hex, short, nullid, nullrev
47 47 from mercurial.lock import release
48 48 from mercurial import commands, cmdutil, hg, patch, util
49 49 from mercurial import repair, extensions, url, error
50 50 import os, sys, re, errno
51 51
52 52 commands.norepo += " qclone"
53 53
54 54 # Patch names looks like unix-file names.
55 55 # They must be joinable with queue directory and result in the patch path.
56 56 normname = util.normpath
57 57
58 58 class statusentry(object):
59 59 def __init__(self, node, name):
60 60 self.node, self.name = node, name
61 61 def __repr__(self):
62 62 return hex(self.node) + ':' + self.name
63 63
64 64 class patchheader(object):
65 65 def __init__(self, pf, plainmode=False):
66 66 def eatdiff(lines):
67 67 while lines:
68 68 l = lines[-1]
69 69 if (l.startswith("diff -") or
70 70 l.startswith("Index:") or
71 71 l.startswith("===========")):
72 72 del lines[-1]
73 73 else:
74 74 break
75 75 def eatempty(lines):
76 76 while lines:
77 77 if not lines[-1].strip():
78 78 del lines[-1]
79 79 else:
80 80 break
81 81
82 82 message = []
83 83 comments = []
84 84 user = None
85 85 date = None
86 86 parent = None
87 87 format = None
88 88 subject = None
89 89 diffstart = 0
90 90
91 91 for line in file(pf):
92 92 line = line.rstrip()
93 93 if (line.startswith('diff --git')
94 94 or (diffstart and line.startswith('+++ '))):
95 95 diffstart = 2
96 96 break
97 97 diffstart = 0 # reset
98 98 if line.startswith("--- "):
99 99 diffstart = 1
100 100 continue
101 101 elif format == "hgpatch":
102 102 # parse values when importing the result of an hg export
103 103 if line.startswith("# User "):
104 104 user = line[7:]
105 105 elif line.startswith("# Date "):
106 106 date = line[7:]
107 107 elif line.startswith("# Parent "):
108 108 parent = line[9:]
109 109 elif not line.startswith("# ") and line:
110 110 message.append(line)
111 111 format = None
112 112 elif line == '# HG changeset patch':
113 113 message = []
114 114 format = "hgpatch"
115 115 elif (format != "tagdone" and (line.startswith("Subject: ") or
116 116 line.startswith("subject: "))):
117 117 subject = line[9:]
118 118 format = "tag"
119 119 elif (format != "tagdone" and (line.startswith("From: ") or
120 120 line.startswith("from: "))):
121 121 user = line[6:]
122 122 format = "tag"
123 123 elif (format != "tagdone" and (line.startswith("Date: ") or
124 124 line.startswith("date: "))):
125 125 date = line[6:]
126 126 format = "tag"
127 127 elif format == "tag" and line == "":
128 128 # when looking for tags (subject: from: etc) they
129 129 # end once you find a blank line in the source
130 130 format = "tagdone"
131 131 elif message or line:
132 132 message.append(line)
133 133 comments.append(line)
134 134
135 135 eatdiff(message)
136 136 eatdiff(comments)
137 137 eatempty(message)
138 138 eatempty(comments)
139 139
140 140 # make sure message isn't empty
141 141 if format and format.startswith("tag") and subject:
142 142 message.insert(0, "")
143 143 message.insert(0, subject)
144 144
145 145 self.message = message
146 146 self.comments = comments
147 147 self.user = user
148 148 self.date = date
149 149 self.parent = parent
150 150 self.haspatch = diffstart > 1
151 151 self.plainmode = plainmode
152 152
153 153 def setuser(self, user):
154 154 if not self.updateheader(['From: ', '# User '], user):
155 155 try:
156 156 patchheaderat = self.comments.index('# HG changeset patch')
157 157 self.comments.insert(patchheaderat + 1, '# User ' + user)
158 158 except ValueError:
159 159 if self.plainmode or self._hasheader(['Date: ']):
160 160 self.comments = ['From: ' + user] + self.comments
161 161 else:
162 162 tmp = ['# HG changeset patch', '# User ' + user, '']
163 163 self.comments = tmp + self.comments
164 164 self.user = user
165 165
166 166 def setdate(self, date):
167 167 if not self.updateheader(['Date: ', '# Date '], date):
168 168 try:
169 169 patchheaderat = self.comments.index('# HG changeset patch')
170 170 self.comments.insert(patchheaderat + 1, '# Date ' + date)
171 171 except ValueError:
172 172 if self.plainmode or self._hasheader(['From: ']):
173 173 self.comments = ['Date: ' + date] + self.comments
174 174 else:
175 175 tmp = ['# HG changeset patch', '# Date ' + date, '']
176 176 self.comments = tmp + self.comments
177 177 self.date = date
178 178
179 179 def setparent(self, parent):
180 180 if not self.updateheader(['# Parent '], parent):
181 181 try:
182 182 patchheaderat = self.comments.index('# HG changeset patch')
183 183 self.comments.insert(patchheaderat + 1, '# Parent ' + parent)
184 184 except ValueError:
185 185 pass
186 186 self.parent = parent
187 187
188 188 def setmessage(self, message):
189 189 if self.comments:
190 190 self._delmsg()
191 191 self.message = [message]
192 192 self.comments += self.message
193 193
194 194 def updateheader(self, prefixes, new):
195 195 '''Update all references to a field in the patch header.
196 196 Return whether the field is present.'''
197 197 res = False
198 198 for prefix in prefixes:
199 199 for i in xrange(len(self.comments)):
200 200 if self.comments[i].startswith(prefix):
201 201 self.comments[i] = prefix + new
202 202 res = True
203 203 break
204 204 return res
205 205
206 206 def _hasheader(self, prefixes):
207 207 '''Check if a header starts with any of the given prefixes.'''
208 208 for prefix in prefixes:
209 209 for comment in self.comments:
210 210 if comment.startswith(prefix):
211 211 return True
212 212 return False
213 213
214 214 def __str__(self):
215 215 if not self.comments:
216 216 return ''
217 217 return '\n'.join(self.comments) + '\n\n'
218 218
219 219 def _delmsg(self):
220 220 '''Remove existing message, keeping the rest of the comments fields.
221 221 If comments contains 'subject: ', message will prepend
222 222 the field and a blank line.'''
223 223 if self.message:
224 224 subj = 'subject: ' + self.message[0].lower()
225 225 for i in xrange(len(self.comments)):
226 226 if subj == self.comments[i].lower():
227 227 del self.comments[i]
228 228 self.message = self.message[2:]
229 229 break
230 230 ci = 0
231 231 for mi in self.message:
232 232 while mi != self.comments[ci]:
233 233 ci += 1
234 234 del self.comments[ci]
235 235
236 236 class queue(object):
237 237 def __init__(self, ui, path, patchdir=None):
238 238 self.basepath = path
239 239 try:
240 240 fh = open(os.path.join(path, 'patches.queue'))
241 241 cur = fh.read().rstrip()
242 242 if not cur:
243 243 curpath = os.path.join(path, 'patches')
244 244 else:
245 245 curpath = os.path.join(path, 'patches-' + cur)
246 246 except IOError:
247 247 curpath = os.path.join(path, 'patches')
248 248 self.path = patchdir or curpath
249 249 self.opener = util.opener(self.path)
250 250 self.ui = ui
251 251 self.applied_dirty = 0
252 252 self.series_dirty = 0
253 253 self.added = []
254 254 self.series_path = "series"
255 255 self.status_path = "status"
256 256 self.guards_path = "guards"
257 257 self.active_guards = None
258 258 self.guards_dirty = False
259 259 # Handle mq.git as a bool with extended values
260 260 try:
261 261 gitmode = ui.configbool('mq', 'git', None)
262 262 if gitmode is None:
263 263 raise error.ConfigError()
264 264 self.gitmode = gitmode and 'yes' or 'no'
265 265 except error.ConfigError:
266 266 self.gitmode = ui.config('mq', 'git', 'auto').lower()
267 267 self.plainmode = ui.configbool('mq', 'plain', False)
268 268
269 269 @util.propertycache
270 270 def applied(self):
271 271 if os.path.exists(self.join(self.status_path)):
272 272 def parse(l):
273 273 n, name = l.split(':', 1)
274 274 return statusentry(bin(n), name)
275 275 lines = self.opener(self.status_path).read().splitlines()
276 276 return [parse(l) for l in lines]
277 277 return []
278 278
279 279 @util.propertycache
280 280 def full_series(self):
281 281 if os.path.exists(self.join(self.series_path)):
282 282 return self.opener(self.series_path).read().splitlines()
283 283 return []
284 284
285 285 @util.propertycache
286 286 def series(self):
287 287 self.parse_series()
288 288 return self.series
289 289
290 290 @util.propertycache
291 291 def series_guards(self):
292 292 self.parse_series()
293 293 return self.series_guards
294 294
295 295 def invalidate(self):
296 296 for a in 'applied full_series series series_guards'.split():
297 297 if a in self.__dict__:
298 298 delattr(self, a)
299 299 self.applied_dirty = 0
300 300 self.series_dirty = 0
301 301 self.guards_dirty = False
302 302 self.active_guards = None
303 303
304 304 def diffopts(self, opts={}, patchfn=None):
305 305 diffopts = patch.diffopts(self.ui, opts)
306 306 if self.gitmode == 'auto':
307 307 diffopts.upgrade = True
308 308 elif self.gitmode == 'keep':
309 309 pass
310 310 elif self.gitmode in ('yes', 'no'):
311 311 diffopts.git = self.gitmode == 'yes'
312 312 else:
313 313 raise util.Abort(_('mq.git option can be auto/keep/yes/no'
314 314 ' got %s') % self.gitmode)
315 315 if patchfn:
316 316 diffopts = self.patchopts(diffopts, patchfn)
317 317 return diffopts
318 318
319 319 def patchopts(self, diffopts, *patches):
320 320 """Return a copy of input diff options with git set to true if
321 321 referenced patch is a git patch and should be preserved as such.
322 322 """
323 323 diffopts = diffopts.copy()
324 324 if not diffopts.git and self.gitmode == 'keep':
325 325 for patchfn in patches:
326 326 patchf = self.opener(patchfn, 'r')
327 327 # if the patch was a git patch, refresh it as a git patch
328 328 for line in patchf:
329 329 if line.startswith('diff --git'):
330 330 diffopts.git = True
331 331 break
332 332 patchf.close()
333 333 return diffopts
334 334
335 335 def join(self, *p):
336 336 return os.path.join(self.path, *p)
337 337
338 338 def find_series(self, patch):
339 339 def matchpatch(l):
340 340 l = l.split('#', 1)[0]
341 341 return l.strip() == patch
342 342 for index, l in enumerate(self.full_series):
343 343 if matchpatch(l):
344 344 return index
345 345 return None
346 346
347 347 guard_re = re.compile(r'\s?#([-+][^-+# \t\r\n\f][^# \t\r\n\f]*)')
348 348
349 349 def parse_series(self):
350 350 self.series = []
351 351 self.series_guards = []
352 352 for l in self.full_series:
353 353 h = l.find('#')
354 354 if h == -1:
355 355 patch = l
356 356 comment = ''
357 357 elif h == 0:
358 358 continue
359 359 else:
360 360 patch = l[:h]
361 361 comment = l[h:]
362 362 patch = patch.strip()
363 363 if patch:
364 364 if patch in self.series:
365 365 raise util.Abort(_('%s appears more than once in %s') %
366 366 (patch, self.join(self.series_path)))
367 367 self.series.append(patch)
368 368 self.series_guards.append(self.guard_re.findall(comment))
369 369
370 370 def check_guard(self, guard):
371 371 if not guard:
372 372 return _('guard cannot be an empty string')
373 373 bad_chars = '# \t\r\n\f'
374 374 first = guard[0]
375 375 if first in '-+':
376 376 return (_('guard %r starts with invalid character: %r') %
377 377 (guard, first))
378 378 for c in bad_chars:
379 379 if c in guard:
380 380 return _('invalid character in guard %r: %r') % (guard, c)
381 381
382 382 def set_active(self, guards):
383 383 for guard in guards:
384 384 bad = self.check_guard(guard)
385 385 if bad:
386 386 raise util.Abort(bad)
387 387 guards = sorted(set(guards))
388 388 self.ui.debug('active guards: %s\n' % ' '.join(guards))
389 389 self.active_guards = guards
390 390 self.guards_dirty = True
391 391
392 392 def active(self):
393 393 if self.active_guards is None:
394 394 self.active_guards = []
395 395 try:
396 396 guards = self.opener(self.guards_path).read().split()
397 397 except IOError, err:
398 398 if err.errno != errno.ENOENT:
399 399 raise
400 400 guards = []
401 401 for i, guard in enumerate(guards):
402 402 bad = self.check_guard(guard)
403 403 if bad:
404 404 self.ui.warn('%s:%d: %s\n' %
405 405 (self.join(self.guards_path), i + 1, bad))
406 406 else:
407 407 self.active_guards.append(guard)
408 408 return self.active_guards
409 409
410 410 def set_guards(self, idx, guards):
411 411 for g in guards:
412 412 if len(g) < 2:
413 413 raise util.Abort(_('guard %r too short') % g)
414 414 if g[0] not in '-+':
415 415 raise util.Abort(_('guard %r starts with invalid char') % g)
416 416 bad = self.check_guard(g[1:])
417 417 if bad:
418 418 raise util.Abort(bad)
419 419 drop = self.guard_re.sub('', self.full_series[idx])
420 420 self.full_series[idx] = drop + ''.join([' #' + g for g in guards])
421 421 self.parse_series()
422 422 self.series_dirty = True
423 423
424 424 def pushable(self, idx):
425 425 if isinstance(idx, str):
426 426 idx = self.series.index(idx)
427 427 patchguards = self.series_guards[idx]
428 428 if not patchguards:
429 429 return True, None
430 430 guards = self.active()
431 431 exactneg = [g for g in patchguards if g[0] == '-' and g[1:] in guards]
432 432 if exactneg:
433 433 return False, exactneg[0]
434 434 pos = [g for g in patchguards if g[0] == '+']
435 435 exactpos = [g for g in pos if g[1:] in guards]
436 436 if pos:
437 437 if exactpos:
438 438 return True, exactpos[0]
439 439 return False, pos
440 440 return True, ''
441 441
442 442 def explain_pushable(self, idx, all_patches=False):
443 443 write = all_patches and self.ui.write or self.ui.warn
444 444 if all_patches or self.ui.verbose:
445 445 if isinstance(idx, str):
446 446 idx = self.series.index(idx)
447 447 pushable, why = self.pushable(idx)
448 448 if all_patches and pushable:
449 449 if why is None:
450 450 write(_('allowing %s - no guards in effect\n') %
451 451 self.series[idx])
452 452 else:
453 453 if not why:
454 454 write(_('allowing %s - no matching negative guards\n') %
455 455 self.series[idx])
456 456 else:
457 457 write(_('allowing %s - guarded by %r\n') %
458 458 (self.series[idx], why))
459 459 if not pushable:
460 460 if why:
461 461 write(_('skipping %s - guarded by %r\n') %
462 462 (self.series[idx], why))
463 463 else:
464 464 write(_('skipping %s - no matching guards\n') %
465 465 self.series[idx])
466 466
467 467 def save_dirty(self):
468 468 def write_list(items, path):
469 469 fp = self.opener(path, 'w')
470 470 for i in items:
471 471 fp.write("%s\n" % i)
472 472 fp.close()
473 473 if self.applied_dirty:
474 474 write_list(map(str, self.applied), self.status_path)
475 475 if self.series_dirty:
476 476 write_list(self.full_series, self.series_path)
477 477 if self.guards_dirty:
478 478 write_list(self.active_guards, self.guards_path)
479 479 if self.added:
480 480 qrepo = self.qrepo()
481 481 if qrepo:
482 482 qrepo[None].add(self.added)
483 483 self.added = []
484 484
485 485 def removeundo(self, repo):
486 486 undo = repo.sjoin('undo')
487 487 if not os.path.exists(undo):
488 488 return
489 489 try:
490 490 os.unlink(undo)
491 491 except OSError, inst:
492 492 self.ui.warn(_('error removing undo: %s\n') % str(inst))
493 493
494 494 def printdiff(self, repo, diffopts, node1, node2=None, files=None,
495 495 fp=None, changes=None, opts={}):
496 496 stat = opts.get('stat')
497 497 m = cmdutil.match(repo, files, opts)
498 498 cmdutil.diffordiffstat(self.ui, repo, diffopts, node1, node2, m,
499 499 changes, stat, fp)
500 500
501 501 def mergeone(self, repo, mergeq, head, patch, rev, diffopts):
502 502 # first try just applying the patch
503 503 (err, n) = self.apply(repo, [patch], update_status=False,
504 504 strict=True, merge=rev)
505 505
506 506 if err == 0:
507 507 return (err, n)
508 508
509 509 if n is None:
510 510 raise util.Abort(_("apply failed for patch %s") % patch)
511 511
512 512 self.ui.warn(_("patch didn't work out, merging %s\n") % patch)
513 513
514 514 # apply failed, strip away that rev and merge.
515 515 hg.clean(repo, head)
516 516 self.strip(repo, n, update=False, backup='strip')
517 517
518 518 ctx = repo[rev]
519 519 ret = hg.merge(repo, rev)
520 520 if ret:
521 521 raise util.Abort(_("update returned %d") % ret)
522 522 n = repo.commit(ctx.description(), ctx.user(), force=True)
523 523 if n is None:
524 524 raise util.Abort(_("repo commit failed"))
525 525 try:
526 526 ph = patchheader(mergeq.join(patch), self.plainmode)
527 527 except:
528 528 raise util.Abort(_("unable to read %s") % patch)
529 529
530 530 diffopts = self.patchopts(diffopts, patch)
531 531 patchf = self.opener(patch, "w")
532 532 comments = str(ph)
533 533 if comments:
534 534 patchf.write(comments)
535 535 self.printdiff(repo, diffopts, head, n, fp=patchf)
536 536 patchf.close()
537 537 self.removeundo(repo)
538 538 return (0, n)
539 539
540 540 def qparents(self, repo, rev=None):
541 541 if rev is None:
542 542 (p1, p2) = repo.dirstate.parents()
543 543 if p2 == nullid:
544 544 return p1
545 545 if not self.applied:
546 546 return None
547 547 return self.applied[-1].node
548 548 p1, p2 = repo.changelog.parents(rev)
549 549 if p2 != nullid and p2 in [x.node for x in self.applied]:
550 550 return p2
551 551 return p1
552 552
553 553 def mergepatch(self, repo, mergeq, series, diffopts):
554 554 if not self.applied:
555 555 # each of the patches merged in will have two parents. This
556 556 # can confuse the qrefresh, qdiff, and strip code because it
557 557 # needs to know which parent is actually in the patch queue.
558 558 # so, we insert a merge marker with only one parent. This way
559 559 # the first patch in the queue is never a merge patch
560 560 #
561 561 pname = ".hg.patches.merge.marker"
562 562 n = repo.commit('[mq]: merge marker', force=True)
563 563 self.removeundo(repo)
564 564 self.applied.append(statusentry(n, pname))
565 565 self.applied_dirty = 1
566 566
567 567 head = self.qparents(repo)
568 568
569 569 for patch in series:
570 570 patch = mergeq.lookup(patch, strict=True)
571 571 if not patch:
572 572 self.ui.warn(_("patch %s does not exist\n") % patch)
573 573 return (1, None)
574 574 pushable, reason = self.pushable(patch)
575 575 if not pushable:
576 576 self.explain_pushable(patch, all_patches=True)
577 577 continue
578 578 info = mergeq.isapplied(patch)
579 579 if not info:
580 580 self.ui.warn(_("patch %s is not applied\n") % patch)
581 581 return (1, None)
582 582 rev = info[1]
583 583 err, head = self.mergeone(repo, mergeq, head, patch, rev, diffopts)
584 584 if head:
585 585 self.applied.append(statusentry(head, patch))
586 586 self.applied_dirty = 1
587 587 if err:
588 588 return (err, head)
589 589 self.save_dirty()
590 590 return (0, head)
591 591
592 592 def patch(self, repo, patchfile):
593 593 '''Apply patchfile to the working directory.
594 594 patchfile: name of patch file'''
595 595 files = {}
596 596 try:
597 597 fuzz = patch.patch(patchfile, self.ui, strip=1, cwd=repo.root,
598 598 files=files, eolmode=None)
599 599 except Exception, inst:
600 600 self.ui.note(str(inst) + '\n')
601 601 if not self.ui.verbose:
602 602 self.ui.warn(_("patch failed, unable to continue (try -v)\n"))
603 603 return (False, files, False)
604 604
605 605 return (True, files, fuzz)
606 606
607 607 def apply(self, repo, series, list=False, update_status=True,
608 608 strict=False, patchdir=None, merge=None, all_files=None):
609 609 wlock = lock = tr = None
610 610 try:
611 611 wlock = repo.wlock()
612 612 lock = repo.lock()
613 613 tr = repo.transaction("qpush")
614 614 try:
615 615 ret = self._apply(repo, series, list, update_status,
616 616 strict, patchdir, merge, all_files=all_files)
617 617 tr.close()
618 618 self.save_dirty()
619 619 return ret
620 620 except:
621 621 try:
622 622 tr.abort()
623 623 finally:
624 624 repo.invalidate()
625 625 repo.dirstate.invalidate()
626 626 raise
627 627 finally:
628 628 release(tr, lock, wlock)
629 629 self.removeundo(repo)
630 630
631 631 def _apply(self, repo, series, list=False, update_status=True,
632 632 strict=False, patchdir=None, merge=None, all_files=None):
633 633 '''returns (error, hash)
634 634 error = 1 for unable to read, 2 for patch failed, 3 for patch fuzz'''
635 635 # TODO unify with commands.py
636 636 if not patchdir:
637 637 patchdir = self.path
638 638 err = 0
639 639 n = None
640 640 for patchname in series:
641 641 pushable, reason = self.pushable(patchname)
642 642 if not pushable:
643 643 self.explain_pushable(patchname, all_patches=True)
644 644 continue
645 645 self.ui.status(_("applying %s\n") % patchname)
646 646 pf = os.path.join(patchdir, patchname)
647 647
648 648 try:
649 649 ph = patchheader(self.join(patchname), self.plainmode)
650 650 except:
651 651 self.ui.warn(_("unable to read %s\n") % patchname)
652 652 err = 1
653 653 break
654 654
655 655 message = ph.message
656 656 if not message:
657 657 message = "imported patch %s\n" % patchname
658 658 else:
659 659 if list:
660 660 message.append("\nimported patch %s" % patchname)
661 661 message = '\n'.join(message)
662 662
663 663 if ph.haspatch:
664 664 (patcherr, files, fuzz) = self.patch(repo, pf)
665 665 if all_files is not None:
666 666 all_files.update(files)
667 667 patcherr = not patcherr
668 668 else:
669 669 self.ui.warn(_("patch %s is empty\n") % patchname)
670 670 patcherr, files, fuzz = 0, [], 0
671 671
672 672 if merge and files:
673 673 # Mark as removed/merged and update dirstate parent info
674 674 removed = []
675 675 merged = []
676 676 for f in files:
677 677 if os.path.exists(repo.wjoin(f)):
678 678 merged.append(f)
679 679 else:
680 680 removed.append(f)
681 681 for f in removed:
682 682 repo.dirstate.remove(f)
683 683 for f in merged:
684 684 repo.dirstate.merge(f)
685 685 p1, p2 = repo.dirstate.parents()
686 686 repo.dirstate.setparents(p1, merge)
687 687
688 688 files = patch.updatedir(self.ui, repo, files)
689 689 match = cmdutil.matchfiles(repo, files or [])
690 690 n = repo.commit(message, ph.user, ph.date, match=match, force=True)
691 691
692 692 if n is None:
693 693 raise util.Abort(_("repo commit failed"))
694 694
695 695 if update_status:
696 696 self.applied.append(statusentry(n, patchname))
697 697
698 698 if patcherr:
699 699 self.ui.warn(_("patch failed, rejects left in working dir\n"))
700 700 err = 2
701 701 break
702 702
703 703 if fuzz and strict:
704 704 self.ui.warn(_("fuzz found when applying patch, stopping\n"))
705 705 err = 3
706 706 break
707 707 return (err, n)
708 708
709 709 def _cleanup(self, patches, numrevs, keep=False):
710 710 if not keep:
711 711 r = self.qrepo()
712 712 if r:
713 713 r[None].remove(patches, True)
714 714 else:
715 715 for p in patches:
716 716 os.unlink(self.join(p))
717 717
718 718 if numrevs:
719 719 del self.applied[:numrevs]
720 720 self.applied_dirty = 1
721 721
722 722 for i in sorted([self.find_series(p) for p in patches], reverse=True):
723 723 del self.full_series[i]
724 724 self.parse_series()
725 725 self.series_dirty = 1
726 726
727 727 def _revpatches(self, repo, revs):
728 728 firstrev = repo[self.applied[0].node].rev()
729 729 patches = []
730 730 for i, rev in enumerate(revs):
731 731
732 732 if rev < firstrev:
733 733 raise util.Abort(_('revision %d is not managed') % rev)
734 734
735 735 ctx = repo[rev]
736 736 base = self.applied[i].node
737 737 if ctx.node() != base:
738 738 msg = _('cannot delete revision %d above applied patches')
739 739 raise util.Abort(msg % rev)
740 740
741 741 patch = self.applied[i].name
742 742 for fmt in ('[mq]: %s', 'imported patch %s'):
743 743 if ctx.description() == fmt % patch:
744 744 msg = _('patch %s finalized without changeset message\n')
745 745 repo.ui.status(msg % patch)
746 746 break
747 747
748 748 patches.append(patch)
749 749 return patches
750 750
751 751 def finish(self, repo, revs):
752 752 patches = self._revpatches(repo, sorted(revs))
753 753 self._cleanup(patches, len(patches))
754 754
755 755 def delete(self, repo, patches, opts):
756 756 if not patches and not opts.get('rev'):
757 757 raise util.Abort(_('qdelete requires at least one revision or '
758 758 'patch name'))
759 759
760 760 realpatches = []
761 761 for patch in patches:
762 762 patch = self.lookup(patch, strict=True)
763 763 info = self.isapplied(patch)
764 764 if info:
765 765 raise util.Abort(_("cannot delete applied patch %s") % patch)
766 766 if patch not in self.series:
767 767 raise util.Abort(_("patch %s not in series file") % patch)
768 768 realpatches.append(patch)
769 769
770 770 numrevs = 0
771 771 if opts.get('rev'):
772 772 if not self.applied:
773 773 raise util.Abort(_('no patches applied'))
774 774 revs = cmdutil.revrange(repo, opts['rev'])
775 775 if len(revs) > 1 and revs[0] > revs[1]:
776 776 revs.reverse()
777 777 revpatches = self._revpatches(repo, revs)
778 778 realpatches += revpatches
779 779 numrevs = len(revpatches)
780 780
781 781 self._cleanup(realpatches, numrevs, opts.get('keep'))
782 782
783 783 def check_toppatch(self, repo):
784 784 if self.applied:
785 785 top = self.applied[-1].node
786 786 patch = self.applied[-1].name
787 787 pp = repo.dirstate.parents()
788 788 if top not in pp:
789 789 raise util.Abort(_("working directory revision is not qtip"))
790 790 return top, patch
791 791 return None, None
792 792
793 793 def check_localchanges(self, repo, force=False, refresh=True):
794 794 m, a, r, d = repo.status()[:4]
795 795 if (m or a or r or d) and not force:
796 796 if refresh:
797 797 raise util.Abort(_("local changes found, refresh first"))
798 798 else:
799 799 raise util.Abort(_("local changes found"))
800 800 return m, a, r, d
801 801
802 802 _reserved = ('series', 'status', 'guards')
803 803 def check_reserved_name(self, name):
804 804 if (name in self._reserved or name.startswith('.hg')
805 805 or name.startswith('.mq') or '#' in name or ':' in name):
806 806 raise util.Abort(_('"%s" cannot be used as the name of a patch')
807 807 % name)
808 808
809 809 def new(self, repo, patchfn, *pats, **opts):
810 810 """options:
811 811 msg: a string or a no-argument function returning a string
812 812 """
813 813 msg = opts.get('msg')
814 814 user = opts.get('user')
815 815 date = opts.get('date')
816 816 if date:
817 817 date = util.parsedate(date)
818 818 diffopts = self.diffopts({'git': opts.get('git')})
819 819 self.check_reserved_name(patchfn)
820 820 if os.path.exists(self.join(patchfn)):
821 821 raise util.Abort(_('patch "%s" already exists') % patchfn)
822 822 if opts.get('include') or opts.get('exclude') or pats:
823 823 match = cmdutil.match(repo, pats, opts)
824 824 # detect missing files in pats
825 825 def badfn(f, msg):
826 826 raise util.Abort('%s: %s' % (f, msg))
827 827 match.bad = badfn
828 828 m, a, r, d = repo.status(match=match)[:4]
829 829 else:
830 830 m, a, r, d = self.check_localchanges(repo, force=True)
831 831 match = cmdutil.matchfiles(repo, m + a + r)
832 832 if len(repo[None].parents()) > 1:
833 833 raise util.Abort(_('cannot manage merge changesets'))
834 834 commitfiles = m + a + r
835 835 self.check_toppatch(repo)
836 836 insert = self.full_series_end()
837 837 wlock = repo.wlock()
838 838 try:
839 839 # if patch file write fails, abort early
840 840 p = self.opener(patchfn, "w")
841 841 try:
842 842 if self.plainmode:
843 843 if user:
844 844 p.write("From: " + user + "\n")
845 845 if not date:
846 846 p.write("\n")
847 847 if date:
848 848 p.write("Date: %d %d\n\n" % date)
849 849 else:
850 850 p.write("# HG changeset patch\n")
851 851 p.write("# Parent "
852 852 + hex(repo[None].parents()[0].node()) + "\n")
853 853 if user:
854 854 p.write("# User " + user + "\n")
855 855 if date:
856 856 p.write("# Date %s %s\n\n" % date)
857 857 if hasattr(msg, '__call__'):
858 858 msg = msg()
859 859 commitmsg = msg and msg or ("[mq]: %s" % patchfn)
860 860 n = repo.commit(commitmsg, user, date, match=match, force=True)
861 861 if n is None:
862 862 raise util.Abort(_("repo commit failed"))
863 863 try:
864 864 self.full_series[insert:insert] = [patchfn]
865 865 self.applied.append(statusentry(n, patchfn))
866 866 self.parse_series()
867 867 self.series_dirty = 1
868 868 self.applied_dirty = 1
869 869 if msg:
870 870 msg = msg + "\n\n"
871 871 p.write(msg)
872 872 if commitfiles:
873 873 parent = self.qparents(repo, n)
874 874 chunks = patch.diff(repo, node1=parent, node2=n,
875 875 match=match, opts=diffopts)
876 876 for chunk in chunks:
877 877 p.write(chunk)
878 878 p.close()
879 879 wlock.release()
880 880 wlock = None
881 881 r = self.qrepo()
882 882 if r:
883 883 r[None].add([patchfn])
884 884 except:
885 885 repo.rollback()
886 886 raise
887 887 except Exception:
888 888 patchpath = self.join(patchfn)
889 889 try:
890 890 os.unlink(patchpath)
891 891 except:
892 892 self.ui.warn(_('error unlinking %s\n') % patchpath)
893 893 raise
894 894 self.removeundo(repo)
895 895 finally:
896 896 release(wlock)
897 897
898 898 def strip(self, repo, rev, update=True, backup="all", force=None):
899 899 wlock = lock = None
900 900 try:
901 901 wlock = repo.wlock()
902 902 lock = repo.lock()
903 903
904 904 if update:
905 905 self.check_localchanges(repo, force=force, refresh=False)
906 906 urev = self.qparents(repo, rev)
907 907 hg.clean(repo, urev)
908 908 repo.dirstate.write()
909 909
910 910 self.removeundo(repo)
911 911 repair.strip(self.ui, repo, rev, backup)
912 912 # strip may have unbundled a set of backed up revisions after
913 913 # the actual strip
914 914 self.removeundo(repo)
915 915 finally:
916 916 release(lock, wlock)
917 917
918 918 def isapplied(self, patch):
919 919 """returns (index, rev, patch)"""
920 920 for i, a in enumerate(self.applied):
921 921 if a.name == patch:
922 922 return (i, a.node, a.name)
923 923 return None
924 924
925 925 # if the exact patch name does not exist, we try a few
926 926 # variations. If strict is passed, we try only #1
927 927 #
928 928 # 1) a number to indicate an offset in the series file
929 929 # 2) a unique substring of the patch name was given
930 930 # 3) patchname[-+]num to indicate an offset in the series file
931 931 def lookup(self, patch, strict=False):
932 932 patch = patch and str(patch)
933 933
934 934 def partial_name(s):
935 935 if s in self.series:
936 936 return s
937 937 matches = [x for x in self.series if s in x]
938 938 if len(matches) > 1:
939 939 self.ui.warn(_('patch name "%s" is ambiguous:\n') % s)
940 940 for m in matches:
941 941 self.ui.warn(' %s\n' % m)
942 942 return None
943 943 if matches:
944 944 return matches[0]
945 945 if self.series and self.applied:
946 946 if s == 'qtip':
947 947 return self.series[self.series_end(True)-1]
948 948 if s == 'qbase':
949 949 return self.series[0]
950 950 return None
951 951
952 952 if patch is None:
953 953 return None
954 954 if patch in self.series:
955 955 return patch
956 956
957 957 if not os.path.isfile(self.join(patch)):
958 958 try:
959 959 sno = int(patch)
960 960 except (ValueError, OverflowError):
961 961 pass
962 962 else:
963 963 if -len(self.series) <= sno < len(self.series):
964 964 return self.series[sno]
965 965
966 966 if not strict:
967 967 res = partial_name(patch)
968 968 if res:
969 969 return res
970 970 minus = patch.rfind('-')
971 971 if minus >= 0:
972 972 res = partial_name(patch[:minus])
973 973 if res:
974 974 i = self.series.index(res)
975 975 try:
976 976 off = int(patch[minus + 1:] or 1)
977 977 except (ValueError, OverflowError):
978 978 pass
979 979 else:
980 980 if i - off >= 0:
981 981 return self.series[i - off]
982 982 plus = patch.rfind('+')
983 983 if plus >= 0:
984 984 res = partial_name(patch[:plus])
985 985 if res:
986 986 i = self.series.index(res)
987 987 try:
988 988 off = int(patch[plus + 1:] or 1)
989 989 except (ValueError, OverflowError):
990 990 pass
991 991 else:
992 992 if i + off < len(self.series):
993 993 return self.series[i + off]
994 994 raise util.Abort(_("patch %s not in series") % patch)
995 995
996 996 def push(self, repo, patch=None, force=False, list=False,
997 997 mergeq=None, all=False, move=False):
998 998 diffopts = self.diffopts()
999 999 wlock = repo.wlock()
1000 1000 try:
1001 1001 heads = []
1002 1002 for b, ls in repo.branchmap().iteritems():
1003 1003 heads += ls
1004 1004 if not heads:
1005 1005 heads = [nullid]
1006 1006 if repo.dirstate.parents()[0] not in heads:
1007 1007 self.ui.status(_("(working directory not at a head)\n"))
1008 1008
1009 1009 if not self.series:
1010 1010 self.ui.warn(_('no patches in series\n'))
1011 1011 return 0
1012 1012
1013 1013 patch = self.lookup(patch)
1014 1014 # Suppose our series file is: A B C and the current 'top'
1015 1015 # patch is B. qpush C should be performed (moving forward)
1016 1016 # qpush B is a NOP (no change) qpush A is an error (can't
1017 1017 # go backwards with qpush)
1018 1018 if patch:
1019 1019 info = self.isapplied(patch)
1020 1020 if info:
1021 1021 if info[0] < len(self.applied) - 1:
1022 1022 raise util.Abort(
1023 1023 _("cannot push to a previous patch: %s") % patch)
1024 1024 self.ui.warn(
1025 1025 _('qpush: %s is already at the top\n') % patch)
1026 1026 return 0
1027 1027 pushable, reason = self.pushable(patch)
1028 1028 if not pushable:
1029 1029 if reason:
1030 1030 reason = _('guarded by %r') % reason
1031 1031 else:
1032 1032 reason = _('no matching guards')
1033 1033 self.ui.warn(_("cannot push '%s' - %s\n") % (patch, reason))
1034 1034 return 1
1035 1035 elif all:
1036 1036 patch = self.series[-1]
1037 1037 if self.isapplied(patch):
1038 1038 self.ui.warn(_('all patches are currently applied\n'))
1039 1039 return 0
1040 1040
1041 1041 # Following the above example, starting at 'top' of B:
1042 1042 # qpush should be performed (pushes C), but a subsequent
1043 1043 # qpush without an argument is an error (nothing to
1044 1044 # apply). This allows a loop of "...while hg qpush..." to
1045 1045 # work as it detects an error when done
1046 1046 start = self.series_end()
1047 1047 if start == len(self.series):
1048 1048 self.ui.warn(_('patch series already fully applied\n'))
1049 1049 return 1
1050 1050 if not force:
1051 1051 self.check_localchanges(repo)
1052 1052
1053 1053 if move:
1054 try:
1055 index = self.series.index(patch, start)
1056 fullpatch = self.full_series[index]
1057 del self.full_series[index]
1058 except ValueError:
1059 raise util.Abort(_("patch '%s' not found") % patch)
1054 if not patch:
1055 raise util.Abort(_("please specify the patch to move"))
1056 for i, rpn in enumerate(self.full_series[start:]):
1057 # strip markers for patch guards
1058 if self.guard_re.split(rpn, 1)[0] == patch:
1059 break
1060 index = start + i
1061 assert index < len(self.full_series)
1062 fullpatch = self.full_series[index]
1063 del self.full_series[index]
1060 1064 self.full_series.insert(start, fullpatch)
1061 1065 self.parse_series()
1062 1066 self.series_dirty = 1
1063 1067
1064 1068 self.applied_dirty = 1
1065 1069 if start > 0:
1066 1070 self.check_toppatch(repo)
1067 1071 if not patch:
1068 1072 patch = self.series[start]
1069 1073 end = start + 1
1070 1074 else:
1071 1075 end = self.series.index(patch, start) + 1
1072 1076
1073 1077 s = self.series[start:end]
1074 1078 all_files = set()
1075 1079 try:
1076 1080 if mergeq:
1077 1081 ret = self.mergepatch(repo, mergeq, s, diffopts)
1078 1082 else:
1079 1083 ret = self.apply(repo, s, list, all_files=all_files)
1080 1084 except:
1081 1085 self.ui.warn(_('cleaning up working directory...'))
1082 1086 node = repo.dirstate.parents()[0]
1083 1087 hg.revert(repo, node, None)
1084 1088 # only remove unknown files that we know we touched or
1085 1089 # created while patching
1086 1090 for f in all_files:
1087 1091 if f not in repo.dirstate:
1088 1092 try:
1089 1093 util.unlink(repo.wjoin(f))
1090 1094 except OSError, inst:
1091 1095 if inst.errno != errno.ENOENT:
1092 1096 raise
1093 1097 self.ui.warn(_('done\n'))
1094 1098 raise
1095 1099
1096 1100 if not self.applied:
1097 1101 return ret[0]
1098 1102 top = self.applied[-1].name
1099 1103 if ret[0] and ret[0] > 1:
1100 1104 msg = _("errors during apply, please fix and refresh %s\n")
1101 1105 self.ui.write(msg % top)
1102 1106 else:
1103 1107 self.ui.write(_("now at: %s\n") % top)
1104 1108 return ret[0]
1105 1109
1106 1110 finally:
1107 1111 wlock.release()
1108 1112
1109 1113 def pop(self, repo, patch=None, force=False, update=True, all=False):
1110 1114 wlock = repo.wlock()
1111 1115 try:
1112 1116 if patch:
1113 1117 # index, rev, patch
1114 1118 info = self.isapplied(patch)
1115 1119 if not info:
1116 1120 patch = self.lookup(patch)
1117 1121 info = self.isapplied(patch)
1118 1122 if not info:
1119 1123 raise util.Abort(_("patch %s is not applied") % patch)
1120 1124
1121 1125 if not self.applied:
1122 1126 # Allow qpop -a to work repeatedly,
1123 1127 # but not qpop without an argument
1124 1128 self.ui.warn(_("no patches applied\n"))
1125 1129 return not all
1126 1130
1127 1131 if all:
1128 1132 start = 0
1129 1133 elif patch:
1130 1134 start = info[0] + 1
1131 1135 else:
1132 1136 start = len(self.applied) - 1
1133 1137
1134 1138 if start >= len(self.applied):
1135 1139 self.ui.warn(_("qpop: %s is already at the top\n") % patch)
1136 1140 return
1137 1141
1138 1142 if not update:
1139 1143 parents = repo.dirstate.parents()
1140 1144 rr = [x.node for x in self.applied]
1141 1145 for p in parents:
1142 1146 if p in rr:
1143 1147 self.ui.warn(_("qpop: forcing dirstate update\n"))
1144 1148 update = True
1145 1149 else:
1146 1150 parents = [p.node() for p in repo[None].parents()]
1147 1151 needupdate = False
1148 1152 for entry in self.applied[start:]:
1149 1153 if entry.node in parents:
1150 1154 needupdate = True
1151 1155 break
1152 1156 update = needupdate
1153 1157
1154 1158 if not force and update:
1155 1159 self.check_localchanges(repo)
1156 1160
1157 1161 self.applied_dirty = 1
1158 1162 end = len(self.applied)
1159 1163 rev = self.applied[start].node
1160 1164 if update:
1161 1165 top = self.check_toppatch(repo)[0]
1162 1166
1163 1167 try:
1164 1168 heads = repo.changelog.heads(rev)
1165 1169 except error.LookupError:
1166 1170 node = short(rev)
1167 1171 raise util.Abort(_('trying to pop unknown node %s') % node)
1168 1172
1169 1173 if heads != [self.applied[-1].node]:
1170 1174 raise util.Abort(_("popping would remove a revision not "
1171 1175 "managed by this patch queue"))
1172 1176
1173 1177 # we know there are no local changes, so we can make a simplified
1174 1178 # form of hg.update.
1175 1179 if update:
1176 1180 qp = self.qparents(repo, rev)
1177 1181 ctx = repo[qp]
1178 1182 m, a, r, d = repo.status(qp, top)[:4]
1179 1183 if d:
1180 1184 raise util.Abort(_("deletions found between repo revs"))
1181 1185 for f in a:
1182 1186 try:
1183 1187 util.unlink(repo.wjoin(f))
1184 1188 except OSError, e:
1185 1189 if e.errno != errno.ENOENT:
1186 1190 raise
1187 1191 repo.dirstate.forget(f)
1188 1192 for f in m + r:
1189 1193 fctx = ctx[f]
1190 1194 repo.wwrite(f, fctx.data(), fctx.flags())
1191 1195 repo.dirstate.normal(f)
1192 1196 repo.dirstate.setparents(qp, nullid)
1193 1197 for patch in reversed(self.applied[start:end]):
1194 1198 self.ui.status(_("popping %s\n") % patch.name)
1195 1199 del self.applied[start:end]
1196 1200 self.strip(repo, rev, update=False, backup='strip')
1197 1201 if self.applied:
1198 1202 self.ui.write(_("now at: %s\n") % self.applied[-1].name)
1199 1203 else:
1200 1204 self.ui.write(_("patch queue now empty\n"))
1201 1205 finally:
1202 1206 wlock.release()
1203 1207
1204 1208 def diff(self, repo, pats, opts):
1205 1209 top, patch = self.check_toppatch(repo)
1206 1210 if not top:
1207 1211 self.ui.write(_("no patches applied\n"))
1208 1212 return
1209 1213 qp = self.qparents(repo, top)
1210 1214 if opts.get('reverse'):
1211 1215 node1, node2 = None, qp
1212 1216 else:
1213 1217 node1, node2 = qp, None
1214 1218 diffopts = self.diffopts(opts, patch)
1215 1219 self.printdiff(repo, diffopts, node1, node2, files=pats, opts=opts)
1216 1220
1217 1221 def refresh(self, repo, pats=None, **opts):
1218 1222 if not self.applied:
1219 1223 self.ui.write(_("no patches applied\n"))
1220 1224 return 1
1221 1225 msg = opts.get('msg', '').rstrip()
1222 1226 newuser = opts.get('user')
1223 1227 newdate = opts.get('date')
1224 1228 if newdate:
1225 1229 newdate = '%d %d' % util.parsedate(newdate)
1226 1230 wlock = repo.wlock()
1227 1231
1228 1232 try:
1229 1233 self.check_toppatch(repo)
1230 1234 (top, patchfn) = (self.applied[-1].node, self.applied[-1].name)
1231 1235 if repo.changelog.heads(top) != [top]:
1232 1236 raise util.Abort(_("cannot refresh a revision with children"))
1233 1237
1234 1238 cparents = repo.changelog.parents(top)
1235 1239 patchparent = self.qparents(repo, top)
1236 1240 ph = patchheader(self.join(patchfn), self.plainmode)
1237 1241 diffopts = self.diffopts({'git': opts.get('git')}, patchfn)
1238 1242 if msg:
1239 1243 ph.setmessage(msg)
1240 1244 if newuser:
1241 1245 ph.setuser(newuser)
1242 1246 if newdate:
1243 1247 ph.setdate(newdate)
1244 1248 ph.setparent(hex(patchparent))
1245 1249
1246 1250 # only commit new patch when write is complete
1247 1251 patchf = self.opener(patchfn, 'w', atomictemp=True)
1248 1252
1249 1253 comments = str(ph)
1250 1254 if comments:
1251 1255 patchf.write(comments)
1252 1256
1253 1257 # update the dirstate in place, strip off the qtip commit
1254 1258 # and then commit.
1255 1259 #
1256 1260 # this should really read:
1257 1261 # mm, dd, aa, aa2 = repo.status(tip, patchparent)[:4]
1258 1262 # but we do it backwards to take advantage of manifest/chlog
1259 1263 # caching against the next repo.status call
1260 1264 mm, aa, dd, aa2 = repo.status(patchparent, top)[:4]
1261 1265 changes = repo.changelog.read(top)
1262 1266 man = repo.manifest.read(changes[0])
1263 1267 aaa = aa[:]
1264 1268 matchfn = cmdutil.match(repo, pats, opts)
1265 1269 # in short mode, we only diff the files included in the
1266 1270 # patch already plus specified files
1267 1271 if opts.get('short'):
1268 1272 # if amending a patch, we start with existing
1269 1273 # files plus specified files - unfiltered
1270 1274 match = cmdutil.matchfiles(repo, mm + aa + dd + matchfn.files())
1271 1275 # filter with inc/exl options
1272 1276 matchfn = cmdutil.match(repo, opts=opts)
1273 1277 else:
1274 1278 match = cmdutil.matchall(repo)
1275 1279 m, a, r, d = repo.status(match=match)[:4]
1276 1280
1277 1281 # we might end up with files that were added between
1278 1282 # qtip and the dirstate parent, but then changed in the
1279 1283 # local dirstate. in this case, we want them to only
1280 1284 # show up in the added section
1281 1285 for x in m:
1282 1286 if x not in aa:
1283 1287 mm.append(x)
1284 1288 # we might end up with files added by the local dirstate that
1285 1289 # were deleted by the patch. In this case, they should only
1286 1290 # show up in the changed section.
1287 1291 for x in a:
1288 1292 if x in dd:
1289 1293 del dd[dd.index(x)]
1290 1294 mm.append(x)
1291 1295 else:
1292 1296 aa.append(x)
1293 1297 # make sure any files deleted in the local dirstate
1294 1298 # are not in the add or change column of the patch
1295 1299 forget = []
1296 1300 for x in d + r:
1297 1301 if x in aa:
1298 1302 del aa[aa.index(x)]
1299 1303 forget.append(x)
1300 1304 continue
1301 1305 elif x in mm:
1302 1306 del mm[mm.index(x)]
1303 1307 dd.append(x)
1304 1308
1305 1309 m = list(set(mm))
1306 1310 r = list(set(dd))
1307 1311 a = list(set(aa))
1308 1312 c = [filter(matchfn, l) for l in (m, a, r)]
1309 1313 match = cmdutil.matchfiles(repo, set(c[0] + c[1] + c[2]))
1310 1314 chunks = patch.diff(repo, patchparent, match=match,
1311 1315 changes=c, opts=diffopts)
1312 1316 for chunk in chunks:
1313 1317 patchf.write(chunk)
1314 1318
1315 1319 try:
1316 1320 if diffopts.git or diffopts.upgrade:
1317 1321 copies = {}
1318 1322 for dst in a:
1319 1323 src = repo.dirstate.copied(dst)
1320 1324 # during qfold, the source file for copies may
1321 1325 # be removed. Treat this as a simple add.
1322 1326 if src is not None and src in repo.dirstate:
1323 1327 copies.setdefault(src, []).append(dst)
1324 1328 repo.dirstate.add(dst)
1325 1329 # remember the copies between patchparent and qtip
1326 1330 for dst in aaa:
1327 1331 f = repo.file(dst)
1328 1332 src = f.renamed(man[dst])
1329 1333 if src:
1330 1334 copies.setdefault(src[0], []).extend(
1331 1335 copies.get(dst, []))
1332 1336 if dst in a:
1333 1337 copies[src[0]].append(dst)
1334 1338 # we can't copy a file created by the patch itself
1335 1339 if dst in copies:
1336 1340 del copies[dst]
1337 1341 for src, dsts in copies.iteritems():
1338 1342 for dst in dsts:
1339 1343 repo.dirstate.copy(src, dst)
1340 1344 else:
1341 1345 for dst in a:
1342 1346 repo.dirstate.add(dst)
1343 1347 # Drop useless copy information
1344 1348 for f in list(repo.dirstate.copies()):
1345 1349 repo.dirstate.copy(None, f)
1346 1350 for f in r:
1347 1351 repo.dirstate.remove(f)
1348 1352 # if the patch excludes a modified file, mark that
1349 1353 # file with mtime=0 so status can see it.
1350 1354 mm = []
1351 1355 for i in xrange(len(m)-1, -1, -1):
1352 1356 if not matchfn(m[i]):
1353 1357 mm.append(m[i])
1354 1358 del m[i]
1355 1359 for f in m:
1356 1360 repo.dirstate.normal(f)
1357 1361 for f in mm:
1358 1362 repo.dirstate.normallookup(f)
1359 1363 for f in forget:
1360 1364 repo.dirstate.forget(f)
1361 1365
1362 1366 if not msg:
1363 1367 if not ph.message:
1364 1368 message = "[mq]: %s\n" % patchfn
1365 1369 else:
1366 1370 message = "\n".join(ph.message)
1367 1371 else:
1368 1372 message = msg
1369 1373
1370 1374 user = ph.user or changes[1]
1371 1375
1372 1376 # assumes strip can roll itself back if interrupted
1373 1377 repo.dirstate.setparents(*cparents)
1374 1378 self.applied.pop()
1375 1379 self.applied_dirty = 1
1376 1380 self.strip(repo, top, update=False,
1377 1381 backup='strip')
1378 1382 except:
1379 1383 repo.dirstate.invalidate()
1380 1384 raise
1381 1385
1382 1386 try:
1383 1387 # might be nice to attempt to roll back strip after this
1384 1388 patchf.rename()
1385 1389 n = repo.commit(message, user, ph.date, match=match,
1386 1390 force=True)
1387 1391 self.applied.append(statusentry(n, patchfn))
1388 1392 except:
1389 1393 ctx = repo[cparents[0]]
1390 1394 repo.dirstate.rebuild(ctx.node(), ctx.manifest())
1391 1395 self.save_dirty()
1392 1396 self.ui.warn(_('refresh interrupted while patch was popped! '
1393 1397 '(revert --all, qpush to recover)\n'))
1394 1398 raise
1395 1399 finally:
1396 1400 wlock.release()
1397 1401 self.removeundo(repo)
1398 1402
1399 1403 def init(self, repo, create=False):
1400 1404 if not create and os.path.isdir(self.path):
1401 1405 raise util.Abort(_("patch queue directory already exists"))
1402 1406 try:
1403 1407 os.mkdir(self.path)
1404 1408 except OSError, inst:
1405 1409 if inst.errno != errno.EEXIST or not create:
1406 1410 raise
1407 1411 if create:
1408 1412 return self.qrepo(create=True)
1409 1413
1410 1414 def unapplied(self, repo, patch=None):
1411 1415 if patch and patch not in self.series:
1412 1416 raise util.Abort(_("patch %s is not in series file") % patch)
1413 1417 if not patch:
1414 1418 start = self.series_end()
1415 1419 else:
1416 1420 start = self.series.index(patch) + 1
1417 1421 unapplied = []
1418 1422 for i in xrange(start, len(self.series)):
1419 1423 pushable, reason = self.pushable(i)
1420 1424 if pushable:
1421 1425 unapplied.append((i, self.series[i]))
1422 1426 self.explain_pushable(i)
1423 1427 return unapplied
1424 1428
1425 1429 def qseries(self, repo, missing=None, start=0, length=None, status=None,
1426 1430 summary=False):
1427 1431 def displayname(pfx, patchname, state):
1428 1432 if pfx:
1429 1433 self.ui.write(pfx)
1430 1434 if summary:
1431 1435 ph = patchheader(self.join(patchname), self.plainmode)
1432 1436 msg = ph.message and ph.message[0] or ''
1433 1437 if self.ui.formatted():
1434 1438 width = util.termwidth() - len(pfx) - len(patchname) - 2
1435 1439 if width > 0:
1436 1440 msg = util.ellipsis(msg, width)
1437 1441 else:
1438 1442 msg = ''
1439 1443 self.ui.write(patchname, label='qseries.' + state)
1440 1444 self.ui.write(': ')
1441 1445 self.ui.write(msg, label='qseries.message.' + state)
1442 1446 else:
1443 1447 self.ui.write(patchname, label='qseries.' + state)
1444 1448 self.ui.write('\n')
1445 1449
1446 1450 applied = set([p.name for p in self.applied])
1447 1451 if length is None:
1448 1452 length = len(self.series) - start
1449 1453 if not missing:
1450 1454 if self.ui.verbose:
1451 1455 idxwidth = len(str(start + length - 1))
1452 1456 for i in xrange(start, start + length):
1453 1457 patch = self.series[i]
1454 1458 if patch in applied:
1455 1459 char, state = 'A', 'applied'
1456 1460 elif self.pushable(i)[0]:
1457 1461 char, state = 'U', 'unapplied'
1458 1462 else:
1459 1463 char, state = 'G', 'guarded'
1460 1464 pfx = ''
1461 1465 if self.ui.verbose:
1462 1466 pfx = '%*d %s ' % (idxwidth, i, char)
1463 1467 elif status and status != char:
1464 1468 continue
1465 1469 displayname(pfx, patch, state)
1466 1470 else:
1467 1471 msng_list = []
1468 1472 for root, dirs, files in os.walk(self.path):
1469 1473 d = root[len(self.path) + 1:]
1470 1474 for f in files:
1471 1475 fl = os.path.join(d, f)
1472 1476 if (fl not in self.series and
1473 1477 fl not in (self.status_path, self.series_path,
1474 1478 self.guards_path)
1475 1479 and not fl.startswith('.')):
1476 1480 msng_list.append(fl)
1477 1481 for x in sorted(msng_list):
1478 1482 pfx = self.ui.verbose and ('D ') or ''
1479 1483 displayname(pfx, x, 'missing')
1480 1484
1481 1485 def issaveline(self, l):
1482 1486 if l.name == '.hg.patches.save.line':
1483 1487 return True
1484 1488
1485 1489 def qrepo(self, create=False):
1486 1490 if create or os.path.isdir(self.join(".hg")):
1487 1491 return hg.repository(self.ui, path=self.path, create=create)
1488 1492
1489 1493 def restore(self, repo, rev, delete=None, qupdate=None):
1490 1494 desc = repo[rev].description().strip()
1491 1495 lines = desc.splitlines()
1492 1496 i = 0
1493 1497 datastart = None
1494 1498 series = []
1495 1499 applied = []
1496 1500 qpp = None
1497 1501 for i, line in enumerate(lines):
1498 1502 if line == 'Patch Data:':
1499 1503 datastart = i + 1
1500 1504 elif line.startswith('Dirstate:'):
1501 1505 l = line.rstrip()
1502 1506 l = l[10:].split(' ')
1503 1507 qpp = [bin(x) for x in l]
1504 1508 elif datastart != None:
1505 1509 l = line.rstrip()
1506 1510 n, name = l.split(':', 1)
1507 1511 if n:
1508 1512 applied.append(statusentry(bin(n), name))
1509 1513 else:
1510 1514 series.append(l)
1511 1515 if datastart is None:
1512 1516 self.ui.warn(_("No saved patch data found\n"))
1513 1517 return 1
1514 1518 self.ui.warn(_("restoring status: %s\n") % lines[0])
1515 1519 self.full_series = series
1516 1520 self.applied = applied
1517 1521 self.parse_series()
1518 1522 self.series_dirty = 1
1519 1523 self.applied_dirty = 1
1520 1524 heads = repo.changelog.heads()
1521 1525 if delete:
1522 1526 if rev not in heads:
1523 1527 self.ui.warn(_("save entry has children, leaving it alone\n"))
1524 1528 else:
1525 1529 self.ui.warn(_("removing save entry %s\n") % short(rev))
1526 1530 pp = repo.dirstate.parents()
1527 1531 if rev in pp:
1528 1532 update = True
1529 1533 else:
1530 1534 update = False
1531 1535 self.strip(repo, rev, update=update, backup='strip')
1532 1536 if qpp:
1533 1537 self.ui.warn(_("saved queue repository parents: %s %s\n") %
1534 1538 (short(qpp[0]), short(qpp[1])))
1535 1539 if qupdate:
1536 1540 self.ui.status(_("queue directory updating\n"))
1537 1541 r = self.qrepo()
1538 1542 if not r:
1539 1543 self.ui.warn(_("Unable to load queue repository\n"))
1540 1544 return 1
1541 1545 hg.clean(r, qpp[0])
1542 1546
1543 1547 def save(self, repo, msg=None):
1544 1548 if not self.applied:
1545 1549 self.ui.warn(_("save: no patches applied, exiting\n"))
1546 1550 return 1
1547 1551 if self.issaveline(self.applied[-1]):
1548 1552 self.ui.warn(_("status is already saved\n"))
1549 1553 return 1
1550 1554
1551 1555 if not msg:
1552 1556 msg = _("hg patches saved state")
1553 1557 else:
1554 1558 msg = "hg patches: " + msg.rstrip('\r\n')
1555 1559 r = self.qrepo()
1556 1560 if r:
1557 1561 pp = r.dirstate.parents()
1558 1562 msg += "\nDirstate: %s %s" % (hex(pp[0]), hex(pp[1]))
1559 1563 msg += "\n\nPatch Data:\n"
1560 1564 msg += ''.join('%s\n' % x for x in self.applied)
1561 1565 msg += ''.join(':%s\n' % x for x in self.full_series)
1562 1566 n = repo.commit(msg, force=True)
1563 1567 if not n:
1564 1568 self.ui.warn(_("repo commit failed\n"))
1565 1569 return 1
1566 1570 self.applied.append(statusentry(n, '.hg.patches.save.line'))
1567 1571 self.applied_dirty = 1
1568 1572 self.removeundo(repo)
1569 1573
1570 1574 def full_series_end(self):
1571 1575 if self.applied:
1572 1576 p = self.applied[-1].name
1573 1577 end = self.find_series(p)
1574 1578 if end is None:
1575 1579 return len(self.full_series)
1576 1580 return end + 1
1577 1581 return 0
1578 1582
1579 1583 def series_end(self, all_patches=False):
1580 1584 """If all_patches is False, return the index of the next pushable patch
1581 1585 in the series, or the series length. If all_patches is True, return the
1582 1586 index of the first patch past the last applied one.
1583 1587 """
1584 1588 end = 0
1585 1589 def next(start):
1586 1590 if all_patches or start >= len(self.series):
1587 1591 return start
1588 1592 for i in xrange(start, len(self.series)):
1589 1593 p, reason = self.pushable(i)
1590 1594 if p:
1591 1595 break
1592 1596 self.explain_pushable(i)
1593 1597 return i
1594 1598 if self.applied:
1595 1599 p = self.applied[-1].name
1596 1600 try:
1597 1601 end = self.series.index(p)
1598 1602 except ValueError:
1599 1603 return 0
1600 1604 return next(end + 1)
1601 1605 return next(end)
1602 1606
1603 1607 def appliedname(self, index):
1604 1608 pname = self.applied[index].name
1605 1609 if not self.ui.verbose:
1606 1610 p = pname
1607 1611 else:
1608 1612 p = str(self.series.index(pname)) + " " + pname
1609 1613 return p
1610 1614
1611 1615 def qimport(self, repo, files, patchname=None, rev=None, existing=None,
1612 1616 force=None, git=False):
1613 1617 def checkseries(patchname):
1614 1618 if patchname in self.series:
1615 1619 raise util.Abort(_('patch %s is already in the series file')
1616 1620 % patchname)
1617 1621 def checkfile(patchname):
1618 1622 if not force and os.path.exists(self.join(patchname)):
1619 1623 raise util.Abort(_('patch "%s" already exists')
1620 1624 % patchname)
1621 1625
1622 1626 if rev:
1623 1627 if files:
1624 1628 raise util.Abort(_('option "-r" not valid when importing '
1625 1629 'files'))
1626 1630 rev = cmdutil.revrange(repo, rev)
1627 1631 rev.sort(reverse=True)
1628 1632 if (len(files) > 1 or len(rev) > 1) and patchname:
1629 1633 raise util.Abort(_('option "-n" not valid when importing multiple '
1630 1634 'patches'))
1631 1635 if rev:
1632 1636 # If mq patches are applied, we can only import revisions
1633 1637 # that form a linear path to qbase.
1634 1638 # Otherwise, they should form a linear path to a head.
1635 1639 heads = repo.changelog.heads(repo.changelog.node(rev[-1]))
1636 1640 if len(heads) > 1:
1637 1641 raise util.Abort(_('revision %d is the root of more than one '
1638 1642 'branch') % rev[-1])
1639 1643 if self.applied:
1640 1644 base = repo.changelog.node(rev[0])
1641 1645 if base in [n.node for n in self.applied]:
1642 1646 raise util.Abort(_('revision %d is already managed')
1643 1647 % rev[0])
1644 1648 if heads != [self.applied[-1].node]:
1645 1649 raise util.Abort(_('revision %d is not the parent of '
1646 1650 'the queue') % rev[0])
1647 1651 base = repo.changelog.rev(self.applied[0].node)
1648 1652 lastparent = repo.changelog.parentrevs(base)[0]
1649 1653 else:
1650 1654 if heads != [repo.changelog.node(rev[0])]:
1651 1655 raise util.Abort(_('revision %d has unmanaged children')
1652 1656 % rev[0])
1653 1657 lastparent = None
1654 1658
1655 1659 diffopts = self.diffopts({'git': git})
1656 1660 for r in rev:
1657 1661 p1, p2 = repo.changelog.parentrevs(r)
1658 1662 n = repo.changelog.node(r)
1659 1663 if p2 != nullrev:
1660 1664 raise util.Abort(_('cannot import merge revision %d') % r)
1661 1665 if lastparent and lastparent != r:
1662 1666 raise util.Abort(_('revision %d is not the parent of %d')
1663 1667 % (r, lastparent))
1664 1668 lastparent = p1
1665 1669
1666 1670 if not patchname:
1667 1671 patchname = normname('%d.diff' % r)
1668 1672 self.check_reserved_name(patchname)
1669 1673 checkseries(patchname)
1670 1674 checkfile(patchname)
1671 1675 self.full_series.insert(0, patchname)
1672 1676
1673 1677 patchf = self.opener(patchname, "w")
1674 1678 cmdutil.export(repo, [n], fp=patchf, opts=diffopts)
1675 1679 patchf.close()
1676 1680
1677 1681 se = statusentry(n, patchname)
1678 1682 self.applied.insert(0, se)
1679 1683
1680 1684 self.added.append(patchname)
1681 1685 patchname = None
1682 1686 self.parse_series()
1683 1687 self.applied_dirty = 1
1684 1688 self.series_dirty = True
1685 1689
1686 1690 for i, filename in enumerate(files):
1687 1691 if existing:
1688 1692 if filename == '-':
1689 1693 raise util.Abort(_('-e is incompatible with import from -'))
1690 1694 if not patchname:
1691 1695 patchname = normname(filename)
1692 1696 self.check_reserved_name(patchname)
1693 1697 if not os.path.isfile(self.join(patchname)):
1694 1698 raise util.Abort(_("patch %s does not exist") % patchname)
1695 1699 else:
1696 1700 try:
1697 1701 if filename == '-':
1698 1702 if not patchname:
1699 1703 raise util.Abort(
1700 1704 _('need --name to import a patch from -'))
1701 1705 text = sys.stdin.read()
1702 1706 else:
1703 1707 text = url.open(self.ui, filename).read()
1704 1708 except (OSError, IOError):
1705 raise util.Abort(_("unable to read %s") % filename)
1709 raise util.Abort(_("unable to read file %s") % filename)
1706 1710 if not patchname:
1707 1711 patchname = normname(os.path.basename(filename))
1708 1712 self.check_reserved_name(patchname)
1709 1713 checkfile(patchname)
1710 1714 patchf = self.opener(patchname, "w")
1711 1715 patchf.write(text)
1712 1716 if not force:
1713 1717 checkseries(patchname)
1714 1718 if patchname not in self.series:
1715 1719 index = self.full_series_end() + i
1716 1720 self.full_series[index:index] = [patchname]
1717 1721 self.parse_series()
1718 1722 self.series_dirty = True
1719 1723 self.ui.warn(_("adding %s to series file\n") % patchname)
1720 1724 self.added.append(patchname)
1721 1725 patchname = None
1722 1726
1723 1727 def delete(ui, repo, *patches, **opts):
1724 1728 """remove patches from queue
1725 1729
1726 1730 The patches must not be applied, and at least one patch is required. With
1727 1731 -k/--keep, the patch files are preserved in the patch directory.
1728 1732
1729 1733 To stop managing a patch and move it into permanent history,
1730 1734 use the :hg:`qfinish` command."""
1731 1735 q = repo.mq
1732 1736 q.delete(repo, patches, opts)
1733 1737 q.save_dirty()
1734 1738 return 0
1735 1739
1736 1740 def applied(ui, repo, patch=None, **opts):
1737 1741 """print the patches already applied"""
1738 1742
1739 1743 q = repo.mq
1740 1744 l = len(q.applied)
1741 1745
1742 1746 if patch:
1743 1747 if patch not in q.series:
1744 1748 raise util.Abort(_("patch %s is not in series file") % patch)
1745 1749 end = q.series.index(patch) + 1
1746 1750 else:
1747 1751 end = q.series_end(True)
1748 1752
1749 1753 if opts.get('last') and not end:
1750 1754 ui.write(_("no patches applied\n"))
1751 1755 return 1
1752 1756 elif opts.get('last') and end == 1:
1753 1757 ui.write(_("only one patch applied\n"))
1754 1758 return 1
1755 1759 elif opts.get('last'):
1756 1760 start = end - 2
1757 1761 end = 1
1758 1762 else:
1759 1763 start = 0
1760 1764
1761 1765 return q.qseries(repo, length=end, start=start, status='A',
1762 1766 summary=opts.get('summary'))
1763 1767
1764 1768 def unapplied(ui, repo, patch=None, **opts):
1765 1769 """print the patches not yet applied"""
1766 1770
1767 1771 q = repo.mq
1768 1772 if patch:
1769 1773 if patch not in q.series:
1770 1774 raise util.Abort(_("patch %s is not in series file") % patch)
1771 1775 start = q.series.index(patch) + 1
1772 1776 else:
1773 1777 start = q.series_end(True)
1774 1778
1775 1779 if start == len(q.series) and opts.get('first'):
1776 1780 ui.write(_("all patches applied\n"))
1777 1781 return 1
1778 1782
1779 1783 length = opts.get('first') and 1 or None
1780 1784 return q.qseries(repo, start=start, length=length, status='U',
1781 1785 summary=opts.get('summary'))
1782 1786
1783 1787 def qimport(ui, repo, *filename, **opts):
1784 1788 """import a patch
1785 1789
1786 1790 The patch is inserted into the series after the last applied
1787 1791 patch. If no patches have been applied, qimport prepends the patch
1788 1792 to the series.
1789 1793
1790 1794 The patch will have the same name as its source file unless you
1791 1795 give it a new one with -n/--name.
1792 1796
1793 1797 You can register an existing patch inside the patch directory with
1794 1798 the -e/--existing flag.
1795 1799
1796 1800 With -f/--force, an existing patch of the same name will be
1797 1801 overwritten.
1798 1802
1799 1803 An existing changeset may be placed under mq control with -r/--rev
1800 1804 (e.g. qimport --rev tip -n patch will place tip under mq control).
1801 1805 With -g/--git, patches imported with --rev will use the git diff
1802 1806 format. See the diffs help topic for information on why this is
1803 1807 important for preserving rename/copy information and permission
1804 1808 changes.
1805 1809
1806 1810 To import a patch from standard input, pass - as the patch file.
1807 1811 When importing from standard input, a patch name must be specified
1808 1812 using the --name flag.
1809 1813 """
1810 1814 q = repo.mq
1811 1815 try:
1812 1816 q.qimport(repo, filename, patchname=opts['name'],
1813 1817 existing=opts['existing'], force=opts['force'], rev=opts['rev'],
1814 1818 git=opts['git'])
1815 1819 finally:
1816 1820 q.save_dirty()
1817 1821
1818 1822 if opts.get('push') and not opts.get('rev'):
1819 1823 return q.push(repo, None)
1820 1824 return 0
1821 1825
1822 1826 def qinit(ui, repo, create):
1823 1827 """initialize a new queue repository
1824 1828
1825 1829 This command also creates a series file for ordering patches, and
1826 1830 an mq-specific .hgignore file in the queue repository, to exclude
1827 1831 the status and guards files (these contain mostly transient state)."""
1828 1832 q = repo.mq
1829 1833 r = q.init(repo, create)
1830 1834 q.save_dirty()
1831 1835 if r:
1832 1836 if not os.path.exists(r.wjoin('.hgignore')):
1833 1837 fp = r.wopener('.hgignore', 'w')
1834 1838 fp.write('^\\.hg\n')
1835 1839 fp.write('^\\.mq\n')
1836 1840 fp.write('syntax: glob\n')
1837 1841 fp.write('status\n')
1838 1842 fp.write('guards\n')
1839 1843 fp.close()
1840 1844 if not os.path.exists(r.wjoin('series')):
1841 1845 r.wopener('series', 'w').close()
1842 1846 r[None].add(['.hgignore', 'series'])
1843 1847 commands.add(ui, r)
1844 1848 return 0
1845 1849
1846 1850 def init(ui, repo, **opts):
1847 1851 """init a new queue repository (DEPRECATED)
1848 1852
1849 1853 The queue repository is unversioned by default. If
1850 1854 -c/--create-repo is specified, qinit will create a separate nested
1851 1855 repository for patches (qinit -c may also be run later to convert
1852 1856 an unversioned patch repository into a versioned one). You can use
1853 1857 qcommit to commit changes to this queue repository.
1854 1858
1855 1859 This command is deprecated. Without -c, it's implied by other relevant
1856 1860 commands. With -c, use :hg:`init --mq` instead."""
1857 1861 return qinit(ui, repo, create=opts['create_repo'])
1858 1862
1859 1863 def clone(ui, source, dest=None, **opts):
1860 1864 '''clone main and patch repository at same time
1861 1865
1862 1866 If source is local, destination will have no patches applied. If
1863 1867 source is remote, this command can not check if patches are
1864 1868 applied in source, so cannot guarantee that patches are not
1865 1869 applied in destination. If you clone remote repository, be sure
1866 1870 before that it has no patches applied.
1867 1871
1868 1872 Source patch repository is looked for in <src>/.hg/patches by
1869 1873 default. Use -p <url> to change.
1870 1874
1871 1875 The patch directory must be a nested Mercurial repository, as
1872 1876 would be created by :hg:`init --mq`.
1873 1877 '''
1874 1878 def patchdir(repo):
1875 1879 url = repo.url()
1876 1880 if url.endswith('/'):
1877 1881 url = url[:-1]
1878 1882 return url + '/.hg/patches'
1879 1883 if dest is None:
1880 1884 dest = hg.defaultdest(source)
1881 1885 sr = hg.repository(hg.remoteui(ui, opts), ui.expandpath(source))
1882 1886 if opts['patches']:
1883 1887 patchespath = ui.expandpath(opts['patches'])
1884 1888 else:
1885 1889 patchespath = patchdir(sr)
1886 1890 try:
1887 1891 hg.repository(ui, patchespath)
1888 1892 except error.RepoError:
1889 1893 raise util.Abort(_('versioned patch repository not found'
1890 1894 ' (see init --mq)'))
1891 1895 qbase, destrev = None, None
1892 1896 if sr.local():
1893 1897 if sr.mq.applied:
1894 1898 qbase = sr.mq.applied[0].node
1895 1899 if not hg.islocal(dest):
1896 1900 heads = set(sr.heads())
1897 1901 destrev = list(heads.difference(sr.heads(qbase)))
1898 1902 destrev.append(sr.changelog.parents(qbase)[0])
1899 1903 elif sr.capable('lookup'):
1900 1904 try:
1901 1905 qbase = sr.lookup('qbase')
1902 1906 except error.RepoError:
1903 1907 pass
1904 1908 ui.note(_('cloning main repository\n'))
1905 1909 sr, dr = hg.clone(ui, sr.url(), dest,
1906 1910 pull=opts['pull'],
1907 1911 rev=destrev,
1908 1912 update=False,
1909 1913 stream=opts['uncompressed'])
1910 1914 ui.note(_('cloning patch repository\n'))
1911 1915 hg.clone(ui, opts['patches'] or patchdir(sr), patchdir(dr),
1912 1916 pull=opts['pull'], update=not opts['noupdate'],
1913 1917 stream=opts['uncompressed'])
1914 1918 if dr.local():
1915 1919 if qbase:
1916 1920 ui.note(_('stripping applied patches from destination '
1917 1921 'repository\n'))
1918 1922 dr.mq.strip(dr, qbase, update=False, backup=None)
1919 1923 if not opts['noupdate']:
1920 1924 ui.note(_('updating destination repository\n'))
1921 1925 hg.update(dr, dr.changelog.tip())
1922 1926
1923 1927 def commit(ui, repo, *pats, **opts):
1924 1928 """commit changes in the queue repository (DEPRECATED)
1925 1929
1926 1930 This command is deprecated; use :hg:`commit --mq` instead."""
1927 1931 q = repo.mq
1928 1932 r = q.qrepo()
1929 1933 if not r:
1930 1934 raise util.Abort('no queue repository')
1931 1935 commands.commit(r.ui, r, *pats, **opts)
1932 1936
1933 1937 def series(ui, repo, **opts):
1934 1938 """print the entire series file"""
1935 1939 repo.mq.qseries(repo, missing=opts['missing'], summary=opts['summary'])
1936 1940 return 0
1937 1941
1938 1942 def top(ui, repo, **opts):
1939 1943 """print the name of the current patch"""
1940 1944 q = repo.mq
1941 1945 t = q.applied and q.series_end(True) or 0
1942 1946 if t:
1943 1947 return q.qseries(repo, start=t - 1, length=1, status='A',
1944 1948 summary=opts.get('summary'))
1945 1949 else:
1946 1950 ui.write(_("no patches applied\n"))
1947 1951 return 1
1948 1952
1949 1953 def next(ui, repo, **opts):
1950 1954 """print the name of the next patch"""
1951 1955 q = repo.mq
1952 1956 end = q.series_end()
1953 1957 if end == len(q.series):
1954 1958 ui.write(_("all patches applied\n"))
1955 1959 return 1
1956 1960 return q.qseries(repo, start=end, length=1, summary=opts.get('summary'))
1957 1961
1958 1962 def prev(ui, repo, **opts):
1959 1963 """print the name of the previous patch"""
1960 1964 q = repo.mq
1961 1965 l = len(q.applied)
1962 1966 if l == 1:
1963 1967 ui.write(_("only one patch applied\n"))
1964 1968 return 1
1965 1969 if not l:
1966 1970 ui.write(_("no patches applied\n"))
1967 1971 return 1
1968 1972 return q.qseries(repo, start=l - 2, length=1, status='A',
1969 1973 summary=opts.get('summary'))
1970 1974
1971 1975 def setupheaderopts(ui, opts):
1972 1976 if not opts.get('user') and opts.get('currentuser'):
1973 1977 opts['user'] = ui.username()
1974 1978 if not opts.get('date') and opts.get('currentdate'):
1975 1979 opts['date'] = "%d %d" % util.makedate()
1976 1980
1977 1981 def new(ui, repo, patch, *args, **opts):
1978 1982 """create a new patch
1979 1983
1980 1984 qnew creates a new patch on top of the currently-applied patch (if
1981 1985 any). The patch will be initialized with any outstanding changes
1982 1986 in the working directory. You may also use -I/--include,
1983 1987 -X/--exclude, and/or a list of files after the patch name to add
1984 1988 only changes to matching files to the new patch, leaving the rest
1985 1989 as uncommitted modifications.
1986 1990
1987 1991 -u/--user and -d/--date can be used to set the (given) user and
1988 1992 date, respectively. -U/--currentuser and -D/--currentdate set user
1989 1993 to current user and date to current date.
1990 1994
1991 1995 -e/--edit, -m/--message or -l/--logfile set the patch header as
1992 1996 well as the commit message. If none is specified, the header is
1993 1997 empty and the commit message is '[mq]: PATCH'.
1994 1998
1995 1999 Use the -g/--git option to keep the patch in the git extended diff
1996 2000 format. Read the diffs help topic for more information on why this
1997 2001 is important for preserving permission changes and copy/rename
1998 2002 information.
1999 2003 """
2000 2004 msg = cmdutil.logmessage(opts)
2001 2005 def getmsg():
2002 2006 return ui.edit(msg, ui.username())
2003 2007 q = repo.mq
2004 2008 opts['msg'] = msg
2005 2009 if opts.get('edit'):
2006 2010 opts['msg'] = getmsg
2007 2011 else:
2008 2012 opts['msg'] = msg
2009 2013 setupheaderopts(ui, opts)
2010 2014 q.new(repo, patch, *args, **opts)
2011 2015 q.save_dirty()
2012 2016 return 0
2013 2017
2014 2018 def refresh(ui, repo, *pats, **opts):
2015 2019 """update the current patch
2016 2020
2017 2021 If any file patterns are provided, the refreshed patch will
2018 2022 contain only the modifications that match those patterns; the
2019 2023 remaining modifications will remain in the working directory.
2020 2024
2021 2025 If -s/--short is specified, files currently included in the patch
2022 2026 will be refreshed just like matched files and remain in the patch.
2023 2027
2024 2028 hg add/remove/copy/rename work as usual, though you might want to
2025 2029 use git-style patches (-g/--git or [diff] git=1) to track copies
2026 2030 and renames. See the diffs help topic for more information on the
2027 2031 git diff format.
2028 2032 """
2029 2033 q = repo.mq
2030 2034 message = cmdutil.logmessage(opts)
2031 2035 if opts['edit']:
2032 2036 if not q.applied:
2033 2037 ui.write(_("no patches applied\n"))
2034 2038 return 1
2035 2039 if message:
2036 2040 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
2037 2041 patch = q.applied[-1].name
2038 2042 ph = patchheader(q.join(patch), q.plainmode)
2039 2043 message = ui.edit('\n'.join(ph.message), ph.user or ui.username())
2040 2044 setupheaderopts(ui, opts)
2041 2045 ret = q.refresh(repo, pats, msg=message, **opts)
2042 2046 q.save_dirty()
2043 2047 return ret
2044 2048
2045 2049 def diff(ui, repo, *pats, **opts):
2046 2050 """diff of the current patch and subsequent modifications
2047 2051
2048 2052 Shows a diff which includes the current patch as well as any
2049 2053 changes which have been made in the working directory since the
2050 2054 last refresh (thus showing what the current patch would become
2051 2055 after a qrefresh).
2052 2056
2053 2057 Use :hg:`diff` if you only want to see the changes made since the
2054 2058 last qrefresh, or :hg:`export qtip` if you want to see changes
2055 2059 made by the current patch without including changes made since the
2056 2060 qrefresh.
2057 2061 """
2058 2062 repo.mq.diff(repo, pats, opts)
2059 2063 return 0
2060 2064
2061 2065 def fold(ui, repo, *files, **opts):
2062 2066 """fold the named patches into the current patch
2063 2067
2064 2068 Patches must not yet be applied. Each patch will be successively
2065 2069 applied to the current patch in the order given. If all the
2066 2070 patches apply successfully, the current patch will be refreshed
2067 2071 with the new cumulative patch, and the folded patches will be
2068 2072 deleted. With -k/--keep, the folded patch files will not be
2069 2073 removed afterwards.
2070 2074
2071 2075 The header for each folded patch will be concatenated with the
2072 2076 current patch header, separated by a line of '* * *'."""
2073 2077
2074 2078 q = repo.mq
2075 2079
2076 2080 if not files:
2077 2081 raise util.Abort(_('qfold requires at least one patch name'))
2078 2082 if not q.check_toppatch(repo)[0]:
2079 2083 raise util.Abort(_('No patches applied'))
2080 2084 q.check_localchanges(repo)
2081 2085
2082 2086 message = cmdutil.logmessage(opts)
2083 2087 if opts['edit']:
2084 2088 if message:
2085 2089 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
2086 2090
2087 2091 parent = q.lookup('qtip')
2088 2092 patches = []
2089 2093 messages = []
2090 2094 for f in files:
2091 2095 p = q.lookup(f)
2092 2096 if p in patches or p == parent:
2093 2097 ui.warn(_('Skipping already folded patch %s') % p)
2094 2098 if q.isapplied(p):
2095 2099 raise util.Abort(_('qfold cannot fold already applied patch %s') % p)
2096 2100 patches.append(p)
2097 2101
2098 2102 for p in patches:
2099 2103 if not message:
2100 2104 ph = patchheader(q.join(p), q.plainmode)
2101 2105 if ph.message:
2102 2106 messages.append(ph.message)
2103 2107 pf = q.join(p)
2104 2108 (patchsuccess, files, fuzz) = q.patch(repo, pf)
2105 2109 if not patchsuccess:
2106 2110 raise util.Abort(_('Error folding patch %s') % p)
2107 2111 patch.updatedir(ui, repo, files)
2108 2112
2109 2113 if not message:
2110 2114 ph = patchheader(q.join(parent), q.plainmode)
2111 2115 message, user = ph.message, ph.user
2112 2116 for msg in messages:
2113 2117 message.append('* * *')
2114 2118 message.extend(msg)
2115 2119 message = '\n'.join(message)
2116 2120
2117 2121 if opts['edit']:
2118 2122 message = ui.edit(message, user or ui.username())
2119 2123
2120 2124 diffopts = q.patchopts(q.diffopts(), *patches)
2121 2125 q.refresh(repo, msg=message, git=diffopts.git)
2122 2126 q.delete(repo, patches, opts)
2123 2127 q.save_dirty()
2124 2128
2125 2129 def goto(ui, repo, patch, **opts):
2126 2130 '''push or pop patches until named patch is at top of stack'''
2127 2131 q = repo.mq
2128 2132 patch = q.lookup(patch)
2129 2133 if q.isapplied(patch):
2130 2134 ret = q.pop(repo, patch, force=opts['force'])
2131 2135 else:
2132 2136 ret = q.push(repo, patch, force=opts['force'])
2133 2137 q.save_dirty()
2134 2138 return ret
2135 2139
2136 2140 def guard(ui, repo, *args, **opts):
2137 2141 '''set or print guards for a patch
2138 2142
2139 2143 Guards control whether a patch can be pushed. A patch with no
2140 2144 guards is always pushed. A patch with a positive guard ("+foo") is
2141 2145 pushed only if the :hg:`qselect` command has activated it. A patch with
2142 2146 a negative guard ("-foo") is never pushed if the :hg:`qselect` command
2143 2147 has activated it.
2144 2148
2145 2149 With no arguments, print the currently active guards.
2146 2150 With arguments, set guards for the named patch.
2147 2151 NOTE: Specifying negative guards now requires '--'.
2148 2152
2149 2153 To set guards on another patch::
2150 2154
2151 2155 hg qguard other.patch -- +2.6.17 -stable
2152 2156 '''
2153 2157 def status(idx):
2154 2158 guards = q.series_guards[idx] or ['unguarded']
2155 2159 ui.write('%s: ' % ui.label(q.series[idx], 'qguard.patch'))
2156 2160 for i, guard in enumerate(guards):
2157 2161 if guard.startswith('+'):
2158 2162 ui.write(guard, label='qguard.positive')
2159 2163 elif guard.startswith('-'):
2160 2164 ui.write(guard, label='qguard.negative')
2161 2165 else:
2162 2166 ui.write(guard, label='qguard.unguarded')
2163 2167 if i != len(guards) - 1:
2164 2168 ui.write(' ')
2165 2169 ui.write('\n')
2166 2170 q = repo.mq
2167 2171 patch = None
2168 2172 args = list(args)
2169 2173 if opts['list']:
2170 2174 if args or opts['none']:
2171 2175 raise util.Abort(_('cannot mix -l/--list with options or arguments'))
2172 2176 for i in xrange(len(q.series)):
2173 2177 status(i)
2174 2178 return
2175 2179 if not args or args[0][0:1] in '-+':
2176 2180 if not q.applied:
2177 2181 raise util.Abort(_('no patches applied'))
2178 2182 patch = q.applied[-1].name
2179 2183 if patch is None and args[0][0:1] not in '-+':
2180 2184 patch = args.pop(0)
2181 2185 if patch is None:
2182 2186 raise util.Abort(_('no patch to work with'))
2183 2187 if args or opts['none']:
2184 2188 idx = q.find_series(patch)
2185 2189 if idx is None:
2186 2190 raise util.Abort(_('no patch named %s') % patch)
2187 2191 q.set_guards(idx, args)
2188 2192 q.save_dirty()
2189 2193 else:
2190 2194 status(q.series.index(q.lookup(patch)))
2191 2195
2192 2196 def header(ui, repo, patch=None):
2193 2197 """print the header of the topmost or specified patch"""
2194 2198 q = repo.mq
2195 2199
2196 2200 if patch:
2197 2201 patch = q.lookup(patch)
2198 2202 else:
2199 2203 if not q.applied:
2200 2204 ui.write(_('no patches applied\n'))
2201 2205 return 1
2202 2206 patch = q.lookup('qtip')
2203 2207 ph = patchheader(q.join(patch), q.plainmode)
2204 2208
2205 2209 ui.write('\n'.join(ph.message) + '\n')
2206 2210
2207 2211 def lastsavename(path):
2208 2212 (directory, base) = os.path.split(path)
2209 2213 names = os.listdir(directory)
2210 2214 namere = re.compile("%s.([0-9]+)" % base)
2211 2215 maxindex = None
2212 2216 maxname = None
2213 2217 for f in names:
2214 2218 m = namere.match(f)
2215 2219 if m:
2216 2220 index = int(m.group(1))
2217 2221 if maxindex is None or index > maxindex:
2218 2222 maxindex = index
2219 2223 maxname = f
2220 2224 if maxname:
2221 2225 return (os.path.join(directory, maxname), maxindex)
2222 2226 return (None, None)
2223 2227
2224 2228 def savename(path):
2225 2229 (last, index) = lastsavename(path)
2226 2230 if last is None:
2227 2231 index = 0
2228 2232 newpath = path + ".%d" % (index + 1)
2229 2233 return newpath
2230 2234
2231 2235 def push(ui, repo, patch=None, **opts):
2232 2236 """push the next patch onto the stack
2233 2237
2234 2238 When -f/--force is applied, all local changes in patched files
2235 2239 will be lost.
2236 2240 """
2237 2241 q = repo.mq
2238 2242 mergeq = None
2239 2243
2240 2244 if opts['merge']:
2241 2245 if opts['name']:
2242 2246 newpath = repo.join(opts['name'])
2243 2247 else:
2244 2248 newpath, i = lastsavename(q.path)
2245 2249 if not newpath:
2246 2250 ui.warn(_("no saved queues found, please use -n\n"))
2247 2251 return 1
2248 2252 mergeq = queue(ui, repo.join(""), newpath)
2249 2253 ui.warn(_("merging with queue at: %s\n") % mergeq.path)
2250 2254 ret = q.push(repo, patch, force=opts['force'], list=opts['list'],
2251 2255 mergeq=mergeq, all=opts.get('all'), move=opts.get('move'))
2252 2256 return ret
2253 2257
2254 2258 def pop(ui, repo, patch=None, **opts):
2255 2259 """pop the current patch off the stack
2256 2260
2257 2261 By default, pops off the top of the patch stack. If given a patch
2258 2262 name, keeps popping off patches until the named patch is at the
2259 2263 top of the stack.
2260 2264 """
2261 2265 localupdate = True
2262 2266 if opts['name']:
2263 2267 q = queue(ui, repo.join(""), repo.join(opts['name']))
2264 2268 ui.warn(_('using patch queue: %s\n') % q.path)
2265 2269 localupdate = False
2266 2270 else:
2267 2271 q = repo.mq
2268 2272 ret = q.pop(repo, patch, force=opts['force'], update=localupdate,
2269 2273 all=opts['all'])
2270 2274 q.save_dirty()
2271 2275 return ret
2272 2276
2273 2277 def rename(ui, repo, patch, name=None, **opts):
2274 2278 """rename a patch
2275 2279
2276 2280 With one argument, renames the current patch to PATCH1.
2277 2281 With two arguments, renames PATCH1 to PATCH2."""
2278 2282
2279 2283 q = repo.mq
2280 2284
2281 2285 if not name:
2282 2286 name = patch
2283 2287 patch = None
2284 2288
2285 2289 if patch:
2286 2290 patch = q.lookup(patch)
2287 2291 else:
2288 2292 if not q.applied:
2289 2293 ui.write(_('no patches applied\n'))
2290 2294 return
2291 2295 patch = q.lookup('qtip')
2292 2296 absdest = q.join(name)
2293 2297 if os.path.isdir(absdest):
2294 2298 name = normname(os.path.join(name, os.path.basename(patch)))
2295 2299 absdest = q.join(name)
2296 2300 if os.path.exists(absdest):
2297 2301 raise util.Abort(_('%s already exists') % absdest)
2298 2302
2299 2303 if name in q.series:
2300 2304 raise util.Abort(
2301 2305 _('A patch named %s already exists in the series file') % name)
2302 2306
2303 2307 ui.note(_('renaming %s to %s\n') % (patch, name))
2304 2308 i = q.find_series(patch)
2305 2309 guards = q.guard_re.findall(q.full_series[i])
2306 2310 q.full_series[i] = name + ''.join([' #' + g for g in guards])
2307 2311 q.parse_series()
2308 2312 q.series_dirty = 1
2309 2313
2310 2314 info = q.isapplied(patch)
2311 2315 if info:
2312 2316 q.applied[info[0]] = statusentry(info[1], name)
2313 2317 q.applied_dirty = 1
2314 2318
2315 2319 destdir = os.path.dirname(absdest)
2316 2320 if not os.path.isdir(destdir):
2317 2321 os.makedirs(destdir)
2318 2322 util.rename(q.join(patch), absdest)
2319 2323 r = q.qrepo()
2320 2324 if r:
2321 2325 wctx = r[None]
2322 2326 wlock = r.wlock()
2323 2327 try:
2324 2328 if r.dirstate[patch] == 'a':
2325 2329 r.dirstate.forget(patch)
2326 2330 r.dirstate.add(name)
2327 2331 else:
2328 2332 if r.dirstate[name] == 'r':
2329 2333 wctx.undelete([name])
2330 2334 wctx.copy(patch, name)
2331 2335 wctx.remove([patch], False)
2332 2336 finally:
2333 2337 wlock.release()
2334 2338
2335 2339 q.save_dirty()
2336 2340
2337 2341 def restore(ui, repo, rev, **opts):
2338 2342 """restore the queue state saved by a revision (DEPRECATED)
2339 2343
2340 2344 This command is deprecated, use rebase --mq instead."""
2341 2345 rev = repo.lookup(rev)
2342 2346 q = repo.mq
2343 2347 q.restore(repo, rev, delete=opts['delete'],
2344 2348 qupdate=opts['update'])
2345 2349 q.save_dirty()
2346 2350 return 0
2347 2351
2348 2352 def save(ui, repo, **opts):
2349 2353 """save current queue state (DEPRECATED)
2350 2354
2351 2355 This command is deprecated, use rebase --mq instead."""
2352 2356 q = repo.mq
2353 2357 message = cmdutil.logmessage(opts)
2354 2358 ret = q.save(repo, msg=message)
2355 2359 if ret:
2356 2360 return ret
2357 2361 q.save_dirty()
2358 2362 if opts['copy']:
2359 2363 path = q.path
2360 2364 if opts['name']:
2361 2365 newpath = os.path.join(q.basepath, opts['name'])
2362 2366 if os.path.exists(newpath):
2363 2367 if not os.path.isdir(newpath):
2364 2368 raise util.Abort(_('destination %s exists and is not '
2365 2369 'a directory') % newpath)
2366 2370 if not opts['force']:
2367 2371 raise util.Abort(_('destination %s exists, '
2368 2372 'use -f to force') % newpath)
2369 2373 else:
2370 2374 newpath = savename(path)
2371 2375 ui.warn(_("copy %s to %s\n") % (path, newpath))
2372 2376 util.copyfiles(path, newpath)
2373 2377 if opts['empty']:
2374 2378 try:
2375 2379 os.unlink(q.join(q.status_path))
2376 2380 except:
2377 2381 pass
2378 2382 return 0
2379 2383
2380 2384 def strip(ui, repo, rev, **opts):
2381 2385 """strip a changeset and all its descendants from the repository
2382 2386
2383 2387 The strip command removes all changesets whose local revision
2384 2388 number is greater than or equal to REV, and then restores any
2385 2389 changesets that are not descendants of REV. If the working
2386 2390 directory has uncommitted changes, the operation is aborted unless
2387 2391 the --force flag is supplied.
2388 2392
2389 2393 If a parent of the working directory is stripped, then the working
2390 2394 directory will automatically be updated to the most recent
2391 2395 available ancestor of the stripped parent after the operation
2392 2396 completes.
2393 2397
2394 2398 Any stripped changesets are stored in ``.hg/strip-backup`` as a
2395 2399 bundle (see :hg:`help bundle` and :hg:`help unbundle`). They can
2396 2400 be restored by running :hg:`unbundle .hg/strip-backup/BUNDLE`,
2397 2401 where BUNDLE is the bundle file created by the strip. Note that
2398 2402 the local revision numbers will in general be different after the
2399 2403 restore.
2400 2404
2401 2405 Use the --nobackup option to discard the backup bundle once the
2402 2406 operation completes.
2403 2407 """
2404 2408 backup = 'all'
2405 2409 if opts['backup']:
2406 2410 backup = 'strip'
2407 2411 elif opts['nobackup']:
2408 2412 backup = 'none'
2409 2413
2410 2414 rev = repo.lookup(rev)
2411 2415 p = repo.dirstate.parents()
2412 2416 cl = repo.changelog
2413 2417 update = True
2414 2418 if p[0] == nullid:
2415 2419 update = False
2416 2420 elif p[1] == nullid and rev != cl.ancestor(p[0], rev):
2417 2421 update = False
2418 2422 elif rev not in (cl.ancestor(p[0], rev), cl.ancestor(p[1], rev)):
2419 2423 update = False
2420 2424
2421 2425 q = repo.mq
2422 2426 if q.applied:
2423 2427 if rev == cl.ancestor(repo.lookup('qtip'), rev):
2424 2428 q.applied_dirty = True
2425 2429 start = 0
2426 2430 end = len(q.applied)
2427 2431 applied_list = [i.node for i in q.applied]
2428 2432 if rev in applied_list:
2429 2433 start = applied_list.index(rev)
2430 2434 del q.applied[start:end]
2431 2435 q.save_dirty()
2432 2436
2433 2437 repo.mq.strip(repo, rev, backup=backup, update=update, force=opts['force'])
2434 2438 return 0
2435 2439
2436 2440 def select(ui, repo, *args, **opts):
2437 2441 '''set or print guarded patches to push
2438 2442
2439 2443 Use the :hg:`qguard` command to set or print guards on patch, then use
2440 2444 qselect to tell mq which guards to use. A patch will be pushed if
2441 2445 it has no guards or any positive guards match the currently
2442 2446 selected guard, but will not be pushed if any negative guards
2443 2447 match the current guard. For example::
2444 2448
2445 2449 qguard foo.patch -stable (negative guard)
2446 2450 qguard bar.patch +stable (positive guard)
2447 2451 qselect stable
2448 2452
2449 2453 This activates the "stable" guard. mq will skip foo.patch (because
2450 2454 it has a negative match) but push bar.patch (because it has a
2451 2455 positive match).
2452 2456
2453 2457 With no arguments, prints the currently active guards.
2454 2458 With one argument, sets the active guard.
2455 2459
2456 2460 Use -n/--none to deactivate guards (no other arguments needed).
2457 2461 When no guards are active, patches with positive guards are
2458 2462 skipped and patches with negative guards are pushed.
2459 2463
2460 2464 qselect can change the guards on applied patches. It does not pop
2461 2465 guarded patches by default. Use --pop to pop back to the last
2462 2466 applied patch that is not guarded. Use --reapply (which implies
2463 2467 --pop) to push back to the current patch afterwards, but skip
2464 2468 guarded patches.
2465 2469
2466 2470 Use -s/--series to print a list of all guards in the series file
2467 2471 (no other arguments needed). Use -v for more information.'''
2468 2472
2469 2473 q = repo.mq
2470 2474 guards = q.active()
2471 2475 if args or opts['none']:
2472 2476 old_unapplied = q.unapplied(repo)
2473 2477 old_guarded = [i for i in xrange(len(q.applied)) if
2474 2478 not q.pushable(i)[0]]
2475 2479 q.set_active(args)
2476 2480 q.save_dirty()
2477 2481 if not args:
2478 2482 ui.status(_('guards deactivated\n'))
2479 2483 if not opts['pop'] and not opts['reapply']:
2480 2484 unapplied = q.unapplied(repo)
2481 2485 guarded = [i for i in xrange(len(q.applied))
2482 2486 if not q.pushable(i)[0]]
2483 2487 if len(unapplied) != len(old_unapplied):
2484 2488 ui.status(_('number of unguarded, unapplied patches has '
2485 2489 'changed from %d to %d\n') %
2486 2490 (len(old_unapplied), len(unapplied)))
2487 2491 if len(guarded) != len(old_guarded):
2488 2492 ui.status(_('number of guarded, applied patches has changed '
2489 2493 'from %d to %d\n') %
2490 2494 (len(old_guarded), len(guarded)))
2491 2495 elif opts['series']:
2492 2496 guards = {}
2493 2497 noguards = 0
2494 2498 for gs in q.series_guards:
2495 2499 if not gs:
2496 2500 noguards += 1
2497 2501 for g in gs:
2498 2502 guards.setdefault(g, 0)
2499 2503 guards[g] += 1
2500 2504 if ui.verbose:
2501 2505 guards['NONE'] = noguards
2502 2506 guards = guards.items()
2503 2507 guards.sort(key=lambda x: x[0][1:])
2504 2508 if guards:
2505 2509 ui.note(_('guards in series file:\n'))
2506 2510 for guard, count in guards:
2507 2511 ui.note('%2d ' % count)
2508 2512 ui.write(guard, '\n')
2509 2513 else:
2510 2514 ui.note(_('no guards in series file\n'))
2511 2515 else:
2512 2516 if guards:
2513 2517 ui.note(_('active guards:\n'))
2514 2518 for g in guards:
2515 2519 ui.write(g, '\n')
2516 2520 else:
2517 2521 ui.write(_('no active guards\n'))
2518 2522 reapply = opts['reapply'] and q.applied and q.appliedname(-1)
2519 2523 popped = False
2520 2524 if opts['pop'] or opts['reapply']:
2521 2525 for i in xrange(len(q.applied)):
2522 2526 pushable, reason = q.pushable(i)
2523 2527 if not pushable:
2524 2528 ui.status(_('popping guarded patches\n'))
2525 2529 popped = True
2526 2530 if i == 0:
2527 2531 q.pop(repo, all=True)
2528 2532 else:
2529 2533 q.pop(repo, i - 1)
2530 2534 break
2531 2535 if popped:
2532 2536 try:
2533 2537 if reapply:
2534 2538 ui.status(_('reapplying unguarded patches\n'))
2535 2539 q.push(repo, reapply)
2536 2540 finally:
2537 2541 q.save_dirty()
2538 2542
2539 2543 def finish(ui, repo, *revrange, **opts):
2540 2544 """move applied patches into repository history
2541 2545
2542 2546 Finishes the specified revisions (corresponding to applied
2543 2547 patches) by moving them out of mq control into regular repository
2544 2548 history.
2545 2549
2546 2550 Accepts a revision range or the -a/--applied option. If --applied
2547 2551 is specified, all applied mq revisions are removed from mq
2548 2552 control. Otherwise, the given revisions must be at the base of the
2549 2553 stack of applied patches.
2550 2554
2551 2555 This can be especially useful if your changes have been applied to
2552 2556 an upstream repository, or if you are about to push your changes
2553 2557 to upstream.
2554 2558 """
2555 2559 if not opts['applied'] and not revrange:
2556 2560 raise util.Abort(_('no revisions specified'))
2557 2561 elif opts['applied']:
2558 2562 revrange = ('qbase:qtip',) + revrange
2559 2563
2560 2564 q = repo.mq
2561 2565 if not q.applied:
2562 2566 ui.status(_('no patches applied\n'))
2563 2567 return 0
2564 2568
2565 2569 revs = cmdutil.revrange(repo, revrange)
2566 2570 q.finish(repo, revs)
2567 2571 q.save_dirty()
2568 2572 return 0
2569 2573
2570 2574 def qqueue(ui, repo, name=None, **opts):
2571 2575 '''manage multiple patch queues
2572 2576
2573 2577 Supports switching between different patch queues, as well as creating
2574 2578 new patch queues and deleting existing ones.
2575 2579
2576 2580 Omitting a queue name or specifying -l/--list will show you the registered
2577 2581 queues - by default the "normal" patches queue is registered. The currently
2578 2582 active queue will be marked with "(active)".
2579 2583
2580 2584 To create a new queue, use -c/--create. The queue is automatically made
2581 2585 active, except in the case where there are applied patches from the
2582 2586 currently active queue in the repository. Then the queue will only be
2583 2587 created and switching will fail.
2584 2588
2585 2589 To delete an existing queue, use --delete. You cannot delete the currently
2586 2590 active queue.
2587 2591 '''
2588 2592
2589 2593 q = repo.mq
2590 2594
2591 2595 _defaultqueue = 'patches'
2592 2596 _allqueues = 'patches.queues'
2593 2597 _activequeue = 'patches.queue'
2594 2598
2595 2599 def _getcurrent():
2596 2600 cur = os.path.basename(q.path)
2597 2601 if cur.startswith('patches-'):
2598 2602 cur = cur[8:]
2599 2603 return cur
2600 2604
2601 2605 def _noqueues():
2602 2606 try:
2603 2607 fh = repo.opener(_allqueues, 'r')
2604 2608 fh.close()
2605 2609 except IOError:
2606 2610 return True
2607 2611
2608 2612 return False
2609 2613
2610 2614 def _getqueues():
2611 2615 current = _getcurrent()
2612 2616
2613 2617 try:
2614 2618 fh = repo.opener(_allqueues, 'r')
2615 2619 queues = [queue.strip() for queue in fh if queue.strip()]
2616 2620 if current not in queues:
2617 2621 queues.append(current)
2618 2622 except IOError:
2619 2623 queues = [_defaultqueue]
2620 2624
2621 2625 return sorted(queues)
2622 2626
2623 2627 def _setactive(name):
2624 2628 if q.applied:
2625 2629 raise util.Abort(_('patches applied - cannot set new queue active'))
2626 2630
2627 2631 fh = repo.opener(_activequeue, 'w')
2628 2632 if name != 'patches':
2629 2633 fh.write(name)
2630 2634 fh.close()
2631 2635
2632 2636 def _addqueue(name):
2633 2637 fh = repo.opener(_allqueues, 'a')
2634 2638 fh.write('%s\n' % (name,))
2635 2639 fh.close()
2636 2640
2637 2641 def _validname(name):
2638 2642 for n in name:
2639 2643 if n in ':\\/.':
2640 2644 return False
2641 2645 return True
2642 2646
2643 2647 if not name or opts.get('list'):
2644 2648 current = _getcurrent()
2645 2649 for queue in _getqueues():
2646 2650 ui.write('%s' % (queue,))
2647 2651 if queue == current:
2648 2652 ui.write(_(' (active)\n'))
2649 2653 else:
2650 2654 ui.write('\n')
2651 2655 return
2652 2656
2653 2657 if not _validname(name):
2654 2658 raise util.Abort(
2655 2659 _('invalid queue name, may not contain the characters ":\\/."'))
2656 2660
2657 2661 existing = _getqueues()
2658 2662
2659 2663 if opts.get('create'):
2660 2664 if name in existing:
2661 2665 raise util.Abort(_('queue "%s" already exists') % name)
2662 2666 if _noqueues():
2663 2667 _addqueue(_defaultqueue)
2664 2668 _addqueue(name)
2665 2669 _setactive(name)
2666 2670 elif opts.get('delete'):
2667 2671 if name not in existing:
2668 2672 raise util.Abort(_('cannot delete queue that does not exist'))
2669 2673
2670 2674 current = _getcurrent()
2671 2675
2672 2676 if name == current:
2673 2677 raise util.Abort(_('cannot delete currently active queue'))
2674 2678
2675 2679 fh = repo.opener('patches.queues.new', 'w')
2676 2680 for queue in existing:
2677 2681 if queue == name:
2678 2682 continue
2679 2683 fh.write('%s\n' % (queue,))
2680 2684 fh.close()
2681 2685 util.rename(repo.join('patches.queues.new'), repo.join(_allqueues))
2682 2686 else:
2683 2687 if name not in existing:
2684 2688 raise util.Abort(_('use --create to create a new queue'))
2685 2689 _setactive(name)
2686 2690
2687 2691 def reposetup(ui, repo):
2688 2692 class mqrepo(repo.__class__):
2689 2693 @util.propertycache
2690 2694 def mq(self):
2691 2695 return queue(self.ui, self.join(""))
2692 2696
2693 2697 def abort_if_wdir_patched(self, errmsg, force=False):
2694 2698 if self.mq.applied and not force:
2695 2699 parent = self.dirstate.parents()[0]
2696 2700 if parent in [s.node for s in self.mq.applied]:
2697 2701 raise util.Abort(errmsg)
2698 2702
2699 2703 def commit(self, text="", user=None, date=None, match=None,
2700 2704 force=False, editor=False, extra={}):
2701 2705 self.abort_if_wdir_patched(
2702 2706 _('cannot commit over an applied mq patch'),
2703 2707 force)
2704 2708
2705 2709 return super(mqrepo, self).commit(text, user, date, match, force,
2706 2710 editor, extra)
2707 2711
2708 2712 def push(self, remote, force=False, revs=None, newbranch=False):
2709 2713 if self.mq.applied and not force and not revs:
2710 2714 raise util.Abort(_('source has mq patches applied'))
2711 2715 return super(mqrepo, self).push(remote, force, revs, newbranch)
2712 2716
2713 2717 def _findtags(self):
2714 2718 '''augment tags from base class with patch tags'''
2715 2719 result = super(mqrepo, self)._findtags()
2716 2720
2717 2721 q = self.mq
2718 2722 if not q.applied:
2719 2723 return result
2720 2724
2721 2725 mqtags = [(patch.node, patch.name) for patch in q.applied]
2722 2726
2723 2727 if mqtags[-1][0] not in self.changelog.nodemap:
2724 2728 self.ui.warn(_('mq status file refers to unknown node %s\n')
2725 2729 % short(mqtags[-1][0]))
2726 2730 return result
2727 2731
2728 2732 mqtags.append((mqtags[-1][0], 'qtip'))
2729 2733 mqtags.append((mqtags[0][0], 'qbase'))
2730 2734 mqtags.append((self.changelog.parents(mqtags[0][0])[0], 'qparent'))
2731 2735 tags = result[0]
2732 2736 for patch in mqtags:
2733 2737 if patch[1] in tags:
2734 2738 self.ui.warn(_('Tag %s overrides mq patch of the same name\n')
2735 2739 % patch[1])
2736 2740 else:
2737 2741 tags[patch[1]] = patch[0]
2738 2742
2739 2743 return result
2740 2744
2741 2745 def _branchtags(self, partial, lrev):
2742 2746 q = self.mq
2743 2747 if not q.applied:
2744 2748 return super(mqrepo, self)._branchtags(partial, lrev)
2745 2749
2746 2750 cl = self.changelog
2747 2751 qbasenode = q.applied[0].node
2748 2752 if qbasenode not in cl.nodemap:
2749 2753 self.ui.warn(_('mq status file refers to unknown node %s\n')
2750 2754 % short(qbasenode))
2751 2755 return super(mqrepo, self)._branchtags(partial, lrev)
2752 2756
2753 2757 qbase = cl.rev(qbasenode)
2754 2758 start = lrev + 1
2755 2759 if start < qbase:
2756 2760 # update the cache (excluding the patches) and save it
2757 2761 ctxgen = (self[r] for r in xrange(lrev + 1, qbase))
2758 2762 self._updatebranchcache(partial, ctxgen)
2759 2763 self._writebranchcache(partial, cl.node(qbase - 1), qbase - 1)
2760 2764 start = qbase
2761 2765 # if start = qbase, the cache is as updated as it should be.
2762 2766 # if start > qbase, the cache includes (part of) the patches.
2763 2767 # we might as well use it, but we won't save it.
2764 2768
2765 2769 # update the cache up to the tip
2766 2770 ctxgen = (self[r] for r in xrange(start, len(cl)))
2767 2771 self._updatebranchcache(partial, ctxgen)
2768 2772
2769 2773 return partial
2770 2774
2771 2775 if repo.local():
2772 2776 repo.__class__ = mqrepo
2773 2777
2774 2778 def mqimport(orig, ui, repo, *args, **kwargs):
2775 2779 if (hasattr(repo, 'abort_if_wdir_patched')
2776 2780 and not kwargs.get('no_commit', False)):
2777 2781 repo.abort_if_wdir_patched(_('cannot import over an applied patch'),
2778 2782 kwargs.get('force'))
2779 2783 return orig(ui, repo, *args, **kwargs)
2780 2784
2781 2785 def mqinit(orig, ui, *args, **kwargs):
2782 2786 mq = kwargs.pop('mq', None)
2783 2787
2784 2788 if not mq:
2785 2789 return orig(ui, *args, **kwargs)
2786 2790
2787 2791 if args:
2788 2792 repopath = args[0]
2789 2793 if not hg.islocal(repopath):
2790 2794 raise util.Abort(_('only a local queue repository '
2791 2795 'may be initialized'))
2792 2796 else:
2793 2797 repopath = cmdutil.findrepo(os.getcwd())
2794 2798 if not repopath:
2795 2799 raise util.Abort(_('There is no Mercurial repository here '
2796 2800 '(.hg not found)'))
2797 2801 repo = hg.repository(ui, repopath)
2798 2802 return qinit(ui, repo, True)
2799 2803
2800 2804 def mqcommand(orig, ui, repo, *args, **kwargs):
2801 2805 """Add --mq option to operate on patch repository instead of main"""
2802 2806
2803 2807 # some commands do not like getting unknown options
2804 2808 mq = kwargs.pop('mq', None)
2805 2809
2806 2810 if not mq:
2807 2811 return orig(ui, repo, *args, **kwargs)
2808 2812
2809 2813 q = repo.mq
2810 2814 r = q.qrepo()
2811 2815 if not r:
2812 2816 raise util.Abort(_('no queue repository'))
2813 2817 return orig(r.ui, r, *args, **kwargs)
2814 2818
2815 2819 def summary(orig, ui, repo, *args, **kwargs):
2816 2820 r = orig(ui, repo, *args, **kwargs)
2817 2821 q = repo.mq
2818 2822 m = []
2819 2823 a, u = len(q.applied), len(q.unapplied(repo))
2820 2824 if a:
2821 2825 m.append(ui.label(_("%d applied"), 'qseries.applied') % a)
2822 2826 if u:
2823 2827 m.append(ui.label(_("%d unapplied"), 'qseries.unapplied') % u)
2824 2828 if m:
2825 2829 ui.write("mq: %s\n" % ', '.join(m))
2826 2830 else:
2827 2831 ui.note(_("mq: (empty queue)\n"))
2828 2832 return r
2829 2833
2830 2834 def uisetup(ui):
2831 2835 mqopt = [('', 'mq', None, _("operate on patch repository"))]
2832 2836
2833 2837 extensions.wrapcommand(commands.table, 'import', mqimport)
2834 2838 extensions.wrapcommand(commands.table, 'summary', summary)
2835 2839
2836 2840 entry = extensions.wrapcommand(commands.table, 'init', mqinit)
2837 2841 entry[1].extend(mqopt)
2838 2842
2839 2843 norepo = commands.norepo.split(" ")
2840 2844 for cmd in commands.table.keys():
2841 2845 cmd = cmdutil.parsealiases(cmd)[0]
2842 2846 if cmd in norepo:
2843 2847 continue
2844 2848 entry = extensions.wrapcommand(commands.table, cmd, mqcommand)
2845 2849 entry[1].extend(mqopt)
2846 2850
2847 2851 seriesopts = [('s', 'summary', None, _('print first line of patch header'))]
2848 2852
2849 2853 cmdtable = {
2850 2854 "qapplied":
2851 2855 (applied,
2852 2856 [('1', 'last', None, _('show only the last patch'))] + seriesopts,
2853 2857 _('hg qapplied [-1] [-s] [PATCH]')),
2854 2858 "qclone":
2855 2859 (clone,
2856 2860 [('', 'pull', None, _('use pull protocol to copy metadata')),
2857 2861 ('U', 'noupdate', None, _('do not update the new working directories')),
2858 2862 ('', 'uncompressed', None,
2859 2863 _('use uncompressed transfer (fast over LAN)')),
2860 2864 ('p', 'patches', '',
2861 2865 _('location of source patch repository'), _('REPO')),
2862 2866 ] + commands.remoteopts,
2863 2867 _('hg qclone [OPTION]... SOURCE [DEST]')),
2864 2868 "qcommit|qci":
2865 2869 (commit,
2866 2870 commands.table["^commit|ci"][1],
2867 2871 _('hg qcommit [OPTION]... [FILE]...')),
2868 2872 "^qdiff":
2869 2873 (diff,
2870 2874 commands.diffopts + commands.diffopts2 + commands.walkopts,
2871 2875 _('hg qdiff [OPTION]... [FILE]...')),
2872 2876 "qdelete|qremove|qrm":
2873 2877 (delete,
2874 2878 [('k', 'keep', None, _('keep patch file')),
2875 2879 ('r', 'rev', [],
2876 2880 _('stop managing a revision (DEPRECATED)'), _('REV'))],
2877 2881 _('hg qdelete [-k] [-r REV]... [PATCH]...')),
2878 2882 'qfold':
2879 2883 (fold,
2880 2884 [('e', 'edit', None, _('edit patch header')),
2881 2885 ('k', 'keep', None, _('keep folded patch files')),
2882 2886 ] + commands.commitopts,
2883 2887 _('hg qfold [-e] [-k] [-m TEXT] [-l FILE] PATCH...')),
2884 2888 'qgoto':
2885 2889 (goto,
2886 2890 [('f', 'force', None, _('overwrite any local changes'))],
2887 2891 _('hg qgoto [OPTION]... PATCH')),
2888 2892 'qguard':
2889 2893 (guard,
2890 2894 [('l', 'list', None, _('list all patches and guards')),
2891 2895 ('n', 'none', None, _('drop all guards'))],
2892 2896 _('hg qguard [-l] [-n] [PATCH] [-- [+GUARD]... [-GUARD]...]')),
2893 2897 'qheader': (header, [], _('hg qheader [PATCH]')),
2894 2898 "qimport":
2895 2899 (qimport,
2896 2900 [('e', 'existing', None, _('import file in patch directory')),
2897 2901 ('n', 'name', '',
2898 2902 _('name of patch file'), _('NAME')),
2899 2903 ('f', 'force', None, _('overwrite existing files')),
2900 2904 ('r', 'rev', [],
2901 2905 _('place existing revisions under mq control'), _('REV')),
2902 2906 ('g', 'git', None, _('use git extended diff format')),
2903 2907 ('P', 'push', None, _('qpush after importing'))],
2904 2908 _('hg qimport [-e] [-n NAME] [-f] [-g] [-P] [-r REV]... FILE...')),
2905 2909 "^qinit":
2906 2910 (init,
2907 2911 [('c', 'create-repo', None, _('create queue repository'))],
2908 2912 _('hg qinit [-c]')),
2909 2913 "^qnew":
2910 2914 (new,
2911 2915 [('e', 'edit', None, _('edit commit message')),
2912 2916 ('f', 'force', None, _('import uncommitted changes (DEPRECATED)')),
2913 2917 ('g', 'git', None, _('use git extended diff format')),
2914 2918 ('U', 'currentuser', None, _('add "From: <current user>" to patch')),
2915 2919 ('u', 'user', '',
2916 2920 _('add "From: <USER>" to patch'), _('USER')),
2917 2921 ('D', 'currentdate', None, _('add "Date: <current date>" to patch')),
2918 2922 ('d', 'date', '',
2919 2923 _('add "Date: <DATE>" to patch'), _('DATE'))
2920 2924 ] + commands.walkopts + commands.commitopts,
2921 2925 _('hg qnew [-e] [-m TEXT] [-l FILE] PATCH [FILE]...')),
2922 2926 "qnext": (next, [] + seriesopts, _('hg qnext [-s]')),
2923 2927 "qprev": (prev, [] + seriesopts, _('hg qprev [-s]')),
2924 2928 "^qpop":
2925 2929 (pop,
2926 2930 [('a', 'all', None, _('pop all patches')),
2927 2931 ('n', 'name', '',
2928 2932 _('queue name to pop (DEPRECATED)'), _('NAME')),
2929 2933 ('f', 'force', None, _('forget any local changes to patched files'))],
2930 2934 _('hg qpop [-a] [-n NAME] [-f] [PATCH | INDEX]')),
2931 2935 "^qpush":
2932 2936 (push,
2933 2937 [('f', 'force', None, _('apply if the patch has rejects')),
2934 2938 ('l', 'list', None, _('list patch name in commit text')),
2935 2939 ('a', 'all', None, _('apply all patches')),
2936 2940 ('m', 'merge', None, _('merge from another queue (DEPRECATED)')),
2937 2941 ('n', 'name', '',
2938 2942 _('merge queue name (DEPRECATED)'), _('NAME')),
2939 2943 ('', 'move', None, _('reorder patch series and apply only the patch'))],
2940 2944 _('hg qpush [-f] [-l] [-a] [-m] [-n NAME] [--move] [PATCH | INDEX]')),
2941 2945 "^qrefresh":
2942 2946 (refresh,
2943 2947 [('e', 'edit', None, _('edit commit message')),
2944 2948 ('g', 'git', None, _('use git extended diff format')),
2945 2949 ('s', 'short', None,
2946 2950 _('refresh only files already in the patch and specified files')),
2947 2951 ('U', 'currentuser', None,
2948 2952 _('add/update author field in patch with current user')),
2949 2953 ('u', 'user', '',
2950 2954 _('add/update author field in patch with given user'), _('USER')),
2951 2955 ('D', 'currentdate', None,
2952 2956 _('add/update date field in patch with current date')),
2953 2957 ('d', 'date', '',
2954 2958 _('add/update date field in patch with given date'), _('DATE'))
2955 2959 ] + commands.walkopts + commands.commitopts,
2956 2960 _('hg qrefresh [-I] [-X] [-e] [-m TEXT] [-l FILE] [-s] [FILE]...')),
2957 2961 'qrename|qmv':
2958 2962 (rename, [], _('hg qrename PATCH1 [PATCH2]')),
2959 2963 "qrestore":
2960 2964 (restore,
2961 2965 [('d', 'delete', None, _('delete save entry')),
2962 2966 ('u', 'update', None, _('update queue working directory'))],
2963 2967 _('hg qrestore [-d] [-u] REV')),
2964 2968 "qsave":
2965 2969 (save,
2966 2970 [('c', 'copy', None, _('copy patch directory')),
2967 2971 ('n', 'name', '',
2968 2972 _('copy directory name'), _('NAME')),
2969 2973 ('e', 'empty', None, _('clear queue status file')),
2970 2974 ('f', 'force', None, _('force copy'))] + commands.commitopts,
2971 2975 _('hg qsave [-m TEXT] [-l FILE] [-c] [-n NAME] [-e] [-f]')),
2972 2976 "qselect":
2973 2977 (select,
2974 2978 [('n', 'none', None, _('disable all guards')),
2975 2979 ('s', 'series', None, _('list all guards in series file')),
2976 2980 ('', 'pop', None, _('pop to before first guarded applied patch')),
2977 2981 ('', 'reapply', None, _('pop, then reapply patches'))],
2978 2982 _('hg qselect [OPTION]... [GUARD]...')),
2979 2983 "qseries":
2980 2984 (series,
2981 2985 [('m', 'missing', None, _('print patches not in series')),
2982 2986 ] + seriesopts,
2983 2987 _('hg qseries [-ms]')),
2984 2988 "strip":
2985 2989 (strip,
2986 2990 [('f', 'force', None, _('force removal of changesets even if the '
2987 2991 'working directory has uncommitted changes')),
2988 2992 ('b', 'backup', None, _('bundle only changesets with local revision'
2989 2993 ' number greater than REV which are not'
2990 2994 ' descendants of REV (DEPRECATED)')),
2991 2995 ('n', 'nobackup', None, _('no backups'))],
2992 2996 _('hg strip [-f] [-n] REV')),
2993 2997 "qtop": (top, [] + seriesopts, _('hg qtop [-s]')),
2994 2998 "qunapplied":
2995 2999 (unapplied,
2996 3000 [('1', 'first', None, _('show only the first patch'))] + seriesopts,
2997 3001 _('hg qunapplied [-1] [-s] [PATCH]')),
2998 3002 "qfinish":
2999 3003 (finish,
3000 3004 [('a', 'applied', None, _('finish all applied changesets'))],
3001 3005 _('hg qfinish [-a] [REV]...')),
3002 3006 'qqueue':
3003 3007 (qqueue,
3004 3008 [
3005 3009 ('l', 'list', False, _('list all available queues')),
3006 3010 ('c', 'create', False, _('create new queue')),
3007 3011 ('', 'delete', False, _('delete reference to queue')),
3008 3012 ],
3009 3013 _('[OPTION] [QUEUE]')),
3010 3014 }
3011 3015
3012 3016 colortable = {'qguard.negative': 'red',
3013 3017 'qguard.positive': 'yellow',
3014 3018 'qguard.unguarded': 'green',
3015 3019 'qseries.applied': 'blue bold underline',
3016 3020 'qseries.guarded': 'black bold',
3017 3021 'qseries.missing': 'red bold',
3018 3022 'qseries.unapplied': 'black bold'}
@@ -1,4470 +1,4472 b''
1 1 # commands.py - command processing for mercurial
2 2 #
3 3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from node import hex, nullid, nullrev, short
9 9 from lock import release
10 10 from i18n import _, gettext
11 11 import os, re, sys, difflib, time, tempfile
12 12 import hg, util, revlog, bundlerepo, extensions, copies, error
13 13 import patch, help, mdiff, url, encoding, templatekw, discovery
14 14 import archival, changegroup, cmdutil, sshserver, hbisect, hgweb, hgweb.server
15 15 import merge as mergemod
16 16 import minirst, revset
17 17 import dagparser
18 18
19 19 # Commands start here, listed alphabetically
20 20
21 21 def add(ui, repo, *pats, **opts):
22 22 """add the specified files on the next commit
23 23
24 24 Schedule files to be version controlled and added to the
25 25 repository.
26 26
27 27 The files will be added to the repository at the next commit. To
28 28 undo an add before that, see :hg:`forget`.
29 29
30 30 If no names are given, add all files to the repository.
31 31
32 32 .. container:: verbose
33 33
34 34 An example showing how new (unknown) files are added
35 35 automatically by :hg:`add`::
36 36
37 37 $ ls
38 38 foo.c
39 39 $ hg status
40 40 ? foo.c
41 41 $ hg add
42 42 adding foo.c
43 43 $ hg status
44 44 A foo.c
45 45
46 46 Returns 0 if all files are successfully added.
47 47 """
48 48
49 49 bad = []
50 50 names = []
51 51 m = cmdutil.match(repo, pats, opts)
52 52 oldbad = m.bad
53 53 m.bad = lambda x, y: bad.append(x) or oldbad(x, y)
54 54
55 55 for f in repo.walk(m):
56 56 exact = m.exact(f)
57 57 if exact or f not in repo.dirstate:
58 58 names.append(f)
59 59 if ui.verbose or not exact:
60 60 ui.status(_('adding %s\n') % m.rel(f))
61 61 if not opts.get('dry_run'):
62 62 bad += [f for f in repo[None].add(names) if f in m.files()]
63 63 return bad and 1 or 0
64 64
65 65 def addremove(ui, repo, *pats, **opts):
66 66 """add all new files, delete all missing files
67 67
68 68 Add all new files and remove all missing files from the
69 69 repository.
70 70
71 71 New files are ignored if they match any of the patterns in
72 72 .hgignore. As with add, these changes take effect at the next
73 73 commit.
74 74
75 75 Use the -s/--similarity option to detect renamed files. With a
76 76 parameter greater than 0, this compares every removed file with
77 77 every added file and records those similar enough as renames. This
78 78 option takes a percentage between 0 (disabled) and 100 (files must
79 79 be identical) as its parameter. Detecting renamed files this way
80 80 can be expensive. After using this option, :hg:`status -C` can be
81 81 used to check which files were identified as moved or renamed.
82 82
83 83 Returns 0 if all files are successfully added.
84 84 """
85 85 try:
86 86 sim = float(opts.get('similarity') or 0)
87 87 except ValueError:
88 88 raise util.Abort(_('similarity must be a number'))
89 89 if sim < 0 or sim > 100:
90 90 raise util.Abort(_('similarity must be between 0 and 100'))
91 91 return cmdutil.addremove(repo, pats, opts, similarity=sim / 100.0)
92 92
93 93 def annotate(ui, repo, *pats, **opts):
94 94 """show changeset information by line for each file
95 95
96 96 List changes in files, showing the revision id responsible for
97 97 each line
98 98
99 99 This command is useful for discovering when a change was made and
100 100 by whom.
101 101
102 102 Without the -a/--text option, annotate will avoid processing files
103 103 it detects as binary. With -a, annotate will annotate the file
104 104 anyway, although the results will probably be neither useful
105 105 nor desirable.
106 106
107 107 Returns 0 on success.
108 108 """
109 109 if opts.get('follow'):
110 110 # --follow is deprecated and now just an alias for -f/--file
111 111 # to mimic the behavior of Mercurial before version 1.5
112 112 opts['file'] = 1
113 113
114 114 datefunc = ui.quiet and util.shortdate or util.datestr
115 115 getdate = util.cachefunc(lambda x: datefunc(x[0].date()))
116 116
117 117 if not pats:
118 118 raise util.Abort(_('at least one filename or pattern is required'))
119 119
120 120 opmap = [('user', lambda x: ui.shortuser(x[0].user())),
121 121 ('number', lambda x: str(x[0].rev())),
122 122 ('changeset', lambda x: short(x[0].node())),
123 123 ('date', getdate),
124 124 ('file', lambda x: x[0].path()),
125 125 ]
126 126
127 127 if (not opts.get('user') and not opts.get('changeset')
128 128 and not opts.get('date') and not opts.get('file')):
129 129 opts['number'] = 1
130 130
131 131 linenumber = opts.get('line_number') is not None
132 132 if linenumber and (not opts.get('changeset')) and (not opts.get('number')):
133 133 raise util.Abort(_('at least one of -n/-c is required for -l'))
134 134
135 135 funcmap = [func for op, func in opmap if opts.get(op)]
136 136 if linenumber:
137 137 lastfunc = funcmap[-1]
138 138 funcmap[-1] = lambda x: "%s:%s" % (lastfunc(x), x[1])
139 139
140 140 ctx = repo[opts.get('rev')]
141 141 m = cmdutil.match(repo, pats, opts)
142 142 follow = not opts.get('no_follow')
143 143 for abs in ctx.walk(m):
144 144 fctx = ctx[abs]
145 145 if not opts.get('text') and util.binary(fctx.data()):
146 146 ui.write(_("%s: binary file\n") % ((pats and m.rel(abs)) or abs))
147 147 continue
148 148
149 149 lines = fctx.annotate(follow=follow, linenumber=linenumber)
150 150 pieces = []
151 151
152 152 for f in funcmap:
153 153 l = [f(n) for n, dummy in lines]
154 154 if l:
155 155 sized = [(x, encoding.colwidth(x)) for x in l]
156 156 ml = max([w for x, w in sized])
157 157 pieces.append(["%s%s" % (' ' * (ml - w), x) for x, w in sized])
158 158
159 159 if pieces:
160 160 for p, l in zip(zip(*pieces), lines):
161 161 ui.write("%s: %s" % (" ".join(p), l[1]))
162 162
163 163 def archive(ui, repo, dest, **opts):
164 164 '''create an unversioned archive of a repository revision
165 165
166 166 By default, the revision used is the parent of the working
167 167 directory; use -r/--rev to specify a different revision.
168 168
169 169 The archive type is automatically detected based on file
170 170 extension (or override using -t/--type).
171 171
172 172 Valid types are:
173 173
174 174 :``files``: a directory full of files (default)
175 175 :``tar``: tar archive, uncompressed
176 176 :``tbz2``: tar archive, compressed using bzip2
177 177 :``tgz``: tar archive, compressed using gzip
178 178 :``uzip``: zip archive, uncompressed
179 179 :``zip``: zip archive, compressed using deflate
180 180
181 181 The exact name of the destination archive or directory is given
182 182 using a format string; see :hg:`help export` for details.
183 183
184 184 Each member added to an archive file has a directory prefix
185 185 prepended. Use -p/--prefix to specify a format string for the
186 186 prefix. The default is the basename of the archive, with suffixes
187 187 removed.
188 188
189 189 Returns 0 on success.
190 190 '''
191 191
192 192 ctx = repo[opts.get('rev')]
193 193 if not ctx:
194 194 raise util.Abort(_('no working directory: please specify a revision'))
195 195 node = ctx.node()
196 196 dest = cmdutil.make_filename(repo, dest, node)
197 197 if os.path.realpath(dest) == repo.root:
198 198 raise util.Abort(_('repository root cannot be destination'))
199 199
200 200 def guess_type():
201 201 exttypes = {
202 202 'tar': ['.tar'],
203 203 'tbz2': ['.tbz2', '.tar.bz2'],
204 204 'tgz': ['.tgz', '.tar.gz'],
205 205 'zip': ['.zip'],
206 206 }
207 207
208 208 for type, extensions in exttypes.items():
209 209 if util.any(dest.endswith(ext) for ext in extensions):
210 210 return type
211 211 return None
212 212
213 213 kind = opts.get('type') or guess_type() or 'files'
214 214 prefix = opts.get('prefix')
215 215
216 216 if dest == '-':
217 217 if kind == 'files':
218 218 raise util.Abort(_('cannot archive plain files to stdout'))
219 219 dest = sys.stdout
220 220 if not prefix:
221 221 prefix = os.path.basename(repo.root) + '-%h'
222 222
223 223 prefix = cmdutil.make_filename(repo, prefix, node)
224 224 matchfn = cmdutil.match(repo, [], opts)
225 225 archival.archive(repo, dest, node, kind, not opts.get('no_decode'),
226 226 matchfn, prefix)
227 227
228 228 def backout(ui, repo, node=None, rev=None, **opts):
229 229 '''reverse effect of earlier changeset
230 230
231 231 Commit the backed out changes as a new changeset. The new
232 232 changeset is a child of the backed out changeset.
233 233
234 234 If you backout a changeset other than the tip, a new head is
235 235 created. This head will be the new tip and you should merge this
236 236 backout changeset with another head.
237 237
238 238 The --merge option remembers the parent of the working directory
239 239 before starting the backout, then merges the new head with that
240 240 changeset afterwards. This saves you from doing the merge by hand.
241 241 The result of this merge is not committed, as with a normal merge.
242 242
243 243 See :hg:`help dates` for a list of formats valid for -d/--date.
244 244
245 245 Returns 0 on success.
246 246 '''
247 247 if rev and node:
248 248 raise util.Abort(_("please specify just one revision"))
249 249
250 250 if not rev:
251 251 rev = node
252 252
253 253 if not rev:
254 254 raise util.Abort(_("please specify a revision to backout"))
255 255
256 256 date = opts.get('date')
257 257 if date:
258 258 opts['date'] = util.parsedate(date)
259 259
260 260 cmdutil.bail_if_changed(repo)
261 261 node = repo.lookup(rev)
262 262
263 263 op1, op2 = repo.dirstate.parents()
264 264 a = repo.changelog.ancestor(op1, node)
265 265 if a != node:
266 266 raise util.Abort(_('cannot backout change on a different branch'))
267 267
268 268 p1, p2 = repo.changelog.parents(node)
269 269 if p1 == nullid:
270 270 raise util.Abort(_('cannot backout a change with no parents'))
271 271 if p2 != nullid:
272 272 if not opts.get('parent'):
273 273 raise util.Abort(_('cannot backout a merge changeset without '
274 274 '--parent'))
275 275 p = repo.lookup(opts['parent'])
276 276 if p not in (p1, p2):
277 277 raise util.Abort(_('%s is not a parent of %s') %
278 278 (short(p), short(node)))
279 279 parent = p
280 280 else:
281 281 if opts.get('parent'):
282 282 raise util.Abort(_('cannot use --parent on non-merge changeset'))
283 283 parent = p1
284 284
285 285 # the backout should appear on the same branch
286 286 branch = repo.dirstate.branch()
287 287 hg.clean(repo, node, show_stats=False)
288 288 repo.dirstate.setbranch(branch)
289 289 revert_opts = opts.copy()
290 290 revert_opts['date'] = None
291 291 revert_opts['all'] = True
292 292 revert_opts['rev'] = hex(parent)
293 293 revert_opts['no_backup'] = None
294 294 revert(ui, repo, **revert_opts)
295 295 commit_opts = opts.copy()
296 296 commit_opts['addremove'] = False
297 297 if not commit_opts['message'] and not commit_opts['logfile']:
298 298 # we don't translate commit messages
299 299 commit_opts['message'] = "Backed out changeset %s" % short(node)
300 300 commit_opts['force_editor'] = True
301 301 commit(ui, repo, **commit_opts)
302 302 def nice(node):
303 303 return '%d:%s' % (repo.changelog.rev(node), short(node))
304 304 ui.status(_('changeset %s backs out changeset %s\n') %
305 305 (nice(repo.changelog.tip()), nice(node)))
306 306 if op1 != node:
307 307 hg.clean(repo, op1, show_stats=False)
308 308 if opts.get('merge'):
309 309 ui.status(_('merging with changeset %s\n')
310 310 % nice(repo.changelog.tip()))
311 311 hg.merge(repo, hex(repo.changelog.tip()))
312 312 else:
313 313 ui.status(_('the backout changeset is a new head - '
314 314 'do not forget to merge\n'))
315 315 ui.status(_('(use "backout --merge" '
316 316 'if you want to auto-merge)\n'))
317 317
318 318 def bisect(ui, repo, rev=None, extra=None, command=None,
319 319 reset=None, good=None, bad=None, skip=None, noupdate=None):
320 320 """subdivision search of changesets
321 321
322 322 This command helps to find changesets which introduce problems. To
323 323 use, mark the earliest changeset you know exhibits the problem as
324 324 bad, then mark the latest changeset which is free from the problem
325 325 as good. Bisect will update your working directory to a revision
326 326 for testing (unless the -U/--noupdate option is specified). Once
327 327 you have performed tests, mark the working directory as good or
328 328 bad, and bisect will either update to another candidate changeset
329 329 or announce that it has found the bad revision.
330 330
331 331 As a shortcut, you can also use the revision argument to mark a
332 332 revision as good or bad without checking it out first.
333 333
334 334 If you supply a command, it will be used for automatic bisection.
335 335 Its exit status will be used to mark revisions as good or bad:
336 336 status 0 means good, 125 means to skip the revision, 127
337 337 (command not found) will abort the bisection, and any other
338 338 non-zero exit status means the revision is bad.
339 339
340 340 Returns 0 on success.
341 341 """
342 342 def print_result(nodes, good):
343 343 displayer = cmdutil.show_changeset(ui, repo, {})
344 344 if len(nodes) == 1:
345 345 # narrowed it down to a single revision
346 346 if good:
347 347 ui.write(_("The first good revision is:\n"))
348 348 else:
349 349 ui.write(_("The first bad revision is:\n"))
350 350 displayer.show(repo[nodes[0]])
351 351 else:
352 352 # multiple possible revisions
353 353 if good:
354 354 ui.write(_("Due to skipped revisions, the first "
355 355 "good revision could be any of:\n"))
356 356 else:
357 357 ui.write(_("Due to skipped revisions, the first "
358 358 "bad revision could be any of:\n"))
359 359 for n in nodes:
360 360 displayer.show(repo[n])
361 361 displayer.close()
362 362
363 363 def check_state(state, interactive=True):
364 364 if not state['good'] or not state['bad']:
365 365 if (good or bad or skip or reset) and interactive:
366 366 return
367 367 if not state['good']:
368 368 raise util.Abort(_('cannot bisect (no known good revisions)'))
369 369 else:
370 370 raise util.Abort(_('cannot bisect (no known bad revisions)'))
371 371 return True
372 372
373 373 # backward compatibility
374 374 if rev in "good bad reset init".split():
375 375 ui.warn(_("(use of 'hg bisect <cmd>' is deprecated)\n"))
376 376 cmd, rev, extra = rev, extra, None
377 377 if cmd == "good":
378 378 good = True
379 379 elif cmd == "bad":
380 380 bad = True
381 381 else:
382 382 reset = True
383 383 elif extra or good + bad + skip + reset + bool(command) > 1:
384 384 raise util.Abort(_('incompatible arguments'))
385 385
386 386 if reset:
387 387 p = repo.join("bisect.state")
388 388 if os.path.exists(p):
389 389 os.unlink(p)
390 390 return
391 391
392 392 state = hbisect.load_state(repo)
393 393
394 394 if command:
395 395 changesets = 1
396 396 try:
397 397 while changesets:
398 398 # update state
399 399 status = util.system(command)
400 400 if status == 125:
401 401 transition = "skip"
402 402 elif status == 0:
403 403 transition = "good"
404 404 # status < 0 means process was killed
405 405 elif status == 127:
406 406 raise util.Abort(_("failed to execute %s") % command)
407 407 elif status < 0:
408 408 raise util.Abort(_("%s killed") % command)
409 409 else:
410 410 transition = "bad"
411 411 ctx = repo[rev or '.']
412 412 state[transition].append(ctx.node())
413 413 ui.status(_('Changeset %d:%s: %s\n') % (ctx, ctx, transition))
414 414 check_state(state, interactive=False)
415 415 # bisect
416 416 nodes, changesets, good = hbisect.bisect(repo.changelog, state)
417 417 # update to next check
418 418 cmdutil.bail_if_changed(repo)
419 419 hg.clean(repo, nodes[0], show_stats=False)
420 420 finally:
421 421 hbisect.save_state(repo, state)
422 422 print_result(nodes, good)
423 423 return
424 424
425 425 # update state
426 426 node = repo.lookup(rev or '.')
427 427 if good or bad or skip:
428 428 if good:
429 429 state['good'].append(node)
430 430 elif bad:
431 431 state['bad'].append(node)
432 432 elif skip:
433 433 state['skip'].append(node)
434 434 hbisect.save_state(repo, state)
435 435
436 436 if not check_state(state):
437 437 return
438 438
439 439 # actually bisect
440 440 nodes, changesets, good = hbisect.bisect(repo.changelog, state)
441 441 if changesets == 0:
442 442 print_result(nodes, good)
443 443 else:
444 444 assert len(nodes) == 1 # only a single node can be tested next
445 445 node = nodes[0]
446 446 # compute the approximate number of remaining tests
447 447 tests, size = 0, 2
448 448 while size <= changesets:
449 449 tests, size = tests + 1, size * 2
450 450 rev = repo.changelog.rev(node)
451 451 ui.write(_("Testing changeset %d:%s "
452 452 "(%d changesets remaining, ~%d tests)\n")
453 453 % (rev, short(node), changesets, tests))
454 454 if not noupdate:
455 455 cmdutil.bail_if_changed(repo)
456 456 return hg.clean(repo, node)
457 457
458 458 def branch(ui, repo, label=None, **opts):
459 459 """set or show the current branch name
460 460
461 461 With no argument, show the current branch name. With one argument,
462 462 set the working directory branch name (the branch will not exist
463 463 in the repository until the next commit). Standard practice
464 464 recommends that primary development take place on the 'default'
465 465 branch.
466 466
467 467 Unless -f/--force is specified, branch will not let you set a
468 468 branch name that already exists, even if it's inactive.
469 469
470 470 Use -C/--clean to reset the working directory branch to that of
471 471 the parent of the working directory, negating a previous branch
472 472 change.
473 473
474 474 Use the command :hg:`update` to switch to an existing branch. Use
475 475 :hg:`commit --close-branch` to mark this branch as closed.
476 476
477 477 Returns 0 on success.
478 478 """
479 479
480 480 if opts.get('clean'):
481 481 label = repo[None].parents()[0].branch()
482 482 repo.dirstate.setbranch(label)
483 483 ui.status(_('reset working directory to branch %s\n') % label)
484 484 elif label:
485 485 utflabel = encoding.fromlocal(label)
486 486 if not opts.get('force') and utflabel in repo.branchtags():
487 487 if label not in [p.branch() for p in repo.parents()]:
488 488 raise util.Abort(_('a branch of the same name already exists'
489 489 " (use 'hg update' to switch to it)"))
490 490 repo.dirstate.setbranch(utflabel)
491 491 ui.status(_('marked working directory as branch %s\n') % label)
492 492 else:
493 493 ui.write("%s\n" % encoding.tolocal(repo.dirstate.branch()))
494 494
495 495 def branches(ui, repo, active=False, closed=False):
496 496 """list repository named branches
497 497
498 498 List the repository's named branches, indicating which ones are
499 499 inactive. If -c/--closed is specified, also list branches which have
500 500 been marked closed (see :hg:`commit --close-branch`).
501 501
502 502 If -a/--active is specified, only show active branches. A branch
503 503 is considered active if it contains repository heads.
504 504
505 505 Use the command :hg:`update` to switch to an existing branch.
506 506
507 507 Returns 0.
508 508 """
509 509
510 510 hexfunc = ui.debugflag and hex or short
511 511 activebranches = [repo[n].branch() for n in repo.heads()]
512 512 def testactive(tag, node):
513 513 realhead = tag in activebranches
514 514 open = node in repo.branchheads(tag, closed=False)
515 515 return realhead and open
516 516 branches = sorted([(testactive(tag, node), repo.changelog.rev(node), tag)
517 517 for tag, node in repo.branchtags().items()],
518 518 reverse=True)
519 519
520 520 for isactive, node, tag in branches:
521 521 if (not active) or isactive:
522 522 encodedtag = encoding.tolocal(tag)
523 523 if ui.quiet:
524 524 ui.write("%s\n" % encodedtag)
525 525 else:
526 526 hn = repo.lookup(node)
527 527 if isactive:
528 528 notice = ''
529 529 elif hn not in repo.branchheads(tag, closed=False):
530 530 if not closed:
531 531 continue
532 532 notice = _(' (closed)')
533 533 else:
534 534 notice = _(' (inactive)')
535 535 rev = str(node).rjust(31 - encoding.colwidth(encodedtag))
536 536 data = encodedtag, rev, hexfunc(hn), notice
537 537 ui.write("%s %s:%s%s\n" % data)
538 538
539 539 def bundle(ui, repo, fname, dest=None, **opts):
540 540 """create a changegroup file
541 541
542 542 Generate a compressed changegroup file collecting changesets not
543 543 known to be in another repository.
544 544
545 545 If you omit the destination repository, then hg assumes the
546 546 destination will have all the nodes you specify with --base
547 547 parameters. To create a bundle containing all changesets, use
548 548 -a/--all (or --base null).
549 549
550 550 You can change compression method with the -t/--type option.
551 551 The available compression methods are: none, bzip2, and
552 552 gzip (by default, bundles are compressed using bzip2).
553 553
554 554 The bundle file can then be transferred using conventional means
555 555 and applied to another repository with the unbundle or pull
556 556 command. This is useful when direct push and pull are not
557 557 available or when exporting an entire repository is undesirable.
558 558
559 559 Applying bundles preserves all changeset contents including
560 560 permissions, copy/rename information, and revision history.
561 561
562 562 Returns 0 on success, 1 if no changes found.
563 563 """
564 564 revs = opts.get('rev') or None
565 565 if opts.get('all'):
566 566 base = ['null']
567 567 else:
568 568 base = opts.get('base')
569 569 if base:
570 570 if dest:
571 571 raise util.Abort(_("--base is incompatible with specifying "
572 572 "a destination"))
573 573 base = [repo.lookup(rev) for rev in base]
574 574 # create the right base
575 575 # XXX: nodesbetween / changegroup* should be "fixed" instead
576 576 o = []
577 577 has = set((nullid,))
578 578 for n in base:
579 579 has.update(repo.changelog.reachable(n))
580 580 if revs:
581 581 revs = [repo.lookup(rev) for rev in revs]
582 582 visit = revs[:]
583 583 has.difference_update(visit)
584 584 else:
585 585 visit = repo.changelog.heads()
586 586 seen = {}
587 587 while visit:
588 588 n = visit.pop(0)
589 589 parents = [p for p in repo.changelog.parents(n) if p not in has]
590 590 if len(parents) == 0:
591 591 if n not in has:
592 592 o.append(n)
593 593 else:
594 594 for p in parents:
595 595 if p not in seen:
596 596 seen[p] = 1
597 597 visit.append(p)
598 598 else:
599 599 dest = ui.expandpath(dest or 'default-push', dest or 'default')
600 600 dest, branches = hg.parseurl(dest, opts.get('branch'))
601 601 other = hg.repository(hg.remoteui(repo, opts), dest)
602 602 revs, checkout = hg.addbranchrevs(repo, other, branches, revs)
603 603 if revs:
604 604 revs = [repo.lookup(rev) for rev in revs]
605 605 o = discovery.findoutgoing(repo, other, force=opts.get('force'))
606 606
607 607 if not o:
608 608 ui.status(_("no changes found\n"))
609 609 return 1
610 610
611 611 if revs:
612 612 cg = repo.changegroupsubset(o, revs, 'bundle')
613 613 else:
614 614 cg = repo.changegroup(o, 'bundle')
615 615
616 616 bundletype = opts.get('type', 'bzip2').lower()
617 617 btypes = {'none': 'HG10UN', 'bzip2': 'HG10BZ', 'gzip': 'HG10GZ'}
618 618 bundletype = btypes.get(bundletype)
619 619 if bundletype not in changegroup.bundletypes:
620 620 raise util.Abort(_('unknown bundle type specified with --type'))
621 621
622 622 changegroup.writebundle(cg, fname, bundletype)
623 623
624 624 def cat(ui, repo, file1, *pats, **opts):
625 625 """output the current or given revision of files
626 626
627 627 Print the specified files as they were at the given revision. If
628 628 no revision is given, the parent of the working directory is used,
629 629 or tip if no revision is checked out.
630 630
631 631 Output may be to a file, in which case the name of the file is
632 632 given using a format string. The formatting rules are the same as
633 633 for the export command, with the following additions:
634 634
635 635 :``%s``: basename of file being printed
636 636 :``%d``: dirname of file being printed, or '.' if in repository root
637 637 :``%p``: root-relative path name of file being printed
638 638
639 639 Returns 0 on success.
640 640 """
641 641 ctx = repo[opts.get('rev')]
642 642 err = 1
643 643 m = cmdutil.match(repo, (file1,) + pats, opts)
644 644 for abs in ctx.walk(m):
645 645 fp = cmdutil.make_file(repo, opts.get('output'), ctx.node(), pathname=abs)
646 646 data = ctx[abs].data()
647 647 if opts.get('decode'):
648 648 data = repo.wwritedata(abs, data)
649 649 fp.write(data)
650 650 err = 0
651 651 return err
652 652
653 653 def clone(ui, source, dest=None, **opts):
654 654 """make a copy of an existing repository
655 655
656 656 Create a copy of an existing repository in a new directory.
657 657
658 658 If no destination directory name is specified, it defaults to the
659 659 basename of the source.
660 660
661 661 The location of the source is added to the new repository's
662 662 .hg/hgrc file, as the default to be used for future pulls.
663 663
664 664 See :hg:`help urls` for valid source format details.
665 665
666 666 It is possible to specify an ``ssh://`` URL as the destination, but no
667 667 .hg/hgrc and working directory will be created on the remote side.
668 668 Please see :hg:`help urls` for important details about ``ssh://`` URLs.
669 669
670 670 A set of changesets (tags, or branch names) to pull may be specified
671 671 by listing each changeset (tag, or branch name) with -r/--rev.
672 672 If -r/--rev is used, the cloned repository will contain only a subset
673 673 of the changesets of the source repository. Only the set of changesets
674 674 defined by all -r/--rev options (including all their ancestors)
675 675 will be pulled into the destination repository.
676 676 No subsequent changesets (including subsequent tags) will be present
677 677 in the destination.
678 678
679 679 Using -r/--rev (or 'clone src#rev dest') implies --pull, even for
680 680 local source repositories.
681 681
682 682 For efficiency, hardlinks are used for cloning whenever the source
683 683 and destination are on the same filesystem (note this applies only
684 684 to the repository data, not to the working directory). Some
685 685 filesystems, such as AFS, implement hardlinking incorrectly, but
686 686 do not report errors. In these cases, use the --pull option to
687 687 avoid hardlinking.
688 688
689 689 In some cases, you can clone repositories and the working directory
690 690 using full hardlinks with ::
691 691
692 692 $ cp -al REPO REPOCLONE
693 693
694 694 This is the fastest way to clone, but it is not always safe. The
695 695 operation is not atomic (making sure REPO is not modified during
696 696 the operation is up to you) and you have to make sure your editor
697 697 breaks hardlinks (Emacs and most Linux Kernel tools do so). Also,
698 698 this is not compatible with certain extensions that place their
699 699 metadata under the .hg directory, such as mq.
700 700
701 701 Mercurial will update the working directory to the first applicable
702 702 revision from this list:
703 703
704 704 a) null if -U or the source repository has no changesets
705 705 b) if -u . and the source repository is local, the first parent of
706 706 the source repository's working directory
707 707 c) the changeset specified with -u (if a branch name, this means the
708 708 latest head of that branch)
709 709 d) the changeset specified with -r
710 710 e) the tipmost head specified with -b
711 711 f) the tipmost head specified with the url#branch source syntax
712 712 g) the tipmost head of the default branch
713 713 h) tip
714 714
715 715 Returns 0 on success.
716 716 """
717 717 if opts.get('noupdate') and opts.get('updaterev'):
718 718 raise util.Abort(_("cannot specify both --noupdate and --updaterev"))
719 719
720 720 r = hg.clone(hg.remoteui(ui, opts), source, dest,
721 721 pull=opts.get('pull'),
722 722 stream=opts.get('uncompressed'),
723 723 rev=opts.get('rev'),
724 724 update=opts.get('updaterev') or not opts.get('noupdate'),
725 725 branch=opts.get('branch'))
726 726
727 727 return r is None
728 728
729 729 def commit(ui, repo, *pats, **opts):
730 730 """commit the specified files or all outstanding changes
731 731
732 732 Commit changes to the given files into the repository. Unlike a
733 733 centralized RCS, this operation is a local operation. See
734 734 :hg:`push` for a way to actively distribute your changes.
735 735
736 736 If a list of files is omitted, all changes reported by :hg:`status`
737 737 will be committed.
738 738
739 739 If you are committing the result of a merge, do not provide any
740 740 filenames or -I/-X filters.
741 741
742 742 If no commit message is specified, the configured editor is
743 743 started to prompt you for a message.
744 744
745 745 See :hg:`help dates` for a list of formats valid for -d/--date.
746 746
747 747 Returns 0 on success, 1 if nothing changed.
748 748 """
749 749 extra = {}
750 750 if opts.get('close_branch'):
751 751 if repo['.'].node() not in repo.branchheads():
752 752 # The topo heads set is included in the branch heads set of the
753 753 # current branch, so it's sufficient to test branchheads
754 754 raise util.Abort(_('can only close branch heads'))
755 755 extra['close'] = 1
756 756 e = cmdutil.commiteditor
757 757 if opts.get('force_editor'):
758 758 e = cmdutil.commitforceeditor
759 759
760 760 def commitfunc(ui, repo, message, match, opts):
761 761 return repo.commit(message, opts.get('user'), opts.get('date'), match,
762 762 editor=e, extra=extra)
763 763
764 764 branch = repo[None].branch()
765 765 bheads = repo.branchheads(branch)
766 766
767 767 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
768 768 if not node:
769 769 ui.status(_("nothing changed\n"))
770 770 return 1
771 771
772 772 ctx = repo[node]
773 773 parents = ctx.parents()
774 774
775 775 if bheads and not [x for x in parents
776 776 if x.node() in bheads and x.branch() == branch]:
777 777 ui.status(_('created new head\n'))
778 778 # The message is not printed for initial roots. For the other
779 779 # changesets, it is printed in the following situations:
780 780 #
781 781 # Par column: for the 2 parents with ...
782 782 # N: null or no parent
783 783 # B: parent is on another named branch
784 784 # C: parent is a regular non head changeset
785 785 # H: parent was a branch head of the current branch
786 786 # Msg column: whether we print "created new head" message
787 787 # In the following, it is assumed that there already exists some
788 788 # initial branch heads of the current branch, otherwise nothing is
789 789 # printed anyway.
790 790 #
791 791 # Par Msg Comment
792 792 # NN y additional topo root
793 793 #
794 794 # BN y additional branch root
795 795 # CN y additional topo head
796 796 # HN n usual case
797 797 #
798 798 # BB y weird additional branch root
799 799 # CB y branch merge
800 800 # HB n merge with named branch
801 801 #
802 802 # CC y additional head from merge
803 803 # CH n merge with a head
804 804 #
805 805 # HH n head merge: head count decreases
806 806
807 807 if not opts.get('close_branch'):
808 808 for r in parents:
809 809 if r.extra().get('close') and r.branch() == branch:
810 810 ui.status(_('reopening closed branch head %d\n') % r)
811 811
812 812 if ui.debugflag:
813 813 ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx.hex()))
814 814 elif ui.verbose:
815 815 ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx))
816 816
817 817 def copy(ui, repo, *pats, **opts):
818 818 """mark files as copied for the next commit
819 819
820 820 Mark dest as having copies of source files. If dest is a
821 821 directory, copies are put in that directory. If dest is a file,
822 822 the source must be a single file.
823 823
824 824 By default, this command copies the contents of files as they
825 825 exist in the working directory. If invoked with -A/--after, the
826 826 operation is recorded, but no copying is performed.
827 827
828 828 This command takes effect with the next commit. To undo a copy
829 829 before that, see :hg:`revert`.
830 830
831 831 Returns 0 on success, 1 if errors are encountered.
832 832 """
833 833 wlock = repo.wlock(False)
834 834 try:
835 835 return cmdutil.copy(ui, repo, pats, opts)
836 836 finally:
837 837 wlock.release()
838 838
839 839 def debugancestor(ui, repo, *args):
840 840 """find the ancestor revision of two revisions in a given index"""
841 841 if len(args) == 3:
842 842 index, rev1, rev2 = args
843 843 r = revlog.revlog(util.opener(os.getcwd(), audit=False), index)
844 844 lookup = r.lookup
845 845 elif len(args) == 2:
846 846 if not repo:
847 847 raise util.Abort(_("There is no Mercurial repository here "
848 848 "(.hg not found)"))
849 849 rev1, rev2 = args
850 850 r = repo.changelog
851 851 lookup = repo.lookup
852 852 else:
853 853 raise util.Abort(_('either two or three arguments required'))
854 854 a = r.ancestor(lookup(rev1), lookup(rev2))
855 855 ui.write("%d:%s\n" % (r.rev(a), hex(a)))
856 856
857 857 def debugbuilddag(ui, repo, text,
858 858 mergeable_file=False,
859 859 appended_file=False,
860 860 overwritten_file=False,
861 861 new_file=False):
862 862 """builds a repo with a given dag from scratch in the current empty repo
863 863
864 864 Elements:
865 865
866 866 - "+n" is a linear run of n nodes based on the current default parent
867 867 - "." is a single node based on the current default parent
868 868 - "$" resets the default parent to null (implied at the start);
869 869 otherwise the default parent is always the last node created
870 870 - "<p" sets the default parent to the backref p
871 871 - "*p" is a fork at parent p, which is a backref
872 872 - "*p1/p2" is a merge of parents p1 and p2, which are backrefs
873 873 - "/p2" is a merge of the preceding node and p2
874 874 - ":tag" defines a local tag for the preceding node
875 875 - "@branch" sets the named branch for subsequent nodes
876 876 - "!command" runs the command using your shell
877 877 - "!!my command\\n" is like "!", but to the end of the line
878 878 - "#...\\n" is a comment up to the end of the line
879 879
880 880 Whitespace between the above elements is ignored.
881 881
882 882 A backref is either
883 883
884 884 - a number n, which references the node curr-n, where curr is the current
885 885 node, or
886 886 - the name of a local tag you placed earlier using ":tag", or
887 887 - empty to denote the default parent.
888 888
889 889 All string valued-elements are either strictly alphanumeric, or must
890 890 be enclosed in double quotes ("..."), with "\" as escape character.
891 891
892 892 Note that the --overwritten-file and --appended-file options imply the
893 893 use of "HGMERGE=internal:local" during DAG buildup.
894 894 """
895 895
896 896 if not (mergeable_file or appended_file or overwritten_file or new_file):
897 897 raise util.Abort(_('need at least one of -m, -a, -o, -n'))
898 898
899 899 if len(repo.changelog) > 0:
900 900 raise util.Abort(_('repository is not empty'))
901 901
902 902 if overwritten_file or appended_file:
903 903 # we don't want to fail in merges during buildup
904 904 os.environ['HGMERGE'] = 'internal:local'
905 905
906 906 def writefile(fname, text, fmode="w"):
907 907 f = open(fname, fmode)
908 908 try:
909 909 f.write(text)
910 910 finally:
911 911 f.close()
912 912
913 913 if mergeable_file:
914 914 linesperrev = 2
915 915 # determine number of revs in DAG
916 916 n = 0
917 917 for type, data in dagparser.parsedag(text):
918 918 if type == 'n':
919 919 n += 1
920 920 # make a file with k lines per rev
921 921 writefile("mf", "\n".join(str(i) for i in xrange(0, n * linesperrev))
922 922 + "\n")
923 923
924 924 at = -1
925 925 atbranch = 'default'
926 926 for type, data in dagparser.parsedag(text):
927 927 if type == 'n':
928 928 ui.status('node %s\n' % str(data))
929 929 id, ps = data
930 930 p1 = ps[0]
931 931 if p1 != at:
932 932 update(ui, repo, node=p1, clean=True)
933 933 at = p1
934 934 if repo.dirstate.branch() != atbranch:
935 935 branch(ui, repo, atbranch, force=True)
936 936 if len(ps) > 1:
937 937 p2 = ps[1]
938 938 merge(ui, repo, node=p2)
939 939
940 940 if mergeable_file:
941 941 f = open("mf", "r+")
942 942 try:
943 943 lines = f.read().split("\n")
944 944 lines[id * linesperrev] += " r%i" % id
945 945 f.seek(0)
946 946 f.write("\n".join(lines))
947 947 finally:
948 948 f.close()
949 949
950 950 if appended_file:
951 951 writefile("af", "r%i\n" % id, "a")
952 952
953 953 if overwritten_file:
954 954 writefile("of", "r%i\n" % id)
955 955
956 956 if new_file:
957 957 writefile("nf%i" % id, "r%i\n" % id)
958 958
959 959 commit(ui, repo, addremove=True, message="r%i" % id, date=(id, 0))
960 960 at = id
961 961 elif type == 'l':
962 962 id, name = data
963 963 ui.status('tag %s\n' % name)
964 964 tag(ui, repo, name, local=True)
965 965 elif type == 'a':
966 966 ui.status('branch %s\n' % data)
967 967 atbranch = data
968 968 elif type in 'cC':
969 969 r = util.system(data, cwd=repo.root)
970 970 if r:
971 971 desc, r = util.explain_exit(r)
972 972 raise util.Abort(_('%s command %s') % (data, desc))
973 973
974 974 def debugcommands(ui, cmd='', *args):
975 975 """list all available commands and options"""
976 976 for cmd, vals in sorted(table.iteritems()):
977 977 cmd = cmd.split('|')[0].strip('^')
978 978 opts = ', '.join([i[1] for i in vals[1]])
979 979 ui.write('%s: %s\n' % (cmd, opts))
980 980
981 981 def debugcomplete(ui, cmd='', **opts):
982 982 """returns the completion list associated with the given command"""
983 983
984 984 if opts.get('options'):
985 985 options = []
986 986 otables = [globalopts]
987 987 if cmd:
988 988 aliases, entry = cmdutil.findcmd(cmd, table, False)
989 989 otables.append(entry[1])
990 990 for t in otables:
991 991 for o in t:
992 992 if "(DEPRECATED)" in o[3]:
993 993 continue
994 994 if o[0]:
995 995 options.append('-%s' % o[0])
996 996 options.append('--%s' % o[1])
997 997 ui.write("%s\n" % "\n".join(options))
998 998 return
999 999
1000 1000 cmdlist = cmdutil.findpossible(cmd, table)
1001 1001 if ui.verbose:
1002 1002 cmdlist = [' '.join(c[0]) for c in cmdlist.values()]
1003 1003 ui.write("%s\n" % "\n".join(sorted(cmdlist)))
1004 1004
1005 1005 def debugfsinfo(ui, path = "."):
1006 1006 """show information detected about current filesystem"""
1007 1007 open('.debugfsinfo', 'w').write('')
1008 1008 ui.write('exec: %s\n' % (util.checkexec(path) and 'yes' or 'no'))
1009 1009 ui.write('symlink: %s\n' % (util.checklink(path) and 'yes' or 'no'))
1010 1010 ui.write('case-sensitive: %s\n' % (util.checkcase('.debugfsinfo')
1011 1011 and 'yes' or 'no'))
1012 1012 os.unlink('.debugfsinfo')
1013 1013
1014 1014 def debugrebuildstate(ui, repo, rev="tip"):
1015 1015 """rebuild the dirstate as it would look like for the given revision"""
1016 1016 ctx = repo[rev]
1017 1017 wlock = repo.wlock()
1018 1018 try:
1019 1019 repo.dirstate.rebuild(ctx.node(), ctx.manifest())
1020 1020 finally:
1021 1021 wlock.release()
1022 1022
1023 1023 def debugcheckstate(ui, repo):
1024 1024 """validate the correctness of the current dirstate"""
1025 1025 parent1, parent2 = repo.dirstate.parents()
1026 1026 m1 = repo[parent1].manifest()
1027 1027 m2 = repo[parent2].manifest()
1028 1028 errors = 0
1029 1029 for f in repo.dirstate:
1030 1030 state = repo.dirstate[f]
1031 1031 if state in "nr" and f not in m1:
1032 1032 ui.warn(_("%s in state %s, but not in manifest1\n") % (f, state))
1033 1033 errors += 1
1034 1034 if state in "a" and f in m1:
1035 1035 ui.warn(_("%s in state %s, but also in manifest1\n") % (f, state))
1036 1036 errors += 1
1037 1037 if state in "m" and f not in m1 and f not in m2:
1038 1038 ui.warn(_("%s in state %s, but not in either manifest\n") %
1039 1039 (f, state))
1040 1040 errors += 1
1041 1041 for f in m1:
1042 1042 state = repo.dirstate[f]
1043 1043 if state not in "nrm":
1044 1044 ui.warn(_("%s in manifest1, but listed as state %s") % (f, state))
1045 1045 errors += 1
1046 1046 if errors:
1047 1047 error = _(".hg/dirstate inconsistent with current parent's manifest")
1048 1048 raise util.Abort(error)
1049 1049
1050 1050 def showconfig(ui, repo, *values, **opts):
1051 1051 """show combined config settings from all hgrc files
1052 1052
1053 1053 With no arguments, print names and values of all config items.
1054 1054
1055 1055 With one argument of the form section.name, print just the value
1056 1056 of that config item.
1057 1057
1058 1058 With multiple arguments, print names and values of all config
1059 1059 items with matching section names.
1060 1060
1061 1061 With --debug, the source (filename and line number) is printed
1062 1062 for each config item.
1063 1063
1064 1064 Returns 0 on success.
1065 1065 """
1066 1066
1067 1067 for f in util.rcpath():
1068 1068 ui.debug(_('read config from: %s\n') % f)
1069 1069 untrusted = bool(opts.get('untrusted'))
1070 1070 if values:
1071 1071 if len([v for v in values if '.' in v]) > 1:
1072 1072 raise util.Abort(_('only one config item permitted'))
1073 1073 for section, name, value in ui.walkconfig(untrusted=untrusted):
1074 1074 sectname = section + '.' + name
1075 1075 if values:
1076 1076 for v in values:
1077 1077 if v == section:
1078 1078 ui.debug('%s: ' %
1079 1079 ui.configsource(section, name, untrusted))
1080 1080 ui.write('%s=%s\n' % (sectname, value))
1081 1081 elif v == sectname:
1082 1082 ui.debug('%s: ' %
1083 1083 ui.configsource(section, name, untrusted))
1084 1084 ui.write(value, '\n')
1085 1085 else:
1086 1086 ui.debug('%s: ' %
1087 1087 ui.configsource(section, name, untrusted))
1088 1088 ui.write('%s=%s\n' % (sectname, value))
1089 1089
1090 1090 def debugpushkey(ui, repopath, namespace, *keyinfo):
1091 1091 '''access the pushkey key/value protocol
1092 1092
1093 1093 With two args, list the keys in the given namespace.
1094 1094
1095 1095 With five args, set a key to new if it currently is set to old.
1096 1096 Reports success or failure.
1097 1097 '''
1098 1098
1099 1099 target = hg.repository(ui, repopath)
1100 1100 if keyinfo:
1101 1101 key, old, new = keyinfo
1102 1102 r = target.pushkey(namespace, key, old, new)
1103 1103 ui.status(str(r) + '\n')
1104 1104 return not(r)
1105 1105 else:
1106 1106 for k, v in target.listkeys(namespace).iteritems():
1107 1107 ui.write("%s\t%s\n" % (k.encode('string-escape'),
1108 1108 v.encode('string-escape')))
1109 1109
1110 1110 def debugrevspec(ui, repo, expr):
1111 1111 '''parse and apply a revision specification'''
1112 1112 if ui.verbose:
1113 1113 tree = revset.parse(expr)
1114 1114 ui.note(tree, "\n")
1115 1115 func = revset.match(expr)
1116 1116 for c in func(repo, range(len(repo))):
1117 1117 ui.write("%s\n" % c)
1118 1118
1119 1119 def debugsetparents(ui, repo, rev1, rev2=None):
1120 1120 """manually set the parents of the current working directory
1121 1121
1122 1122 This is useful for writing repository conversion tools, but should
1123 1123 be used with care.
1124 1124
1125 1125 Returns 0 on success.
1126 1126 """
1127 1127
1128 1128 if not rev2:
1129 1129 rev2 = hex(nullid)
1130 1130
1131 1131 wlock = repo.wlock()
1132 1132 try:
1133 1133 repo.dirstate.setparents(repo.lookup(rev1), repo.lookup(rev2))
1134 1134 finally:
1135 1135 wlock.release()
1136 1136
1137 1137 def debugstate(ui, repo, nodates=None):
1138 1138 """show the contents of the current dirstate"""
1139 1139 timestr = ""
1140 1140 showdate = not nodates
1141 1141 for file_, ent in sorted(repo.dirstate._map.iteritems()):
1142 1142 if showdate:
1143 1143 if ent[3] == -1:
1144 1144 # Pad or slice to locale representation
1145 1145 locale_len = len(time.strftime("%Y-%m-%d %H:%M:%S ",
1146 1146 time.localtime(0)))
1147 1147 timestr = 'unset'
1148 1148 timestr = (timestr[:locale_len] +
1149 1149 ' ' * (locale_len - len(timestr)))
1150 1150 else:
1151 1151 timestr = time.strftime("%Y-%m-%d %H:%M:%S ",
1152 1152 time.localtime(ent[3]))
1153 1153 if ent[1] & 020000:
1154 1154 mode = 'lnk'
1155 1155 else:
1156 1156 mode = '%3o' % (ent[1] & 0777)
1157 1157 ui.write("%c %s %10d %s%s\n" % (ent[0], mode, ent[2], timestr, file_))
1158 1158 for f in repo.dirstate.copies():
1159 1159 ui.write(_("copy: %s -> %s\n") % (repo.dirstate.copied(f), f))
1160 1160
1161 1161 def debugsub(ui, repo, rev=None):
1162 1162 if rev == '':
1163 1163 rev = None
1164 1164 for k, v in sorted(repo[rev].substate.items()):
1165 1165 ui.write('path %s\n' % k)
1166 1166 ui.write(' source %s\n' % v[0])
1167 1167 ui.write(' revision %s\n' % v[1])
1168 1168
1169 1169 def debugdag(ui, repo, file_=None, *revs, **opts):
1170 1170 """format the changelog or an index DAG as a concise textual description
1171 1171
1172 1172 If you pass a revlog index, the revlog's DAG is emitted. If you list
1173 1173 revision numbers, they get labelled in the output as rN.
1174 1174
1175 1175 Otherwise, the changelog DAG of the current repo is emitted.
1176 1176 """
1177 1177 spaces = opts.get('spaces')
1178 1178 dots = opts.get('dots')
1179 1179 if file_:
1180 1180 rlog = revlog.revlog(util.opener(os.getcwd(), audit=False), file_)
1181 1181 revs = set((int(r) for r in revs))
1182 1182 def events():
1183 1183 for r in rlog:
1184 1184 yield 'n', (r, list(set(p for p in rlog.parentrevs(r) if p != -1)))
1185 1185 if r in revs:
1186 1186 yield 'l', (r, "r%i" % r)
1187 1187 elif repo:
1188 1188 cl = repo.changelog
1189 1189 tags = opts.get('tags')
1190 1190 branches = opts.get('branches')
1191 1191 if tags:
1192 1192 labels = {}
1193 1193 for l, n in repo.tags().items():
1194 1194 labels.setdefault(cl.rev(n), []).append(l)
1195 1195 def events():
1196 1196 b = "default"
1197 1197 for r in cl:
1198 1198 if branches:
1199 1199 newb = cl.read(cl.node(r))[5]['branch']
1200 1200 if newb != b:
1201 1201 yield 'a', newb
1202 1202 b = newb
1203 1203 yield 'n', (r, list(set(p for p in cl.parentrevs(r) if p != -1)))
1204 1204 if tags:
1205 1205 ls = labels.get(r)
1206 1206 if ls:
1207 1207 for l in ls:
1208 1208 yield 'l', (r, l)
1209 1209 else:
1210 1210 raise util.Abort(_('need repo for changelog dag'))
1211 1211
1212 1212 for line in dagparser.dagtextlines(events(),
1213 1213 addspaces=spaces,
1214 1214 wraplabels=True,
1215 1215 wrapannotations=True,
1216 1216 wrapnonlinear=dots,
1217 1217 usedots=dots,
1218 1218 maxlinewidth=70):
1219 1219 ui.write(line)
1220 1220 ui.write("\n")
1221 1221
1222 1222 def debugdata(ui, file_, rev):
1223 1223 """dump the contents of a data file revision"""
1224 1224 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_[:-2] + ".i")
1225 1225 try:
1226 1226 ui.write(r.revision(r.lookup(rev)))
1227 1227 except KeyError:
1228 1228 raise util.Abort(_('invalid revision identifier %s') % rev)
1229 1229
1230 1230 def debugdate(ui, date, range=None, **opts):
1231 1231 """parse and display a date"""
1232 1232 if opts["extended"]:
1233 1233 d = util.parsedate(date, util.extendeddateformats)
1234 1234 else:
1235 1235 d = util.parsedate(date)
1236 1236 ui.write("internal: %s %s\n" % d)
1237 1237 ui.write("standard: %s\n" % util.datestr(d))
1238 1238 if range:
1239 1239 m = util.matchdate(range)
1240 1240 ui.write("match: %s\n" % m(d[0]))
1241 1241
1242 1242 def debugindex(ui, file_):
1243 1243 """dump the contents of an index file"""
1244 1244 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_)
1245 1245 ui.write(" rev offset length base linkrev"
1246 1246 " nodeid p1 p2\n")
1247 1247 for i in r:
1248 1248 node = r.node(i)
1249 1249 try:
1250 1250 pp = r.parents(node)
1251 1251 except:
1252 1252 pp = [nullid, nullid]
1253 1253 ui.write("% 6d % 9d % 7d % 6d % 7d %s %s %s\n" % (
1254 1254 i, r.start(i), r.length(i), r.base(i), r.linkrev(i),
1255 1255 short(node), short(pp[0]), short(pp[1])))
1256 1256
1257 1257 def debugindexdot(ui, file_):
1258 1258 """dump an index DAG as a graphviz dot file"""
1259 1259 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_)
1260 1260 ui.write("digraph G {\n")
1261 1261 for i in r:
1262 1262 node = r.node(i)
1263 1263 pp = r.parents(node)
1264 1264 ui.write("\t%d -> %d\n" % (r.rev(pp[0]), i))
1265 1265 if pp[1] != nullid:
1266 1266 ui.write("\t%d -> %d\n" % (r.rev(pp[1]), i))
1267 1267 ui.write("}\n")
1268 1268
1269 1269 def debuginstall(ui):
1270 1270 '''test Mercurial installation
1271 1271
1272 1272 Returns 0 on success.
1273 1273 '''
1274 1274
1275 1275 def writetemp(contents):
1276 1276 (fd, name) = tempfile.mkstemp(prefix="hg-debuginstall-")
1277 1277 f = os.fdopen(fd, "wb")
1278 1278 f.write(contents)
1279 1279 f.close()
1280 1280 return name
1281 1281
1282 1282 problems = 0
1283 1283
1284 1284 # encoding
1285 1285 ui.status(_("Checking encoding (%s)...\n") % encoding.encoding)
1286 1286 try:
1287 1287 encoding.fromlocal("test")
1288 1288 except util.Abort, inst:
1289 1289 ui.write(" %s\n" % inst)
1290 1290 ui.write(_(" (check that your locale is properly set)\n"))
1291 1291 problems += 1
1292 1292
1293 1293 # compiled modules
1294 1294 ui.status(_("Checking extensions...\n"))
1295 1295 try:
1296 1296 import bdiff, mpatch, base85
1297 1297 except Exception, inst:
1298 1298 ui.write(" %s\n" % inst)
1299 1299 ui.write(_(" One or more extensions could not be found"))
1300 1300 ui.write(_(" (check that you compiled the extensions)\n"))
1301 1301 problems += 1
1302 1302
1303 1303 # templates
1304 1304 ui.status(_("Checking templates...\n"))
1305 1305 try:
1306 1306 import templater
1307 1307 templater.templater(templater.templatepath("map-cmdline.default"))
1308 1308 except Exception, inst:
1309 1309 ui.write(" %s\n" % inst)
1310 1310 ui.write(_(" (templates seem to have been installed incorrectly)\n"))
1311 1311 problems += 1
1312 1312
1313 1313 # patch
1314 1314 ui.status(_("Checking patch...\n"))
1315 1315 patchproblems = 0
1316 1316 a = "1\n2\n3\n4\n"
1317 1317 b = "1\n2\n3\ninsert\n4\n"
1318 1318 fa = writetemp(a)
1319 1319 d = mdiff.unidiff(a, None, b, None, os.path.basename(fa),
1320 1320 os.path.basename(fa))
1321 1321 fd = writetemp(d)
1322 1322
1323 1323 files = {}
1324 1324 try:
1325 1325 patch.patch(fd, ui, cwd=os.path.dirname(fa), files=files)
1326 1326 except util.Abort, e:
1327 1327 ui.write(_(" patch call failed:\n"))
1328 1328 ui.write(" " + str(e) + "\n")
1329 1329 patchproblems += 1
1330 1330 else:
1331 1331 if list(files) != [os.path.basename(fa)]:
1332 1332 ui.write(_(" unexpected patch output!\n"))
1333 1333 patchproblems += 1
1334 1334 a = open(fa).read()
1335 1335 if a != b:
1336 1336 ui.write(_(" patch test failed!\n"))
1337 1337 patchproblems += 1
1338 1338
1339 1339 if patchproblems:
1340 1340 if ui.config('ui', 'patch'):
1341 1341 ui.write(_(" (Current patch tool may be incompatible with patch,"
1342 1342 " or misconfigured. Please check your .hgrc file)\n"))
1343 1343 else:
1344 1344 ui.write(_(" Internal patcher failure, please report this error"
1345 1345 " to http://mercurial.selenic.com/bts/\n"))
1346 1346 problems += patchproblems
1347 1347
1348 1348 os.unlink(fa)
1349 1349 os.unlink(fd)
1350 1350
1351 1351 # editor
1352 1352 ui.status(_("Checking commit editor...\n"))
1353 1353 editor = ui.geteditor()
1354 1354 cmdpath = util.find_exe(editor) or util.find_exe(editor.split()[0])
1355 1355 if not cmdpath:
1356 1356 if editor == 'vi':
1357 1357 ui.write(_(" No commit editor set and can't find vi in PATH\n"))
1358 1358 ui.write(_(" (specify a commit editor in your .hgrc file)\n"))
1359 1359 else:
1360 1360 ui.write(_(" Can't find editor '%s' in PATH\n") % editor)
1361 1361 ui.write(_(" (specify a commit editor in your .hgrc file)\n"))
1362 1362 problems += 1
1363 1363
1364 1364 # check username
1365 1365 ui.status(_("Checking username...\n"))
1366 1366 try:
1367 1367 user = ui.username()
1368 1368 except util.Abort, e:
1369 1369 ui.write(" %s\n" % e)
1370 1370 ui.write(_(" (specify a username in your .hgrc file)\n"))
1371 1371 problems += 1
1372 1372
1373 1373 if not problems:
1374 1374 ui.status(_("No problems detected\n"))
1375 1375 else:
1376 1376 ui.write(_("%s problems detected,"
1377 1377 " please check your install!\n") % problems)
1378 1378
1379 1379 return problems
1380 1380
1381 1381 def debugrename(ui, repo, file1, *pats, **opts):
1382 1382 """dump rename information"""
1383 1383
1384 1384 ctx = repo[opts.get('rev')]
1385 1385 m = cmdutil.match(repo, (file1,) + pats, opts)
1386 1386 for abs in ctx.walk(m):
1387 1387 fctx = ctx[abs]
1388 1388 o = fctx.filelog().renamed(fctx.filenode())
1389 1389 rel = m.rel(abs)
1390 1390 if o:
1391 1391 ui.write(_("%s renamed from %s:%s\n") % (rel, o[0], hex(o[1])))
1392 1392 else:
1393 1393 ui.write(_("%s not renamed\n") % rel)
1394 1394
1395 1395 def debugwalk(ui, repo, *pats, **opts):
1396 1396 """show how files match on given patterns"""
1397 1397 m = cmdutil.match(repo, pats, opts)
1398 1398 items = list(repo.walk(m))
1399 1399 if not items:
1400 1400 return
1401 1401 fmt = 'f %%-%ds %%-%ds %%s' % (
1402 1402 max([len(abs) for abs in items]),
1403 1403 max([len(m.rel(abs)) for abs in items]))
1404 1404 for abs in items:
1405 1405 line = fmt % (abs, m.rel(abs), m.exact(abs) and 'exact' or '')
1406 1406 ui.write("%s\n" % line.rstrip())
1407 1407
1408 1408 def diff(ui, repo, *pats, **opts):
1409 1409 """diff repository (or selected files)
1410 1410
1411 1411 Show differences between revisions for the specified files.
1412 1412
1413 1413 Differences between files are shown using the unified diff format.
1414 1414
1415 1415 NOTE: diff may generate unexpected results for merges, as it will
1416 1416 default to comparing against the working directory's first parent
1417 1417 changeset if no revisions are specified.
1418 1418
1419 1419 When two revision arguments are given, then changes are shown
1420 1420 between those revisions. If only one revision is specified then
1421 1421 that revision is compared to the working directory, and, when no
1422 1422 revisions are specified, the working directory files are compared
1423 1423 to its parent.
1424 1424
1425 1425 Alternatively you can specify -c/--change with a revision to see
1426 1426 the changes in that changeset relative to its first parent.
1427 1427
1428 1428 Without the -a/--text option, diff will avoid generating diffs of
1429 1429 files it detects as binary. With -a, diff will generate a diff
1430 1430 anyway, probably with undesirable results.
1431 1431
1432 1432 Use the -g/--git option to generate diffs in the git extended diff
1433 1433 format. For more information, read :hg:`help diffs`.
1434 1434
1435 1435 Returns 0 on success.
1436 1436 """
1437 1437
1438 1438 revs = opts.get('rev')
1439 1439 change = opts.get('change')
1440 1440 stat = opts.get('stat')
1441 1441 reverse = opts.get('reverse')
1442 1442
1443 1443 if revs and change:
1444 1444 msg = _('cannot specify --rev and --change at the same time')
1445 1445 raise util.Abort(msg)
1446 1446 elif change:
1447 1447 node2 = repo.lookup(change)
1448 1448 node1 = repo[node2].parents()[0].node()
1449 1449 else:
1450 1450 node1, node2 = cmdutil.revpair(repo, revs)
1451 1451
1452 1452 if reverse:
1453 1453 node1, node2 = node2, node1
1454 1454
1455 1455 diffopts = patch.diffopts(ui, opts)
1456 1456 m = cmdutil.match(repo, pats, opts)
1457 1457 cmdutil.diffordiffstat(ui, repo, diffopts, node1, node2, m, stat=stat)
1458 1458
1459 1459 def export(ui, repo, *changesets, **opts):
1460 1460 """dump the header and diffs for one or more changesets
1461 1461
1462 1462 Print the changeset header and diffs for one or more revisions.
1463 1463
1464 1464 The information shown in the changeset header is: author, date,
1465 1465 branch name (if non-default), changeset hash, parent(s) and commit
1466 1466 comment.
1467 1467
1468 1468 NOTE: export may generate unexpected diff output for merge
1469 1469 changesets, as it will compare the merge changeset against its
1470 1470 first parent only.
1471 1471
1472 1472 Output may be to a file, in which case the name of the file is
1473 1473 given using a format string. The formatting rules are as follows:
1474 1474
1475 1475 :``%%``: literal "%" character
1476 1476 :``%H``: changeset hash (40 bytes of hexadecimal)
1477 1477 :``%N``: number of patches being generated
1478 1478 :``%R``: changeset revision number
1479 1479 :``%b``: basename of the exporting repository
1480 1480 :``%h``: short-form changeset hash (12 bytes of hexadecimal)
1481 1481 :``%n``: zero-padded sequence number, starting at 1
1482 1482 :``%r``: zero-padded changeset revision number
1483 1483
1484 1484 Without the -a/--text option, export will avoid generating diffs
1485 1485 of files it detects as binary. With -a, export will generate a
1486 1486 diff anyway, probably with undesirable results.
1487 1487
1488 1488 Use the -g/--git option to generate diffs in the git extended diff
1489 1489 format. See :hg:`help diffs` for more information.
1490 1490
1491 1491 With the --switch-parent option, the diff will be against the
1492 1492 second parent. It can be useful to review a merge.
1493 1493
1494 1494 Returns 0 on success.
1495 1495 """
1496 1496 changesets += tuple(opts.get('rev', []))
1497 1497 if not changesets:
1498 1498 raise util.Abort(_("export requires at least one changeset"))
1499 1499 revs = cmdutil.revrange(repo, changesets)
1500 1500 if len(revs) > 1:
1501 1501 ui.note(_('exporting patches:\n'))
1502 1502 else:
1503 1503 ui.note(_('exporting patch:\n'))
1504 1504 cmdutil.export(repo, revs, template=opts.get('output'),
1505 1505 switch_parent=opts.get('switch_parent'),
1506 1506 opts=patch.diffopts(ui, opts))
1507 1507
1508 1508 def forget(ui, repo, *pats, **opts):
1509 1509 """forget the specified files on the next commit
1510 1510
1511 1511 Mark the specified files so they will no longer be tracked
1512 1512 after the next commit.
1513 1513
1514 1514 This only removes files from the current branch, not from the
1515 1515 entire project history, and it does not delete them from the
1516 1516 working directory.
1517 1517
1518 1518 To undo a forget before the next commit, see :hg:`add`.
1519 1519
1520 1520 Returns 0 on success.
1521 1521 """
1522 1522
1523 1523 if not pats:
1524 1524 raise util.Abort(_('no files specified'))
1525 1525
1526 1526 m = cmdutil.match(repo, pats, opts)
1527 1527 s = repo.status(match=m, clean=True)
1528 1528 forget = sorted(s[0] + s[1] + s[3] + s[6])
1529 1529 errs = 0
1530 1530
1531 1531 for f in m.files():
1532 1532 if f not in repo.dirstate and not os.path.isdir(m.rel(f)):
1533 1533 ui.warn(_('not removing %s: file is already untracked\n')
1534 1534 % m.rel(f))
1535 1535 errs = 1
1536 1536
1537 1537 for f in forget:
1538 1538 if ui.verbose or not m.exact(f):
1539 1539 ui.status(_('removing %s\n') % m.rel(f))
1540 1540
1541 1541 repo[None].remove(forget, unlink=False)
1542 1542 return errs
1543 1543
1544 1544 def grep(ui, repo, pattern, *pats, **opts):
1545 1545 """search for a pattern in specified files and revisions
1546 1546
1547 1547 Search revisions of files for a regular expression.
1548 1548
1549 1549 This command behaves differently than Unix grep. It only accepts
1550 1550 Python/Perl regexps. It searches repository history, not the
1551 1551 working directory. It always prints the revision number in which a
1552 1552 match appears.
1553 1553
1554 1554 By default, grep only prints output for the first revision of a
1555 1555 file in which it finds a match. To get it to print every revision
1556 1556 that contains a change in match status ("-" for a match that
1557 1557 becomes a non-match, or "+" for a non-match that becomes a match),
1558 1558 use the --all flag.
1559 1559
1560 1560 Returns 0 if a match is found, 1 otherwise.
1561 1561 """
1562 1562 reflags = 0
1563 1563 if opts.get('ignore_case'):
1564 1564 reflags |= re.I
1565 1565 try:
1566 1566 regexp = re.compile(pattern, reflags)
1567 1567 except Exception, inst:
1568 1568 ui.warn(_("grep: invalid match pattern: %s\n") % inst)
1569 1569 return 1
1570 1570 sep, eol = ':', '\n'
1571 1571 if opts.get('print0'):
1572 1572 sep = eol = '\0'
1573 1573
1574 1574 getfile = util.lrucachefunc(repo.file)
1575 1575
1576 1576 def matchlines(body):
1577 1577 begin = 0
1578 1578 linenum = 0
1579 1579 while True:
1580 1580 match = regexp.search(body, begin)
1581 1581 if not match:
1582 1582 break
1583 1583 mstart, mend = match.span()
1584 1584 linenum += body.count('\n', begin, mstart) + 1
1585 1585 lstart = body.rfind('\n', begin, mstart) + 1 or begin
1586 1586 begin = body.find('\n', mend) + 1 or len(body)
1587 1587 lend = begin - 1
1588 1588 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
1589 1589
1590 1590 class linestate(object):
1591 1591 def __init__(self, line, linenum, colstart, colend):
1592 1592 self.line = line
1593 1593 self.linenum = linenum
1594 1594 self.colstart = colstart
1595 1595 self.colend = colend
1596 1596
1597 1597 def __hash__(self):
1598 1598 return hash((self.linenum, self.line))
1599 1599
1600 1600 def __eq__(self, other):
1601 1601 return self.line == other.line
1602 1602
1603 1603 matches = {}
1604 1604 copies = {}
1605 1605 def grepbody(fn, rev, body):
1606 1606 matches[rev].setdefault(fn, [])
1607 1607 m = matches[rev][fn]
1608 1608 for lnum, cstart, cend, line in matchlines(body):
1609 1609 s = linestate(line, lnum, cstart, cend)
1610 1610 m.append(s)
1611 1611
1612 1612 def difflinestates(a, b):
1613 1613 sm = difflib.SequenceMatcher(None, a, b)
1614 1614 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
1615 1615 if tag == 'insert':
1616 1616 for i in xrange(blo, bhi):
1617 1617 yield ('+', b[i])
1618 1618 elif tag == 'delete':
1619 1619 for i in xrange(alo, ahi):
1620 1620 yield ('-', a[i])
1621 1621 elif tag == 'replace':
1622 1622 for i in xrange(alo, ahi):
1623 1623 yield ('-', a[i])
1624 1624 for i in xrange(blo, bhi):
1625 1625 yield ('+', b[i])
1626 1626
1627 1627 def display(fn, ctx, pstates, states):
1628 1628 rev = ctx.rev()
1629 1629 datefunc = ui.quiet and util.shortdate or util.datestr
1630 1630 found = False
1631 1631 filerevmatches = {}
1632 1632 if opts.get('all'):
1633 1633 iter = difflinestates(pstates, states)
1634 1634 else:
1635 1635 iter = [('', l) for l in states]
1636 1636 for change, l in iter:
1637 1637 cols = [fn, str(rev)]
1638 1638 before, match, after = None, None, None
1639 1639 if opts.get('line_number'):
1640 1640 cols.append(str(l.linenum))
1641 1641 if opts.get('all'):
1642 1642 cols.append(change)
1643 1643 if opts.get('user'):
1644 1644 cols.append(ui.shortuser(ctx.user()))
1645 1645 if opts.get('date'):
1646 1646 cols.append(datefunc(ctx.date()))
1647 1647 if opts.get('files_with_matches'):
1648 1648 c = (fn, rev)
1649 1649 if c in filerevmatches:
1650 1650 continue
1651 1651 filerevmatches[c] = 1
1652 1652 else:
1653 1653 before = l.line[:l.colstart]
1654 1654 match = l.line[l.colstart:l.colend]
1655 1655 after = l.line[l.colend:]
1656 1656 ui.write(sep.join(cols))
1657 1657 if before is not None:
1658 1658 ui.write(sep + before)
1659 1659 ui.write(match, label='grep.match')
1660 1660 ui.write(after)
1661 1661 ui.write(eol)
1662 1662 found = True
1663 1663 return found
1664 1664
1665 1665 skip = {}
1666 1666 revfiles = {}
1667 1667 matchfn = cmdutil.match(repo, pats, opts)
1668 1668 found = False
1669 1669 follow = opts.get('follow')
1670 1670
1671 1671 def prep(ctx, fns):
1672 1672 rev = ctx.rev()
1673 1673 pctx = ctx.parents()[0]
1674 1674 parent = pctx.rev()
1675 1675 matches.setdefault(rev, {})
1676 1676 matches.setdefault(parent, {})
1677 1677 files = revfiles.setdefault(rev, [])
1678 1678 for fn in fns:
1679 1679 flog = getfile(fn)
1680 1680 try:
1681 1681 fnode = ctx.filenode(fn)
1682 1682 except error.LookupError:
1683 1683 continue
1684 1684
1685 1685 copied = flog.renamed(fnode)
1686 1686 copy = follow and copied and copied[0]
1687 1687 if copy:
1688 1688 copies.setdefault(rev, {})[fn] = copy
1689 1689 if fn in skip:
1690 1690 if copy:
1691 1691 skip[copy] = True
1692 1692 continue
1693 1693 files.append(fn)
1694 1694
1695 1695 if fn not in matches[rev]:
1696 1696 grepbody(fn, rev, flog.read(fnode))
1697 1697
1698 1698 pfn = copy or fn
1699 1699 if pfn not in matches[parent]:
1700 1700 try:
1701 1701 fnode = pctx.filenode(pfn)
1702 1702 grepbody(pfn, parent, flog.read(fnode))
1703 1703 except error.LookupError:
1704 1704 pass
1705 1705
1706 1706 for ctx in cmdutil.walkchangerevs(repo, matchfn, opts, prep):
1707 1707 rev = ctx.rev()
1708 1708 parent = ctx.parents()[0].rev()
1709 1709 for fn in sorted(revfiles.get(rev, [])):
1710 1710 states = matches[rev][fn]
1711 1711 copy = copies.get(rev, {}).get(fn)
1712 1712 if fn in skip:
1713 1713 if copy:
1714 1714 skip[copy] = True
1715 1715 continue
1716 1716 pstates = matches.get(parent, {}).get(copy or fn, [])
1717 1717 if pstates or states:
1718 1718 r = display(fn, ctx, pstates, states)
1719 1719 found = found or r
1720 1720 if r and not opts.get('all'):
1721 1721 skip[fn] = True
1722 1722 if copy:
1723 1723 skip[copy] = True
1724 1724 del matches[rev]
1725 1725 del revfiles[rev]
1726 1726
1727 1727 return not found
1728 1728
1729 1729 def heads(ui, repo, *branchrevs, **opts):
1730 1730 """show current repository heads or show branch heads
1731 1731
1732 1732 With no arguments, show all repository branch heads.
1733 1733
1734 1734 Repository "heads" are changesets with no child changesets. They are
1735 1735 where development generally takes place and are the usual targets
1736 1736 for update and merge operations. Branch heads are changesets that have
1737 1737 no child changeset on the same branch.
1738 1738
1739 1739 If one or more REVs are given, only branch heads on the branches
1740 1740 associated with the specified changesets are shown.
1741 1741
1742 1742 If -c/--closed is specified, also show branch heads marked closed
1743 1743 (see :hg:`commit --close-branch`).
1744 1744
1745 1745 If STARTREV is specified, only those heads that are descendants of
1746 1746 STARTREV will be displayed.
1747 1747
1748 1748 If -t/--topo is specified, named branch mechanics will be ignored and only
1749 1749 changesets without children will be shown.
1750 1750
1751 1751 Returns 0 if matching heads are found, 1 if not.
1752 1752 """
1753 1753
1754 1754 if opts.get('rev'):
1755 1755 start = repo.lookup(opts['rev'])
1756 1756 else:
1757 1757 start = None
1758 1758
1759 1759 if opts.get('topo'):
1760 1760 heads = [repo[h] for h in repo.heads(start)]
1761 1761 else:
1762 1762 heads = []
1763 1763 for b, ls in repo.branchmap().iteritems():
1764 1764 if start is None:
1765 1765 heads += [repo[h] for h in ls]
1766 1766 continue
1767 1767 startrev = repo.changelog.rev(start)
1768 1768 descendants = set(repo.changelog.descendants(startrev))
1769 1769 descendants.add(startrev)
1770 1770 rev = repo.changelog.rev
1771 1771 heads += [repo[h] for h in ls if rev(h) in descendants]
1772 1772
1773 1773 if branchrevs:
1774 1774 decode, encode = encoding.fromlocal, encoding.tolocal
1775 1775 branches = set(repo[decode(br)].branch() for br in branchrevs)
1776 1776 heads = [h for h in heads if h.branch() in branches]
1777 1777
1778 1778 if not opts.get('closed'):
1779 1779 heads = [h for h in heads if not h.extra().get('close')]
1780 1780
1781 1781 if opts.get('active') and branchrevs:
1782 1782 dagheads = repo.heads(start)
1783 1783 heads = [h for h in heads if h.node() in dagheads]
1784 1784
1785 1785 if branchrevs:
1786 1786 haveheads = set(h.branch() for h in heads)
1787 1787 if branches - haveheads:
1788 1788 headless = ', '.join(encode(b) for b in branches - haveheads)
1789 1789 msg = _('no open branch heads found on branches %s')
1790 1790 if opts.get('rev'):
1791 1791 msg += _(' (started at %s)' % opts['rev'])
1792 1792 ui.warn((msg + '\n') % headless)
1793 1793
1794 1794 if not heads:
1795 1795 return 1
1796 1796
1797 1797 heads = sorted(heads, key=lambda x: -x.rev())
1798 1798 displayer = cmdutil.show_changeset(ui, repo, opts)
1799 1799 for ctx in heads:
1800 1800 displayer.show(ctx)
1801 1801 displayer.close()
1802 1802
1803 1803 def help_(ui, name=None, with_version=False, unknowncmd=False):
1804 1804 """show help for a given topic or a help overview
1805 1805
1806 1806 With no arguments, print a list of commands with short help messages.
1807 1807
1808 1808 Given a topic, extension, or command name, print help for that
1809 1809 topic.
1810 1810
1811 1811 Returns 0 if successful.
1812 1812 """
1813 1813 option_lists = []
1814 1814 textwidth = util.termwidth() - 2
1815 1815
1816 1816 def addglobalopts(aliases):
1817 1817 if ui.verbose:
1818 1818 option_lists.append((_("global options:"), globalopts))
1819 1819 if name == 'shortlist':
1820 1820 option_lists.append((_('use "hg help" for the full list '
1821 1821 'of commands'), ()))
1822 1822 else:
1823 1823 if name == 'shortlist':
1824 1824 msg = _('use "hg help" for the full list of commands '
1825 1825 'or "hg -v" for details')
1826 1826 elif aliases:
1827 1827 msg = _('use "hg -v help%s" to show aliases and '
1828 1828 'global options') % (name and " " + name or "")
1829 1829 else:
1830 1830 msg = _('use "hg -v help %s" to show global options') % name
1831 1831 option_lists.append((msg, ()))
1832 1832
1833 1833 def helpcmd(name):
1834 1834 if with_version:
1835 1835 version_(ui)
1836 1836 ui.write('\n')
1837 1837
1838 1838 try:
1839 1839 aliases, entry = cmdutil.findcmd(name, table, strict=unknowncmd)
1840 1840 except error.AmbiguousCommand, inst:
1841 1841 # py3k fix: except vars can't be used outside the scope of the
1842 1842 # except block, nor can be used inside a lambda. python issue4617
1843 1843 prefix = inst.args[0]
1844 1844 select = lambda c: c.lstrip('^').startswith(prefix)
1845 1845 helplist(_('list of commands:\n\n'), select)
1846 1846 return
1847 1847
1848 1848 # check if it's an invalid alias and display its error if it is
1849 1849 if getattr(entry[0], 'badalias', False):
1850 1850 if not unknowncmd:
1851 1851 entry[0](ui)
1852 1852 return
1853 1853
1854 1854 # synopsis
1855 1855 if len(entry) > 2:
1856 1856 if entry[2].startswith('hg'):
1857 1857 ui.write("%s\n" % entry[2])
1858 1858 else:
1859 1859 ui.write('hg %s %s\n' % (aliases[0], entry[2]))
1860 1860 else:
1861 1861 ui.write('hg %s\n' % aliases[0])
1862 1862
1863 1863 # aliases
1864 1864 if not ui.quiet and len(aliases) > 1:
1865 1865 ui.write(_("\naliases: %s\n") % ', '.join(aliases[1:]))
1866 1866
1867 1867 # description
1868 1868 doc = gettext(entry[0].__doc__)
1869 1869 if not doc:
1870 1870 doc = _("(no help text available)")
1871 1871 if hasattr(entry[0], 'definition'): # aliased command
1872 1872 doc = _('alias for: hg %s\n\n%s') % (entry[0].definition, doc)
1873 1873 if ui.quiet:
1874 1874 doc = doc.splitlines()[0]
1875 1875 keep = ui.verbose and ['verbose'] or []
1876 1876 formatted, pruned = minirst.format(doc, textwidth, keep=keep)
1877 1877 ui.write("\n%s\n" % formatted)
1878 1878 if pruned:
1879 1879 ui.write(_('\nuse "hg -v help %s" to show verbose help\n') % name)
1880 1880
1881 1881 if not ui.quiet:
1882 1882 # options
1883 1883 if entry[1]:
1884 1884 option_lists.append((_("options:\n"), entry[1]))
1885 1885
1886 1886 addglobalopts(False)
1887 1887
1888 1888 def helplist(header, select=None):
1889 1889 h = {}
1890 1890 cmds = {}
1891 1891 for c, e in table.iteritems():
1892 1892 f = c.split("|", 1)[0]
1893 1893 if select and not select(f):
1894 1894 continue
1895 1895 if (not select and name != 'shortlist' and
1896 1896 e[0].__module__ != __name__):
1897 1897 continue
1898 1898 if name == "shortlist" and not f.startswith("^"):
1899 1899 continue
1900 1900 f = f.lstrip("^")
1901 1901 if not ui.debugflag and f.startswith("debug"):
1902 1902 continue
1903 1903 doc = e[0].__doc__
1904 1904 if doc and 'DEPRECATED' in doc and not ui.verbose:
1905 1905 continue
1906 1906 doc = gettext(doc)
1907 1907 if not doc:
1908 1908 doc = _("(no help text available)")
1909 1909 h[f] = doc.splitlines()[0].rstrip()
1910 1910 cmds[f] = c.lstrip("^")
1911 1911
1912 1912 if not h:
1913 1913 ui.status(_('no commands defined\n'))
1914 1914 return
1915 1915
1916 1916 ui.status(header)
1917 1917 fns = sorted(h)
1918 1918 m = max(map(len, fns))
1919 1919 for f in fns:
1920 1920 if ui.verbose:
1921 1921 commands = cmds[f].replace("|",", ")
1922 1922 ui.write(" %s:\n %s\n"%(commands, h[f]))
1923 1923 else:
1924 1924 ui.write('%s\n' % (util.wrap(h[f],
1925 1925 initindent=' %-*s ' % (m, f),
1926 1926 hangindent=' ' * (m + 4))))
1927 1927
1928 1928 if not ui.quiet:
1929 1929 addglobalopts(True)
1930 1930
1931 1931 def helptopic(name):
1932 1932 for names, header, doc in help.helptable:
1933 1933 if name in names:
1934 1934 break
1935 1935 else:
1936 1936 raise error.UnknownCommand(name)
1937 1937
1938 1938 # description
1939 1939 if not doc:
1940 1940 doc = _("(no help text available)")
1941 1941 if hasattr(doc, '__call__'):
1942 1942 doc = doc()
1943 1943
1944 1944 ui.write("%s\n\n" % header)
1945 1945 ui.write("%s\n" % minirst.format(doc, textwidth, indent=4))
1946 1946
1947 1947 def helpext(name):
1948 1948 try:
1949 1949 mod = extensions.find(name)
1950 1950 doc = gettext(mod.__doc__) or _('no help text available')
1951 1951 except KeyError:
1952 1952 mod = None
1953 1953 doc = extensions.disabledext(name)
1954 1954 if not doc:
1955 1955 raise error.UnknownCommand(name)
1956 1956
1957 1957 if '\n' not in doc:
1958 1958 head, tail = doc, ""
1959 1959 else:
1960 1960 head, tail = doc.split('\n', 1)
1961 1961 ui.write(_('%s extension - %s\n\n') % (name.split('.')[-1], head))
1962 1962 if tail:
1963 1963 ui.write(minirst.format(tail, textwidth))
1964 1964 ui.status('\n\n')
1965 1965
1966 1966 if mod:
1967 1967 try:
1968 1968 ct = mod.cmdtable
1969 1969 except AttributeError:
1970 1970 ct = {}
1971 1971 modcmds = set([c.split('|', 1)[0] for c in ct])
1972 1972 helplist(_('list of commands:\n\n'), modcmds.__contains__)
1973 1973 else:
1974 1974 ui.write(_('use "hg help extensions" for information on enabling '
1975 1975 'extensions\n'))
1976 1976
1977 1977 def helpextcmd(name):
1978 1978 cmd, ext, mod = extensions.disabledcmd(name, ui.config('ui', 'strict'))
1979 1979 doc = gettext(mod.__doc__).splitlines()[0]
1980 1980
1981 1981 msg = help.listexts(_("'%s' is provided by the following "
1982 1982 "extension:") % cmd, {ext: doc}, len(ext),
1983 1983 indent=4)
1984 1984 ui.write(minirst.format(msg, textwidth))
1985 1985 ui.write('\n\n')
1986 1986 ui.write(_('use "hg help extensions" for information on enabling '
1987 1987 'extensions\n'))
1988 1988
1989 1989 if name and name != 'shortlist':
1990 1990 i = None
1991 1991 if unknowncmd:
1992 1992 queries = (helpextcmd,)
1993 1993 else:
1994 1994 queries = (helptopic, helpcmd, helpext, helpextcmd)
1995 1995 for f in queries:
1996 1996 try:
1997 1997 f(name)
1998 1998 i = None
1999 1999 break
2000 2000 except error.UnknownCommand, inst:
2001 2001 i = inst
2002 2002 if i:
2003 2003 raise i
2004 2004
2005 2005 else:
2006 2006 # program name
2007 2007 if ui.verbose or with_version:
2008 2008 version_(ui)
2009 2009 else:
2010 2010 ui.status(_("Mercurial Distributed SCM\n"))
2011 2011 ui.status('\n')
2012 2012
2013 2013 # list of commands
2014 2014 if name == "shortlist":
2015 2015 header = _('basic commands:\n\n')
2016 2016 else:
2017 2017 header = _('list of commands:\n\n')
2018 2018
2019 2019 helplist(header)
2020 2020 if name != 'shortlist':
2021 2021 exts, maxlength = extensions.enabled()
2022 2022 text = help.listexts(_('enabled extensions:'), exts, maxlength)
2023 2023 if text:
2024 2024 ui.write("\n%s\n" % minirst.format(text, textwidth))
2025 2025
2026 2026 # list all option lists
2027 2027 opt_output = []
2028 2028 multioccur = False
2029 2029 for title, options in option_lists:
2030 2030 opt_output.append(("\n%s" % title, None))
2031 2031 for option in options:
2032 2032 if len(option) == 5:
2033 2033 shortopt, longopt, default, desc, optlabel = option
2034 2034 else:
2035 2035 shortopt, longopt, default, desc = option
2036 2036 optlabel = _("VALUE") # default label
2037 2037
2038 2038 if _("DEPRECATED") in desc and not ui.verbose:
2039 2039 continue
2040 2040 if isinstance(default, list):
2041 2041 numqualifier = " %s [+]" % optlabel
2042 2042 multioccur = True
2043 2043 elif (default is not None) and not isinstance(default, bool):
2044 2044 numqualifier = " %s" % optlabel
2045 2045 else:
2046 2046 numqualifier = ""
2047 2047 opt_output.append(("%2s%s" %
2048 2048 (shortopt and "-%s" % shortopt,
2049 2049 longopt and " --%s%s" %
2050 2050 (longopt, numqualifier)),
2051 2051 "%s%s" % (desc,
2052 2052 default
2053 2053 and _(" (default: %s)") % default
2054 2054 or "")))
2055 2055 if multioccur:
2056 2056 msg = _("\n[+] marked option can be specified multiple times")
2057 2057 if ui.verbose and name != 'shortlist':
2058 2058 opt_output.append((msg, None))
2059 2059 else:
2060 2060 opt_output.insert(-1, (msg, None))
2061 2061
2062 2062 if not name:
2063 2063 ui.write(_("\nadditional help topics:\n\n"))
2064 2064 topics = []
2065 2065 for names, header, doc in help.helptable:
2066 2066 topics.append((sorted(names, key=len, reverse=True)[0], header))
2067 2067 topics_len = max([len(s[0]) for s in topics])
2068 2068 for t, desc in topics:
2069 2069 ui.write(" %-*s %s\n" % (topics_len, t, desc))
2070 2070
2071 2071 if opt_output:
2072 2072 colwidth = encoding.colwidth
2073 2073 # normalize: (opt or message, desc or None, width of opt)
2074 2074 entries = [desc and (opt, desc, colwidth(opt)) or (opt, None, 0)
2075 2075 for opt, desc in opt_output]
2076 2076 hanging = max([e[2] for e in entries])
2077 2077 for opt, desc, width in entries:
2078 2078 if desc:
2079 2079 initindent = ' %s%s ' % (opt, ' ' * (hanging - width))
2080 2080 hangindent = ' ' * (hanging + 3)
2081 2081 ui.write('%s\n' % (util.wrap(desc,
2082 2082 initindent=initindent,
2083 2083 hangindent=hangindent)))
2084 2084 else:
2085 2085 ui.write("%s\n" % opt)
2086 2086
2087 2087 def identify(ui, repo, source=None,
2088 2088 rev=None, num=None, id=None, branch=None, tags=None):
2089 2089 """identify the working copy or specified revision
2090 2090
2091 2091 With no revision, print a summary of the current state of the
2092 2092 repository.
2093 2093
2094 2094 Specifying a path to a repository root or Mercurial bundle will
2095 2095 cause lookup to operate on that repository/bundle.
2096 2096
2097 2097 This summary identifies the repository state using one or two
2098 2098 parent hash identifiers, followed by a "+" if there are
2099 2099 uncommitted changes in the working directory, a list of tags for
2100 2100 this revision and a branch name for non-default branches.
2101 2101
2102 2102 Returns 0 if successful.
2103 2103 """
2104 2104
2105 2105 if not repo and not source:
2106 2106 raise util.Abort(_("There is no Mercurial repository here "
2107 2107 "(.hg not found)"))
2108 2108
2109 2109 hexfunc = ui.debugflag and hex or short
2110 2110 default = not (num or id or branch or tags)
2111 2111 output = []
2112 2112
2113 2113 revs = []
2114 2114 if source:
2115 2115 source, branches = hg.parseurl(ui.expandpath(source))
2116 2116 repo = hg.repository(ui, source)
2117 2117 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
2118 2118
2119 2119 if not repo.local():
2120 2120 if not rev and revs:
2121 2121 rev = revs[0]
2122 2122 if not rev:
2123 2123 rev = "tip"
2124 2124 if num or branch or tags:
2125 2125 raise util.Abort(
2126 2126 "can't query remote revision number, branch, or tags")
2127 2127 output = [hexfunc(repo.lookup(rev))]
2128 2128 elif not rev:
2129 2129 ctx = repo[None]
2130 2130 parents = ctx.parents()
2131 2131 changed = False
2132 2132 if default or id or num:
2133 2133 changed = util.any(repo.status())
2134 2134 if default or id:
2135 2135 output = ["%s%s" % ('+'.join([hexfunc(p.node()) for p in parents]),
2136 2136 (changed) and "+" or "")]
2137 2137 if num:
2138 2138 output.append("%s%s" % ('+'.join([str(p.rev()) for p in parents]),
2139 2139 (changed) and "+" or ""))
2140 2140 else:
2141 2141 ctx = repo[rev]
2142 2142 if default or id:
2143 2143 output = [hexfunc(ctx.node())]
2144 2144 if num:
2145 2145 output.append(str(ctx.rev()))
2146 2146
2147 2147 if repo.local() and default and not ui.quiet:
2148 2148 b = encoding.tolocal(ctx.branch())
2149 2149 if b != 'default':
2150 2150 output.append("(%s)" % b)
2151 2151
2152 2152 # multiple tags for a single parent separated by '/'
2153 2153 t = "/".join(ctx.tags())
2154 2154 if t:
2155 2155 output.append(t)
2156 2156
2157 2157 if branch:
2158 2158 output.append(encoding.tolocal(ctx.branch()))
2159 2159
2160 2160 if tags:
2161 2161 output.extend(ctx.tags())
2162 2162
2163 2163 ui.write("%s\n" % ' '.join(output))
2164 2164
2165 2165 def import_(ui, repo, patch1, *patches, **opts):
2166 2166 """import an ordered set of patches
2167 2167
2168 2168 Import a list of patches and commit them individually (unless
2169 2169 --no-commit is specified).
2170 2170
2171 2171 If there are outstanding changes in the working directory, import
2172 2172 will abort unless given the -f/--force flag.
2173 2173
2174 2174 You can import a patch straight from a mail message. Even patches
2175 2175 as attachments work (to use the body part, it must have type
2176 2176 text/plain or text/x-patch). From and Subject headers of email
2177 2177 message are used as default committer and commit message. All
2178 2178 text/plain body parts before first diff are added to commit
2179 2179 message.
2180 2180
2181 2181 If the imported patch was generated by :hg:`export`, user and
2182 2182 description from patch override values from message headers and
2183 2183 body. Values given on command line with -m/--message and -u/--user
2184 2184 override these.
2185 2185
2186 2186 If --exact is specified, import will set the working directory to
2187 2187 the parent of each patch before applying it, and will abort if the
2188 2188 resulting changeset has a different ID than the one recorded in
2189 2189 the patch. This may happen due to character set problems or other
2190 2190 deficiencies in the text patch format.
2191 2191
2192 2192 With -s/--similarity, hg will attempt to discover renames and
2193 2193 copies in the patch in the same way as 'addremove'.
2194 2194
2195 2195 To read a patch from standard input, use "-" as the patch name. If
2196 2196 a URL is specified, the patch will be downloaded from it.
2197 2197 See :hg:`help dates` for a list of formats valid for -d/--date.
2198 2198
2199 2199 Returns 0 on success.
2200 2200 """
2201 2201 patches = (patch1,) + patches
2202 2202
2203 2203 date = opts.get('date')
2204 2204 if date:
2205 2205 opts['date'] = util.parsedate(date)
2206 2206
2207 2207 try:
2208 2208 sim = float(opts.get('similarity') or 0)
2209 2209 except ValueError:
2210 2210 raise util.Abort(_('similarity must be a number'))
2211 2211 if sim < 0 or sim > 100:
2212 2212 raise util.Abort(_('similarity must be between 0 and 100'))
2213 2213
2214 2214 if opts.get('exact') or not opts.get('force'):
2215 2215 cmdutil.bail_if_changed(repo)
2216 2216
2217 2217 d = opts["base"]
2218 2218 strip = opts["strip"]
2219 2219 wlock = lock = None
2220 2220
2221 2221 def tryone(ui, hunk):
2222 2222 tmpname, message, user, date, branch, nodeid, p1, p2 = \
2223 2223 patch.extract(ui, hunk)
2224 2224
2225 2225 if not tmpname:
2226 2226 return None
2227 2227 commitid = _('to working directory')
2228 2228
2229 2229 try:
2230 2230 cmdline_message = cmdutil.logmessage(opts)
2231 2231 if cmdline_message:
2232 2232 # pickup the cmdline msg
2233 2233 message = cmdline_message
2234 2234 elif message:
2235 2235 # pickup the patch msg
2236 2236 message = message.strip()
2237 2237 else:
2238 2238 # launch the editor
2239 2239 message = None
2240 2240 ui.debug('message:\n%s\n' % message)
2241 2241
2242 2242 wp = repo.parents()
2243 2243 if opts.get('exact'):
2244 2244 if not nodeid or not p1:
2245 2245 raise util.Abort(_('not a Mercurial patch'))
2246 2246 p1 = repo.lookup(p1)
2247 2247 p2 = repo.lookup(p2 or hex(nullid))
2248 2248
2249 2249 if p1 != wp[0].node():
2250 2250 hg.clean(repo, p1)
2251 2251 repo.dirstate.setparents(p1, p2)
2252 2252 elif p2:
2253 2253 try:
2254 2254 p1 = repo.lookup(p1)
2255 2255 p2 = repo.lookup(p2)
2256 2256 if p1 == wp[0].node():
2257 2257 repo.dirstate.setparents(p1, p2)
2258 2258 except error.RepoError:
2259 2259 pass
2260 2260 if opts.get('exact') or opts.get('import_branch'):
2261 2261 repo.dirstate.setbranch(branch or 'default')
2262 2262
2263 2263 files = {}
2264 2264 try:
2265 2265 patch.patch(tmpname, ui, strip=strip, cwd=repo.root,
2266 2266 files=files, eolmode=None)
2267 2267 finally:
2268 2268 files = patch.updatedir(ui, repo, files,
2269 2269 similarity=sim / 100.0)
2270 2270 if not opts.get('no_commit'):
2271 2271 if opts.get('exact'):
2272 2272 m = None
2273 2273 else:
2274 2274 m = cmdutil.matchfiles(repo, files or [])
2275 2275 n = repo.commit(message, opts.get('user') or user,
2276 2276 opts.get('date') or date, match=m,
2277 2277 editor=cmdutil.commiteditor)
2278 2278 if opts.get('exact'):
2279 2279 if hex(n) != nodeid:
2280 2280 repo.rollback()
2281 2281 raise util.Abort(_('patch is damaged'
2282 2282 ' or loses information'))
2283 2283 # Force a dirstate write so that the next transaction
2284 2284 # backups an up-do-date file.
2285 2285 repo.dirstate.write()
2286 2286 if n:
2287 2287 commitid = short(n)
2288 2288
2289 2289 return commitid
2290 2290 finally:
2291 2291 os.unlink(tmpname)
2292 2292
2293 2293 try:
2294 2294 wlock = repo.wlock()
2295 2295 lock = repo.lock()
2296 2296 lastcommit = None
2297 2297 for p in patches:
2298 2298 pf = os.path.join(d, p)
2299 2299
2300 2300 if pf == '-':
2301 2301 ui.status(_("applying patch from stdin\n"))
2302 2302 pf = sys.stdin
2303 2303 else:
2304 2304 ui.status(_("applying %s\n") % p)
2305 2305 pf = url.open(ui, pf)
2306 2306
2307 2307 haspatch = False
2308 2308 for hunk in patch.split(pf):
2309 2309 commitid = tryone(ui, hunk)
2310 2310 if commitid:
2311 2311 haspatch = True
2312 2312 if lastcommit:
2313 2313 ui.status(_('applied %s\n') % lastcommit)
2314 2314 lastcommit = commitid
2315 2315
2316 2316 if not haspatch:
2317 2317 raise util.Abort(_('no diffs found'))
2318 2318
2319 2319 finally:
2320 2320 release(lock, wlock)
2321 2321
2322 2322 def incoming(ui, repo, source="default", **opts):
2323 2323 """show new changesets found in source
2324 2324
2325 2325 Show new changesets found in the specified path/URL or the default
2326 2326 pull location. These are the changesets that would have been pulled
2327 2327 if a pull at the time you issued this command.
2328 2328
2329 2329 For remote repository, using --bundle avoids downloading the
2330 2330 changesets twice if the incoming is followed by a pull.
2331 2331
2332 2332 See pull for valid source format details.
2333 2333
2334 2334 Returns 0 if there are incoming changes, 1 otherwise.
2335 2335 """
2336 2336 limit = cmdutil.loglimit(opts)
2337 2337 source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch'))
2338 2338 other = hg.repository(hg.remoteui(repo, opts), source)
2339 2339 ui.status(_('comparing with %s\n') % url.hidepassword(source))
2340 2340 revs, checkout = hg.addbranchrevs(repo, other, branches, opts.get('rev'))
2341 2341 if revs:
2342 2342 revs = [other.lookup(rev) for rev in revs]
2343 2343
2344 2344 tmp = discovery.findcommonincoming(repo, other, heads=revs,
2345 2345 force=opts.get('force'))
2346 2346 common, incoming, rheads = tmp
2347 2347 if not incoming:
2348 2348 try:
2349 2349 os.unlink(opts["bundle"])
2350 2350 except:
2351 2351 pass
2352 2352 ui.status(_("no changes found\n"))
2353 2353 return 1
2354 2354
2355 2355 cleanup = None
2356 2356 try:
2357 2357 fname = opts["bundle"]
2358 2358 if fname or not other.local():
2359 2359 # create a bundle (uncompressed if other repo is not local)
2360 2360
2361 2361 if revs is None and other.capable('changegroupsubset'):
2362 2362 revs = rheads
2363 2363
2364 2364 if revs is None:
2365 2365 cg = other.changegroup(incoming, "incoming")
2366 2366 else:
2367 2367 cg = other.changegroupsubset(incoming, revs, 'incoming')
2368 2368 bundletype = other.local() and "HG10BZ" or "HG10UN"
2369 2369 fname = cleanup = changegroup.writebundle(cg, fname, bundletype)
2370 2370 # keep written bundle?
2371 2371 if opts["bundle"]:
2372 2372 cleanup = None
2373 2373 if not other.local():
2374 2374 # use the created uncompressed bundlerepo
2375 2375 other = bundlerepo.bundlerepository(ui, repo.root, fname)
2376 2376
2377 2377 o = other.changelog.nodesbetween(incoming, revs)[0]
2378 2378 if opts.get('newest_first'):
2379 2379 o.reverse()
2380 2380 displayer = cmdutil.show_changeset(ui, other, opts)
2381 2381 count = 0
2382 2382 for n in o:
2383 2383 if limit is not None and count >= limit:
2384 2384 break
2385 2385 parents = [p for p in other.changelog.parents(n) if p != nullid]
2386 2386 if opts.get('no_merges') and len(parents) == 2:
2387 2387 continue
2388 2388 count += 1
2389 2389 displayer.show(other[n])
2390 2390 displayer.close()
2391 2391 finally:
2392 2392 if hasattr(other, 'close'):
2393 2393 other.close()
2394 2394 if cleanup:
2395 2395 os.unlink(cleanup)
2396 2396
2397 2397 def init(ui, dest=".", **opts):
2398 2398 """create a new repository in the given directory
2399 2399
2400 2400 Initialize a new repository in the given directory. If the given
2401 2401 directory does not exist, it will be created.
2402 2402
2403 2403 If no directory is given, the current directory is used.
2404 2404
2405 2405 It is possible to specify an ``ssh://`` URL as the destination.
2406 2406 See :hg:`help urls` for more information.
2407 2407
2408 2408 Returns 0 on success.
2409 2409 """
2410 2410 hg.repository(hg.remoteui(ui, opts), dest, create=1)
2411 2411
2412 2412 def locate(ui, repo, *pats, **opts):
2413 2413 """locate files matching specific patterns
2414 2414
2415 2415 Print files under Mercurial control in the working directory whose
2416 2416 names match the given patterns.
2417 2417
2418 2418 By default, this command searches all directories in the working
2419 2419 directory. To search just the current directory and its
2420 2420 subdirectories, use "--include .".
2421 2421
2422 2422 If no patterns are given to match, this command prints the names
2423 2423 of all files under Mercurial control in the working directory.
2424 2424
2425 2425 If you want to feed the output of this command into the "xargs"
2426 2426 command, use the -0 option to both this command and "xargs". This
2427 2427 will avoid the problem of "xargs" treating single filenames that
2428 2428 contain whitespace as multiple filenames.
2429 2429
2430 2430 Returns 0 if a match is found, 1 otherwise.
2431 2431 """
2432 2432 end = opts.get('print0') and '\0' or '\n'
2433 2433 rev = opts.get('rev') or None
2434 2434
2435 2435 ret = 1
2436 2436 m = cmdutil.match(repo, pats, opts, default='relglob')
2437 2437 m.bad = lambda x, y: False
2438 2438 for abs in repo[rev].walk(m):
2439 2439 if not rev and abs not in repo.dirstate:
2440 2440 continue
2441 2441 if opts.get('fullpath'):
2442 2442 ui.write(repo.wjoin(abs), end)
2443 2443 else:
2444 2444 ui.write(((pats and m.rel(abs)) or abs), end)
2445 2445 ret = 0
2446 2446
2447 2447 return ret
2448 2448
2449 2449 def log(ui, repo, *pats, **opts):
2450 2450 """show revision history of entire repository or files
2451 2451
2452 2452 Print the revision history of the specified files or the entire
2453 2453 project.
2454 2454
2455 2455 File history is shown without following rename or copy history of
2456 2456 files. Use -f/--follow with a filename to follow history across
2457 2457 renames and copies. --follow without a filename will only show
2458 2458 ancestors or descendants of the starting revision. --follow-first
2459 2459 only follows the first parent of merge revisions.
2460 2460
2461 2461 If no revision range is specified, the default is tip:0 unless
2462 2462 --follow is set, in which case the working directory parent is
2463 2463 used as the starting revision. You can specify a revision set for
2464 2464 log, see :hg:`help revsets` for more information.
2465 2465
2466 2466 See :hg:`help dates` for a list of formats valid for -d/--date.
2467 2467
2468 2468 By default this command prints revision number and changeset id,
2469 2469 tags, non-trivial parents, user, date and time, and a summary for
2470 2470 each commit. When the -v/--verbose switch is used, the list of
2471 2471 changed files and full commit message are shown.
2472 2472
2473 2473 NOTE: log -p/--patch may generate unexpected diff output for merge
2474 2474 changesets, as it will only compare the merge changeset against
2475 2475 its first parent. Also, only files different from BOTH parents
2476 2476 will appear in files:.
2477 2477
2478 2478 Returns 0 on success.
2479 2479 """
2480 2480
2481 2481 matchfn = cmdutil.match(repo, pats, opts)
2482 2482 limit = cmdutil.loglimit(opts)
2483 2483 count = 0
2484 2484
2485 2485 endrev = None
2486 2486 if opts.get('copies') and opts.get('rev'):
2487 2487 endrev = max(cmdutil.revrange(repo, opts.get('rev'))) + 1
2488 2488
2489 2489 df = False
2490 2490 if opts["date"]:
2491 2491 df = util.matchdate(opts["date"])
2492 2492
2493 2493 branches = opts.get('branch', []) + opts.get('only_branch', [])
2494 2494 opts['branch'] = [repo.lookupbranch(b) for b in branches]
2495 2495
2496 2496 displayer = cmdutil.show_changeset(ui, repo, opts, True)
2497 2497 def prep(ctx, fns):
2498 2498 rev = ctx.rev()
2499 2499 parents = [p for p in repo.changelog.parentrevs(rev)
2500 2500 if p != nullrev]
2501 2501 if opts.get('no_merges') and len(parents) == 2:
2502 2502 return
2503 2503 if opts.get('only_merges') and len(parents) != 2:
2504 2504 return
2505 2505 if opts.get('branch') and ctx.branch() not in opts['branch']:
2506 2506 return
2507 2507 if df and not df(ctx.date()[0]):
2508 2508 return
2509 2509 if opts['user'] and not [k for k in opts['user'] if k in ctx.user()]:
2510 2510 return
2511 2511 if opts.get('keyword'):
2512 2512 for k in [kw.lower() for kw in opts['keyword']]:
2513 2513 if (k in ctx.user().lower() or
2514 2514 k in ctx.description().lower() or
2515 2515 k in " ".join(ctx.files()).lower()):
2516 2516 break
2517 2517 else:
2518 2518 return
2519 2519
2520 2520 copies = None
2521 2521 if opts.get('copies') and rev:
2522 2522 copies = []
2523 2523 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
2524 2524 for fn in ctx.files():
2525 2525 rename = getrenamed(fn, rev)
2526 2526 if rename:
2527 2527 copies.append((fn, rename[0]))
2528 2528
2529 2529 revmatchfn = None
2530 2530 if opts.get('patch') or opts.get('stat'):
2531 2531 revmatchfn = cmdutil.match(repo, fns, default='path')
2532 2532
2533 2533 displayer.show(ctx, copies=copies, matchfn=revmatchfn)
2534 2534
2535 2535 for ctx in cmdutil.walkchangerevs(repo, matchfn, opts, prep):
2536 2536 if count == limit:
2537 2537 break
2538 2538 if displayer.flush(ctx.rev()):
2539 2539 count += 1
2540 2540 displayer.close()
2541 2541
2542 2542 def manifest(ui, repo, node=None, rev=None):
2543 2543 """output the current or given revision of the project manifest
2544 2544
2545 2545 Print a list of version controlled files for the given revision.
2546 2546 If no revision is given, the first parent of the working directory
2547 2547 is used, or the null revision if no revision is checked out.
2548 2548
2549 2549 With -v, print file permissions, symlink and executable bits.
2550 2550 With --debug, print file revision hashes.
2551 2551
2552 2552 Returns 0 on success.
2553 2553 """
2554 2554
2555 2555 if rev and node:
2556 2556 raise util.Abort(_("please specify just one revision"))
2557 2557
2558 2558 if not node:
2559 2559 node = rev
2560 2560
2561 2561 decor = {'l':'644 @ ', 'x':'755 * ', '':'644 '}
2562 2562 ctx = repo[node]
2563 2563 for f in ctx:
2564 2564 if ui.debugflag:
2565 2565 ui.write("%40s " % hex(ctx.manifest()[f]))
2566 2566 if ui.verbose:
2567 2567 ui.write(decor[ctx.flags(f)])
2568 2568 ui.write("%s\n" % f)
2569 2569
2570 2570 def merge(ui, repo, node=None, **opts):
2571 2571 """merge working directory with another revision
2572 2572
2573 2573 The current working directory is updated with all changes made in
2574 2574 the requested revision since the last common predecessor revision.
2575 2575
2576 2576 Files that changed between either parent are marked as changed for
2577 2577 the next commit and a commit must be performed before any further
2578 2578 updates to the repository are allowed. The next commit will have
2579 2579 two parents.
2580 2580
2581 2581 If no revision is specified, the working directory's parent is a
2582 2582 head revision, and the current branch contains exactly one other
2583 2583 head, the other head is merged with by default. Otherwise, an
2584 2584 explicit revision with which to merge with must be provided.
2585 2585
2586 2586 To undo an uncommitted merge, use :hg:`update --clean .` which
2587 2587 will check out a clean copy of the original merge parent, losing
2588 2588 all changes.
2589 2589
2590 2590 Returns 0 on success, 1 if there are unresolved files.
2591 2591 """
2592 2592
2593 2593 if opts.get('rev') and node:
2594 2594 raise util.Abort(_("please specify just one revision"))
2595 2595 if not node:
2596 2596 node = opts.get('rev')
2597 2597
2598 2598 if not node:
2599 2599 branch = repo.changectx(None).branch()
2600 2600 bheads = repo.branchheads(branch)
2601 2601 if len(bheads) > 2:
2602 2602 raise util.Abort(_(
2603 2603 'branch \'%s\' has %d heads - '
2604 2604 'please merge with an explicit rev\n'
2605 2605 '(run \'hg heads .\' to see heads)')
2606 2606 % (branch, len(bheads)))
2607 2607
2608 2608 parent = repo.dirstate.parents()[0]
2609 2609 if len(bheads) == 1:
2610 2610 if len(repo.heads()) > 1:
2611 2611 raise util.Abort(_(
2612 2612 'branch \'%s\' has one head - '
2613 2613 'please merge with an explicit rev\n'
2614 2614 '(run \'hg heads\' to see all heads)')
2615 2615 % branch)
2616 2616 msg = _('there is nothing to merge')
2617 2617 if parent != repo.lookup(repo[None].branch()):
2618 2618 msg = _('%s - use "hg update" instead') % msg
2619 2619 raise util.Abort(msg)
2620 2620
2621 2621 if parent not in bheads:
2622 2622 raise util.Abort(_('working dir not at a head rev - '
2623 2623 'use "hg update" or merge with an explicit rev'))
2624 2624 node = parent == bheads[0] and bheads[-1] or bheads[0]
2625 2625
2626 2626 if opts.get('preview'):
2627 2627 # find nodes that are ancestors of p2 but not of p1
2628 2628 p1 = repo.lookup('.')
2629 2629 p2 = repo.lookup(node)
2630 2630 nodes = repo.changelog.findmissing(common=[p1], heads=[p2])
2631 2631
2632 2632 displayer = cmdutil.show_changeset(ui, repo, opts)
2633 2633 for node in nodes:
2634 2634 displayer.show(repo[node])
2635 2635 displayer.close()
2636 2636 return 0
2637 2637
2638 2638 return hg.merge(repo, node, force=opts.get('force'))
2639 2639
2640 2640 def outgoing(ui, repo, dest=None, **opts):
2641 2641 """show changesets not found in the destination
2642 2642
2643 2643 Show changesets not found in the specified destination repository
2644 2644 or the default push location. These are the changesets that would
2645 2645 be pushed if a push was requested.
2646 2646
2647 2647 See pull for details of valid destination formats.
2648 2648
2649 2649 Returns 0 if there are outgoing changes, 1 otherwise.
2650 2650 """
2651 2651 limit = cmdutil.loglimit(opts)
2652 2652 dest = ui.expandpath(dest or 'default-push', dest or 'default')
2653 2653 dest, branches = hg.parseurl(dest, opts.get('branch'))
2654 2654 revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get('rev'))
2655 2655 if revs:
2656 2656 revs = [repo.lookup(rev) for rev in revs]
2657 2657
2658 2658 other = hg.repository(hg.remoteui(repo, opts), dest)
2659 2659 ui.status(_('comparing with %s\n') % url.hidepassword(dest))
2660 2660 o = discovery.findoutgoing(repo, other, force=opts.get('force'))
2661 2661 if not o:
2662 2662 ui.status(_("no changes found\n"))
2663 2663 return 1
2664 2664 o = repo.changelog.nodesbetween(o, revs)[0]
2665 2665 if opts.get('newest_first'):
2666 2666 o.reverse()
2667 2667 displayer = cmdutil.show_changeset(ui, repo, opts)
2668 2668 count = 0
2669 2669 for n in o:
2670 2670 if limit is not None and count >= limit:
2671 2671 break
2672 2672 parents = [p for p in repo.changelog.parents(n) if p != nullid]
2673 2673 if opts.get('no_merges') and len(parents) == 2:
2674 2674 continue
2675 2675 count += 1
2676 2676 displayer.show(repo[n])
2677 2677 displayer.close()
2678 2678
2679 2679 def parents(ui, repo, file_=None, **opts):
2680 2680 """show the parents of the working directory or revision
2681 2681
2682 2682 Print the working directory's parent revisions. If a revision is
2683 2683 given via -r/--rev, the parent of that revision will be printed.
2684 2684 If a file argument is given, the revision in which the file was
2685 2685 last changed (before the working directory revision or the
2686 2686 argument to --rev if given) is printed.
2687 2687
2688 2688 Returns 0 on success.
2689 2689 """
2690 2690 rev = opts.get('rev')
2691 2691 if rev:
2692 2692 ctx = repo[rev]
2693 2693 else:
2694 2694 ctx = repo[None]
2695 2695
2696 2696 if file_:
2697 2697 m = cmdutil.match(repo, (file_,), opts)
2698 2698 if m.anypats() or len(m.files()) != 1:
2699 2699 raise util.Abort(_('can only specify an explicit filename'))
2700 2700 file_ = m.files()[0]
2701 2701 filenodes = []
2702 2702 for cp in ctx.parents():
2703 2703 if not cp:
2704 2704 continue
2705 2705 try:
2706 2706 filenodes.append(cp.filenode(file_))
2707 2707 except error.LookupError:
2708 2708 pass
2709 2709 if not filenodes:
2710 2710 raise util.Abort(_("'%s' not found in manifest!") % file_)
2711 2711 fl = repo.file(file_)
2712 2712 p = [repo.lookup(fl.linkrev(fl.rev(fn))) for fn in filenodes]
2713 2713 else:
2714 2714 p = [cp.node() for cp in ctx.parents()]
2715 2715
2716 2716 displayer = cmdutil.show_changeset(ui, repo, opts)
2717 2717 for n in p:
2718 2718 if n != nullid:
2719 2719 displayer.show(repo[n])
2720 2720 displayer.close()
2721 2721
2722 2722 def paths(ui, repo, search=None):
2723 2723 """show aliases for remote repositories
2724 2724
2725 2725 Show definition of symbolic path name NAME. If no name is given,
2726 2726 show definition of all available names.
2727 2727
2728 2728 Path names are defined in the [paths] section of
2729 2729 ``/etc/mercurial/hgrc`` and ``$HOME/.hgrc``. If run inside a
2730 2730 repository, ``.hg/hgrc`` is used, too.
2731 2731
2732 2732 The path names ``default`` and ``default-push`` have a special
2733 2733 meaning. When performing a push or pull operation, they are used
2734 2734 as fallbacks if no location is specified on the command-line.
2735 2735 When ``default-push`` is set, it will be used for push and
2736 2736 ``default`` will be used for pull; otherwise ``default`` is used
2737 2737 as the fallback for both. When cloning a repository, the clone
2738 2738 source is written as ``default`` in ``.hg/hgrc``. Note that
2739 2739 ``default`` and ``default-push`` apply to all inbound (e.g.
2740 2740 :hg:`incoming`) and outbound (e.g. :hg:`outgoing`, :hg:`email` and
2741 2741 :hg:`bundle`) operations.
2742 2742
2743 2743 See :hg:`help urls` for more information.
2744 2744
2745 2745 Returns 0 on success.
2746 2746 """
2747 2747 if search:
2748 2748 for name, path in ui.configitems("paths"):
2749 2749 if name == search:
2750 2750 ui.write("%s\n" % url.hidepassword(path))
2751 2751 return
2752 2752 ui.warn(_("not found!\n"))
2753 2753 return 1
2754 2754 else:
2755 2755 for name, path in ui.configitems("paths"):
2756 2756 ui.write("%s = %s\n" % (name, url.hidepassword(path)))
2757 2757
2758 2758 def postincoming(ui, repo, modheads, optupdate, checkout):
2759 2759 if modheads == 0:
2760 2760 return
2761 2761 if optupdate:
2762 2762 if (modheads <= 1 or len(repo.branchheads()) == 1) or checkout:
2763 2763 return hg.update(repo, checkout)
2764 2764 else:
2765 2765 ui.status(_("not updating, since new heads added\n"))
2766 2766 if modheads > 1:
2767 2767 ui.status(_("(run 'hg heads' to see heads, 'hg merge' to merge)\n"))
2768 2768 else:
2769 2769 ui.status(_("(run 'hg update' to get a working copy)\n"))
2770 2770
2771 2771 def pull(ui, repo, source="default", **opts):
2772 2772 """pull changes from the specified source
2773 2773
2774 2774 Pull changes from a remote repository to a local one.
2775 2775
2776 2776 This finds all changes from the repository at the specified path
2777 2777 or URL and adds them to a local repository (the current one unless
2778 2778 -R is specified). By default, this does not update the copy of the
2779 2779 project in the working directory.
2780 2780
2781 2781 Use :hg:`incoming` if you want to see what would have been added
2782 2782 by a pull at the time you issued this command. If you then decide
2783 2783 to add those changes to the repository, you should use :hg:`pull
2784 2784 -r X` where ``X`` is the last changeset listed by :hg:`incoming`.
2785 2785
2786 2786 If SOURCE is omitted, the 'default' path will be used.
2787 2787 See :hg:`help urls` for more information.
2788 2788
2789 2789 Returns 0 on success, 1 if an update had unresolved files.
2790 2790 """
2791 2791 source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch'))
2792 2792 other = hg.repository(hg.remoteui(repo, opts), source)
2793 2793 ui.status(_('pulling from %s\n') % url.hidepassword(source))
2794 2794 revs, checkout = hg.addbranchrevs(repo, other, branches, opts.get('rev'))
2795 2795 if revs:
2796 2796 try:
2797 2797 revs = [other.lookup(rev) for rev in revs]
2798 2798 except error.CapabilityError:
2799 2799 err = _("Other repository doesn't support revision lookup, "
2800 2800 "so a rev cannot be specified.")
2801 2801 raise util.Abort(err)
2802 2802
2803 2803 modheads = repo.pull(other, heads=revs, force=opts.get('force'))
2804 2804 if checkout:
2805 2805 checkout = str(repo.changelog.rev(other.lookup(checkout)))
2806 2806 return postincoming(ui, repo, modheads, opts.get('update'), checkout)
2807 2807
2808 2808 def push(ui, repo, dest=None, **opts):
2809 2809 """push changes to the specified destination
2810 2810
2811 2811 Push changesets from the local repository to the specified
2812 2812 destination.
2813 2813
2814 2814 This operation is symmetrical to pull: it is identical to a pull
2815 2815 in the destination repository from the current one.
2816 2816
2817 2817 By default, push will not allow creation of new heads at the
2818 2818 destination, since multiple heads would make it unclear which head
2819 2819 to use. In this situation, it is recommended to pull and merge
2820 2820 before pushing.
2821 2821
2822 2822 Use --new-branch if you want to allow push to create a new named
2823 2823 branch that is not present at the destination. This allows you to
2824 2824 only create a new branch without forcing other changes.
2825 2825
2826 2826 Use -f/--force to override the default behavior and push all
2827 2827 changesets on all branches.
2828 2828
2829 2829 If -r/--rev is used, the specified revision and all its ancestors
2830 2830 will be pushed to the remote repository.
2831 2831
2832 2832 Please see :hg:`help urls` for important details about ``ssh://``
2833 2833 URLs. If DESTINATION is omitted, a default path will be used.
2834 2834
2835 2835 Returns 0 if push was successful, 1 if nothing to push.
2836 2836 """
2837 2837 dest = ui.expandpath(dest or 'default-push', dest or 'default')
2838 2838 dest, branches = hg.parseurl(dest, opts.get('branch'))
2839 2839 revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get('rev'))
2840 2840 other = hg.repository(hg.remoteui(repo, opts), dest)
2841 2841 ui.status(_('pushing to %s\n') % url.hidepassword(dest))
2842 2842 if revs:
2843 2843 revs = [repo.lookup(rev) for rev in revs]
2844 2844
2845 2845 # push subrepos depth-first for coherent ordering
2846 2846 c = repo['']
2847 2847 subs = c.substate # only repos that are committed
2848 2848 for s in sorted(subs):
2849 2849 if not c.sub(s).push(opts.get('force')):
2850 2850 return False
2851 2851
2852 2852 r = repo.push(other, opts.get('force'), revs=revs,
2853 2853 newbranch=opts.get('new_branch'))
2854 2854 return r == 0
2855 2855
2856 2856 def recover(ui, repo):
2857 2857 """roll back an interrupted transaction
2858 2858
2859 2859 Recover from an interrupted commit or pull.
2860 2860
2861 2861 This command tries to fix the repository status after an
2862 2862 interrupted operation. It should only be necessary when Mercurial
2863 2863 suggests it.
2864 2864
2865 2865 Returns 0 if successful, 1 if nothing to recover or verify fails.
2866 2866 """
2867 2867 if repo.recover():
2868 2868 return hg.verify(repo)
2869 2869 return 1
2870 2870
2871 2871 def remove(ui, repo, *pats, **opts):
2872 2872 """remove the specified files on the next commit
2873 2873
2874 2874 Schedule the indicated files for removal from the repository.
2875 2875
2876 2876 This only removes files from the current branch, not from the
2877 2877 entire project history. -A/--after can be used to remove only
2878 2878 files that have already been deleted, -f/--force can be used to
2879 2879 force deletion, and -Af can be used to remove files from the next
2880 2880 revision without deleting them from the working directory.
2881 2881
2882 2882 The following table details the behavior of remove for different
2883 2883 file states (columns) and option combinations (rows). The file
2884 2884 states are Added [A], Clean [C], Modified [M] and Missing [!] (as
2885 2885 reported by :hg:`status`). The actions are Warn, Remove (from
2886 2886 branch) and Delete (from disk)::
2887 2887
2888 2888 A C M !
2889 2889 none W RD W R
2890 2890 -f R RD RD R
2891 2891 -A W W W R
2892 2892 -Af R R R R
2893 2893
2894 2894 This command schedules the files to be removed at the next commit.
2895 2895 To undo a remove before that, see :hg:`revert`.
2896 2896
2897 2897 Returns 0 on success, 1 if any warnings encountered.
2898 2898 """
2899 2899
2900 2900 ret = 0
2901 2901 after, force = opts.get('after'), opts.get('force')
2902 2902 if not pats and not after:
2903 2903 raise util.Abort(_('no files specified'))
2904 2904
2905 2905 m = cmdutil.match(repo, pats, opts)
2906 2906 s = repo.status(match=m, clean=True)
2907 2907 modified, added, deleted, clean = s[0], s[1], s[3], s[6]
2908 2908
2909 2909 for f in m.files():
2910 2910 if f not in repo.dirstate and not os.path.isdir(m.rel(f)):
2911 2911 ui.warn(_('not removing %s: file is untracked\n') % m.rel(f))
2912 2912 ret = 1
2913 2913
2914 2914 def warn(files, reason):
2915 2915 for f in files:
2916 2916 ui.warn(_('not removing %s: file %s (use -f to force removal)\n')
2917 2917 % (m.rel(f), reason))
2918 2918 ret = 1
2919 2919
2920 2920 if force:
2921 2921 remove, forget = modified + deleted + clean, added
2922 2922 elif after:
2923 2923 remove, forget = deleted, []
2924 2924 warn(modified + added + clean, _('still exists'))
2925 2925 else:
2926 2926 remove, forget = deleted + clean, []
2927 2927 warn(modified, _('is modified'))
2928 2928 warn(added, _('has been marked for add'))
2929 2929
2930 2930 for f in sorted(remove + forget):
2931 2931 if ui.verbose or not m.exact(f):
2932 2932 ui.status(_('removing %s\n') % m.rel(f))
2933 2933
2934 2934 repo[None].forget(forget)
2935 2935 repo[None].remove(remove, unlink=not after)
2936 2936 return ret
2937 2937
2938 2938 def rename(ui, repo, *pats, **opts):
2939 2939 """rename files; equivalent of copy + remove
2940 2940
2941 2941 Mark dest as copies of sources; mark sources for deletion. If dest
2942 2942 is a directory, copies are put in that directory. If dest is a
2943 2943 file, there can only be one source.
2944 2944
2945 2945 By default, this command copies the contents of files as they
2946 2946 exist in the working directory. If invoked with -A/--after, the
2947 2947 operation is recorded, but no copying is performed.
2948 2948
2949 2949 This command takes effect at the next commit. To undo a rename
2950 2950 before that, see :hg:`revert`.
2951 2951
2952 2952 Returns 0 on success, 1 if errors are encountered.
2953 2953 """
2954 2954 wlock = repo.wlock(False)
2955 2955 try:
2956 2956 return cmdutil.copy(ui, repo, pats, opts, rename=True)
2957 2957 finally:
2958 2958 wlock.release()
2959 2959
2960 2960 def resolve(ui, repo, *pats, **opts):
2961 2961 """various operations to help finish a merge
2962 2962
2963 2963 This command includes several actions that are often useful while
2964 2964 performing a merge, after running ``merge`` but before running
2965 2965 ``commit``. (It is only meaningful if your working directory has
2966 2966 two parents.) It is most relevant for merges with unresolved
2967 2967 conflicts, which are typically a result of non-interactive merging with
2968 2968 ``internal:merge`` or a command-line merge tool like ``diff3``.
2969 2969
2970 2970 The available actions are:
2971 2971
2972 2972 1) list files that were merged with conflicts (U, for unresolved)
2973 2973 and without conflicts (R, for resolved): ``hg resolve -l``
2974 2974 (this is like ``status`` for merges)
2975 2975 2) record that you have resolved conflicts in certain files:
2976 2976 ``hg resolve -m [file ...]`` (default: mark all unresolved files)
2977 2977 3) forget that you have resolved conflicts in certain files:
2978 2978 ``hg resolve -u [file ...]`` (default: unmark all resolved files)
2979 2979 4) discard your current attempt(s) at resolving conflicts and
2980 2980 restart the merge from scratch: ``hg resolve file...``
2981 2981 (or ``-a`` for all unresolved files)
2982 2982
2983 2983 Note that Mercurial will not let you commit files with unresolved merge
2984 2984 conflicts. You must use ``hg resolve -m ...`` before you can commit
2985 2985 after a conflicting merge.
2986 2986
2987 2987 Returns 0 on success, 1 if any files fail a resolve attempt.
2988 2988 """
2989 2989
2990 2990 all, mark, unmark, show, nostatus = \
2991 2991 [opts.get(o) for o in 'all mark unmark list no_status'.split()]
2992 2992
2993 2993 if (show and (mark or unmark)) or (mark and unmark):
2994 2994 raise util.Abort(_("too many options specified"))
2995 2995 if pats and all:
2996 2996 raise util.Abort(_("can't specify --all and patterns"))
2997 2997 if not (all or pats or show or mark or unmark):
2998 2998 raise util.Abort(_('no files or directories specified; '
2999 2999 'use --all to remerge all files'))
3000 3000
3001 3001 ms = mergemod.mergestate(repo)
3002 3002 m = cmdutil.match(repo, pats, opts)
3003 3003 ret = 0
3004 3004
3005 3005 for f in ms:
3006 3006 if m(f):
3007 3007 if show:
3008 3008 if nostatus:
3009 3009 ui.write("%s\n" % f)
3010 3010 else:
3011 3011 ui.write("%s %s\n" % (ms[f].upper(), f),
3012 3012 label='resolve.' +
3013 3013 {'u': 'unresolved', 'r': 'resolved'}[ms[f]])
3014 3014 elif mark:
3015 3015 ms.mark(f, "r")
3016 3016 elif unmark:
3017 3017 ms.mark(f, "u")
3018 3018 else:
3019 3019 wctx = repo[None]
3020 3020 mctx = wctx.parents()[-1]
3021 3021
3022 3022 # backup pre-resolve (merge uses .orig for its own purposes)
3023 3023 a = repo.wjoin(f)
3024 3024 util.copyfile(a, a + ".resolve")
3025 3025
3026 3026 # resolve file
3027 3027 if ms.resolve(f, wctx, mctx):
3028 3028 ret = 1
3029 3029
3030 3030 # replace filemerge's .orig file with our resolve file
3031 3031 util.rename(a + ".resolve", a + ".orig")
3032 3032 return ret
3033 3033
3034 3034 def revert(ui, repo, *pats, **opts):
3035 3035 """restore individual files or directories to an earlier state
3036 3036
3037 3037 NOTE: This command is most likely not what you are looking for. revert
3038 3038 will partially overwrite content in the working directory without changing
3039 3039 the working directory parents. Use :hg:`update -r rev` to check out earlier
3040 3040 revisions, or :hg:`update --clean .` to undo a merge which has added
3041 3041 another parent.
3042 3042
3043 3043 With no revision specified, revert the named files or directories
3044 3044 to the contents they had in the parent of the working directory.
3045 3045 This restores the contents of the affected files to an unmodified
3046 3046 state and unschedules adds, removes, copies, and renames. If the
3047 3047 working directory has two parents, you must explicitly specify a
3048 3048 revision.
3049 3049
3050 3050 Using the -r/--rev option, revert the given files or directories
3051 3051 to their contents as of a specific revision. This can be helpful
3052 3052 to "roll back" some or all of an earlier change. See :hg:`help
3053 3053 dates` for a list of formats valid for -d/--date.
3054 3054
3055 3055 Revert modifies the working directory. It does not commit any
3056 3056 changes, or change the parent of the working directory. If you
3057 3057 revert to a revision other than the parent of the working
3058 3058 directory, the reverted files will thus appear modified
3059 3059 afterwards.
3060 3060
3061 3061 If a file has been deleted, it is restored. If the executable mode
3062 3062 of a file was changed, it is reset.
3063 3063
3064 3064 If names are given, all files matching the names are reverted.
3065 3065 If no arguments are given, no files are reverted.
3066 3066
3067 3067 Modified files are saved with a .orig suffix before reverting.
3068 3068 To disable these backups, use --no-backup.
3069 3069
3070 3070 Returns 0 on success.
3071 3071 """
3072 3072
3073 3073 if opts["date"]:
3074 3074 if opts["rev"]:
3075 3075 raise util.Abort(_("you can't specify a revision and a date"))
3076 3076 opts["rev"] = cmdutil.finddate(ui, repo, opts["date"])
3077 3077
3078 3078 if not pats and not opts.get('all'):
3079 3079 raise util.Abort(_('no files or directories specified; '
3080 3080 'use --all to revert the whole repo'))
3081 3081
3082 3082 parent, p2 = repo.dirstate.parents()
3083 3083 if not opts.get('rev') and p2 != nullid:
3084 3084 raise util.Abort(_('uncommitted merge - please provide a '
3085 3085 'specific revision'))
3086 3086 ctx = repo[opts.get('rev')]
3087 3087 node = ctx.node()
3088 3088 mf = ctx.manifest()
3089 3089 if node == parent:
3090 3090 pmf = mf
3091 3091 else:
3092 3092 pmf = None
3093 3093
3094 3094 # need all matching names in dirstate and manifest of target rev,
3095 3095 # so have to walk both. do not print errors if files exist in one
3096 3096 # but not other.
3097 3097
3098 3098 names = {}
3099 3099
3100 3100 wlock = repo.wlock()
3101 3101 try:
3102 3102 # walk dirstate.
3103 3103
3104 3104 m = cmdutil.match(repo, pats, opts)
3105 3105 m.bad = lambda x, y: False
3106 3106 for abs in repo.walk(m):
3107 3107 names[abs] = m.rel(abs), m.exact(abs)
3108 3108
3109 3109 # walk target manifest.
3110 3110
3111 3111 def badfn(path, msg):
3112 3112 if path in names:
3113 3113 return
3114 3114 path_ = path + '/'
3115 3115 for f in names:
3116 3116 if f.startswith(path_):
3117 3117 return
3118 3118 ui.warn("%s: %s\n" % (m.rel(path), msg))
3119 3119
3120 3120 m = cmdutil.match(repo, pats, opts)
3121 3121 m.bad = badfn
3122 3122 for abs in repo[node].walk(m):
3123 3123 if abs not in names:
3124 3124 names[abs] = m.rel(abs), m.exact(abs)
3125 3125
3126 3126 m = cmdutil.matchfiles(repo, names)
3127 3127 changes = repo.status(match=m)[:4]
3128 3128 modified, added, removed, deleted = map(set, changes)
3129 3129
3130 3130 # if f is a rename, also revert the source
3131 3131 cwd = repo.getcwd()
3132 3132 for f in added:
3133 3133 src = repo.dirstate.copied(f)
3134 3134 if src and src not in names and repo.dirstate[src] == 'r':
3135 3135 removed.add(src)
3136 3136 names[src] = (repo.pathto(src, cwd), True)
3137 3137
3138 3138 def removeforget(abs):
3139 3139 if repo.dirstate[abs] == 'a':
3140 3140 return _('forgetting %s\n')
3141 3141 return _('removing %s\n')
3142 3142
3143 3143 revert = ([], _('reverting %s\n'))
3144 3144 add = ([], _('adding %s\n'))
3145 3145 remove = ([], removeforget)
3146 3146 undelete = ([], _('undeleting %s\n'))
3147 3147
3148 3148 disptable = (
3149 3149 # dispatch table:
3150 3150 # file state
3151 3151 # action if in target manifest
3152 3152 # action if not in target manifest
3153 3153 # make backup if in target manifest
3154 3154 # make backup if not in target manifest
3155 3155 (modified, revert, remove, True, True),
3156 3156 (added, revert, remove, True, False),
3157 3157 (removed, undelete, None, False, False),
3158 3158 (deleted, revert, remove, False, False),
3159 3159 )
3160 3160
3161 3161 for abs, (rel, exact) in sorted(names.items()):
3162 3162 mfentry = mf.get(abs)
3163 3163 target = repo.wjoin(abs)
3164 3164 def handle(xlist, dobackup):
3165 3165 xlist[0].append(abs)
3166 3166 if dobackup and not opts.get('no_backup') and util.lexists(target):
3167 3167 bakname = "%s.orig" % rel
3168 3168 ui.note(_('saving current version of %s as %s\n') %
3169 3169 (rel, bakname))
3170 3170 if not opts.get('dry_run'):
3171 3171 util.rename(target, bakname)
3172 3172 if ui.verbose or not exact:
3173 3173 msg = xlist[1]
3174 3174 if not isinstance(msg, basestring):
3175 3175 msg = msg(abs)
3176 3176 ui.status(msg % rel)
3177 3177 for table, hitlist, misslist, backuphit, backupmiss in disptable:
3178 3178 if abs not in table:
3179 3179 continue
3180 3180 # file has changed in dirstate
3181 3181 if mfentry:
3182 3182 handle(hitlist, backuphit)
3183 3183 elif misslist is not None:
3184 3184 handle(misslist, backupmiss)
3185 3185 break
3186 3186 else:
3187 3187 if abs not in repo.dirstate:
3188 3188 if mfentry:
3189 3189 handle(add, True)
3190 3190 elif exact:
3191 3191 ui.warn(_('file not managed: %s\n') % rel)
3192 3192 continue
3193 3193 # file has not changed in dirstate
3194 3194 if node == parent:
3195 3195 if exact:
3196 3196 ui.warn(_('no changes needed to %s\n') % rel)
3197 3197 continue
3198 3198 if pmf is None:
3199 3199 # only need parent manifest in this unlikely case,
3200 3200 # so do not read by default
3201 3201 pmf = repo[parent].manifest()
3202 3202 if abs in pmf:
3203 3203 if mfentry:
3204 3204 # if version of file is same in parent and target
3205 3205 # manifests, do nothing
3206 3206 if (pmf[abs] != mfentry or
3207 3207 pmf.flags(abs) != mf.flags(abs)):
3208 3208 handle(revert, False)
3209 3209 else:
3210 3210 handle(remove, False)
3211 3211
3212 3212 if not opts.get('dry_run'):
3213 3213 def checkout(f):
3214 3214 fc = ctx[f]
3215 3215 repo.wwrite(f, fc.data(), fc.flags())
3216 3216
3217 3217 audit_path = util.path_auditor(repo.root)
3218 3218 for f in remove[0]:
3219 3219 if repo.dirstate[f] == 'a':
3220 3220 repo.dirstate.forget(f)
3221 3221 continue
3222 3222 audit_path(f)
3223 3223 try:
3224 3224 util.unlink(repo.wjoin(f))
3225 3225 except OSError:
3226 3226 pass
3227 3227 repo.dirstate.remove(f)
3228 3228
3229 3229 normal = None
3230 3230 if node == parent:
3231 3231 # We're reverting to our parent. If possible, we'd like status
3232 3232 # to report the file as clean. We have to use normallookup for
3233 3233 # merges to avoid losing information about merged/dirty files.
3234 3234 if p2 != nullid:
3235 3235 normal = repo.dirstate.normallookup
3236 3236 else:
3237 3237 normal = repo.dirstate.normal
3238 3238 for f in revert[0]:
3239 3239 checkout(f)
3240 3240 if normal:
3241 3241 normal(f)
3242 3242
3243 3243 for f in add[0]:
3244 3244 checkout(f)
3245 3245 repo.dirstate.add(f)
3246 3246
3247 3247 normal = repo.dirstate.normallookup
3248 3248 if node == parent and p2 == nullid:
3249 3249 normal = repo.dirstate.normal
3250 3250 for f in undelete[0]:
3251 3251 checkout(f)
3252 3252 normal(f)
3253 3253
3254 3254 finally:
3255 3255 wlock.release()
3256 3256
3257 3257 def rollback(ui, repo, **opts):
3258 3258 """roll back the last transaction (dangerous)
3259 3259
3260 3260 This command should be used with care. There is only one level of
3261 3261 rollback, and there is no way to undo a rollback. It will also
3262 3262 restore the dirstate at the time of the last transaction, losing
3263 3263 any dirstate changes since that time. This command does not alter
3264 3264 the working directory.
3265 3265
3266 3266 Transactions are used to encapsulate the effects of all commands
3267 3267 that create new changesets or propagate existing changesets into a
3268 3268 repository. For example, the following commands are transactional,
3269 3269 and their effects can be rolled back:
3270 3270
3271 3271 - commit
3272 3272 - import
3273 3273 - pull
3274 3274 - push (with this repository as the destination)
3275 3275 - unbundle
3276 3276
3277 3277 This command is not intended for use on public repositories. Once
3278 3278 changes are visible for pull by other users, rolling a transaction
3279 3279 back locally is ineffective (someone else may already have pulled
3280 3280 the changes). Furthermore, a race is possible with readers of the
3281 3281 repository; for example an in-progress pull from the repository
3282 3282 may fail if a rollback is performed.
3283 3283
3284 3284 Returns 0 on success, 1 if no rollback data is available.
3285 3285 """
3286 3286 return repo.rollback(opts.get('dry_run'))
3287 3287
3288 3288 def root(ui, repo):
3289 3289 """print the root (top) of the current working directory
3290 3290
3291 3291 Print the root directory of the current repository.
3292 3292
3293 3293 Returns 0 on success.
3294 3294 """
3295 3295 ui.write(repo.root + "\n")
3296 3296
3297 3297 def serve(ui, repo, **opts):
3298 3298 """start stand-alone webserver
3299 3299
3300 3300 Start a local HTTP repository browser and pull server. You can use
3301 3301 this for ad-hoc sharing and browing of repositories. It is
3302 3302 recommended to use a real web server to serve a repository for
3303 3303 longer periods of time.
3304 3304
3305 3305 Please note that the server does not implement access control.
3306 3306 This means that, by default, anybody can read from the server and
3307 3307 nobody can write to it by default. Set the ``web.allow_push``
3308 3308 option to ``*`` to allow everybody to push to the server. You
3309 3309 should use a real web server if you need to authenticate users.
3310 3310
3311 3311 By default, the server logs accesses to stdout and errors to
3312 3312 stderr. Use the -A/--accesslog and -E/--errorlog options to log to
3313 3313 files.
3314 3314
3315 3315 To have the server choose a free port number to listen on, specify
3316 3316 a port number of 0; in this case, the server will print the port
3317 3317 number it uses.
3318 3318
3319 3319 Returns 0 on success.
3320 3320 """
3321 3321
3322 3322 if opts["stdio"]:
3323 3323 if repo is None:
3324 3324 raise error.RepoError(_("There is no Mercurial repository here"
3325 3325 " (.hg not found)"))
3326 3326 s = sshserver.sshserver(ui, repo)
3327 3327 s.serve_forever()
3328 3328
3329 3329 # this way we can check if something was given in the command-line
3330 3330 if opts.get('port'):
3331 3331 opts['port'] = int(opts.get('port'))
3332 3332
3333 3333 baseui = repo and repo.baseui or ui
3334 3334 optlist = ("name templates style address port prefix ipv6"
3335 3335 " accesslog errorlog certificate encoding")
3336 3336 for o in optlist.split():
3337 3337 val = opts.get(o, '')
3338 3338 if val in (None, ''): # should check against default options instead
3339 3339 continue
3340 3340 baseui.setconfig("web", o, val)
3341 3341 if repo and repo.ui != baseui:
3342 3342 repo.ui.setconfig("web", o, val)
3343 3343
3344 3344 o = opts.get('web_conf') or opts.get('webdir_conf')
3345 3345 if not o:
3346 3346 if not repo:
3347 3347 raise error.RepoError(_("There is no Mercurial repository"
3348 3348 " here (.hg not found)"))
3349 3349 o = repo.root
3350 3350
3351 3351 app = hgweb.hgweb(o, baseui=ui)
3352 3352
3353 3353 class service(object):
3354 3354 def init(self):
3355 3355 util.set_signal_handler()
3356 3356 self.httpd = hgweb.server.create_server(ui, app)
3357 3357
3358 3358 if opts['port'] and not ui.verbose:
3359 3359 return
3360 3360
3361 3361 if self.httpd.prefix:
3362 3362 prefix = self.httpd.prefix.strip('/') + '/'
3363 3363 else:
3364 3364 prefix = ''
3365 3365
3366 3366 port = ':%d' % self.httpd.port
3367 3367 if port == ':80':
3368 3368 port = ''
3369 3369
3370 3370 bindaddr = self.httpd.addr
3371 3371 if bindaddr == '0.0.0.0':
3372 3372 bindaddr = '*'
3373 3373 elif ':' in bindaddr: # IPv6
3374 3374 bindaddr = '[%s]' % bindaddr
3375 3375
3376 3376 fqaddr = self.httpd.fqaddr
3377 3377 if ':' in fqaddr:
3378 3378 fqaddr = '[%s]' % fqaddr
3379 3379 if opts['port']:
3380 3380 write = ui.status
3381 3381 else:
3382 3382 write = ui.write
3383 3383 write(_('listening at http://%s%s/%s (bound to %s:%d)\n') %
3384 3384 (fqaddr, port, prefix, bindaddr, self.httpd.port))
3385 3385
3386 3386 def run(self):
3387 3387 self.httpd.serve_forever()
3388 3388
3389 3389 service = service()
3390 3390
3391 3391 cmdutil.service(opts, initfn=service.init, runfn=service.run)
3392 3392
3393 3393 def status(ui, repo, *pats, **opts):
3394 3394 """show changed files in the working directory
3395 3395
3396 3396 Show status of files in the repository. If names are given, only
3397 3397 files that match are shown. Files that are clean or ignored or
3398 3398 the source of a copy/move operation, are not listed unless
3399 3399 -c/--clean, -i/--ignored, -C/--copies or -A/--all are given.
3400 3400 Unless options described with "show only ..." are given, the
3401 3401 options -mardu are used.
3402 3402
3403 3403 Option -q/--quiet hides untracked (unknown and ignored) files
3404 3404 unless explicitly requested with -u/--unknown or -i/--ignored.
3405 3405
3406 3406 NOTE: status may appear to disagree with diff if permissions have
3407 3407 changed or a merge has occurred. The standard diff format does not
3408 3408 report permission changes and diff only reports changes relative
3409 3409 to one merge parent.
3410 3410
3411 3411 If one revision is given, it is used as the base revision.
3412 3412 If two revisions are given, the differences between them are
3413 3413 shown. The --change option can also be used as a shortcut to list
3414 3414 the changed files of a revision from its first parent.
3415 3415
3416 3416 The codes used to show the status of files are::
3417 3417
3418 3418 M = modified
3419 3419 A = added
3420 3420 R = removed
3421 3421 C = clean
3422 3422 ! = missing (deleted by non-hg command, but still tracked)
3423 3423 ? = not tracked
3424 3424 I = ignored
3425 3425 = origin of the previous file listed as A (added)
3426 3426
3427 3427 Returns 0 on success.
3428 3428 """
3429 3429
3430 3430 revs = opts.get('rev')
3431 3431 change = opts.get('change')
3432 3432
3433 3433 if revs and change:
3434 3434 msg = _('cannot specify --rev and --change at the same time')
3435 3435 raise util.Abort(msg)
3436 3436 elif change:
3437 3437 node2 = repo.lookup(change)
3438 3438 node1 = repo[node2].parents()[0].node()
3439 3439 else:
3440 3440 node1, node2 = cmdutil.revpair(repo, revs)
3441 3441
3442 3442 cwd = (pats and repo.getcwd()) or ''
3443 3443 end = opts.get('print0') and '\0' or '\n'
3444 3444 copy = {}
3445 3445 states = 'modified added removed deleted unknown ignored clean'.split()
3446 3446 show = [k for k in states if opts.get(k)]
3447 3447 if opts.get('all'):
3448 3448 show += ui.quiet and (states[:4] + ['clean']) or states
3449 3449 if not show:
3450 3450 show = ui.quiet and states[:4] or states[:5]
3451 3451
3452 3452 stat = repo.status(node1, node2, cmdutil.match(repo, pats, opts),
3453 3453 'ignored' in show, 'clean' in show, 'unknown' in show)
3454 3454 changestates = zip(states, 'MAR!?IC', stat)
3455 3455
3456 3456 if (opts.get('all') or opts.get('copies')) and not opts.get('no_status'):
3457 3457 ctxn = repo[nullid]
3458 3458 ctx1 = repo[node1]
3459 3459 ctx2 = repo[node2]
3460 3460 added = stat[1]
3461 3461 if node2 is None:
3462 3462 added = stat[0] + stat[1] # merged?
3463 3463
3464 3464 for k, v in copies.copies(repo, ctx1, ctx2, ctxn)[0].iteritems():
3465 3465 if k in added:
3466 3466 copy[k] = v
3467 3467 elif v in added:
3468 3468 copy[v] = k
3469 3469
3470 3470 for state, char, files in changestates:
3471 3471 if state in show:
3472 3472 format = "%s %%s%s" % (char, end)
3473 3473 if opts.get('no_status'):
3474 3474 format = "%%s%s" % end
3475 3475
3476 3476 for f in files:
3477 3477 ui.write(format % repo.pathto(f, cwd),
3478 3478 label='status.' + state)
3479 3479 if f in copy:
3480 3480 ui.write(' %s%s' % (repo.pathto(copy[f], cwd), end),
3481 3481 label='status.copied')
3482 3482
3483 3483 def summary(ui, repo, **opts):
3484 3484 """summarize working directory state
3485 3485
3486 3486 This generates a brief summary of the working directory state,
3487 3487 including parents, branch, commit status, and available updates.
3488 3488
3489 3489 With the --remote option, this will check the default paths for
3490 3490 incoming and outgoing changes. This can be time-consuming.
3491 3491
3492 3492 Returns 0 on success.
3493 3493 """
3494 3494
3495 3495 ctx = repo[None]
3496 3496 parents = ctx.parents()
3497 3497 pnode = parents[0].node()
3498 3498
3499 3499 for p in parents:
3500 3500 # label with log.changeset (instead of log.parent) since this
3501 3501 # shows a working directory parent *changeset*:
3502 3502 ui.write(_('parent: %d:%s ') % (p.rev(), str(p)),
3503 3503 label='log.changeset')
3504 3504 ui.write(' '.join(p.tags()), label='log.tag')
3505 3505 if p.rev() == -1:
3506 3506 if not len(repo):
3507 3507 ui.write(_(' (empty repository)'))
3508 3508 else:
3509 3509 ui.write(_(' (no revision checked out)'))
3510 3510 ui.write('\n')
3511 3511 if p.description():
3512 3512 ui.status(' ' + p.description().splitlines()[0].strip() + '\n',
3513 3513 label='log.summary')
3514 3514
3515 3515 branch = ctx.branch()
3516 3516 bheads = repo.branchheads(branch)
3517 3517 m = _('branch: %s\n') % branch
3518 3518 if branch != 'default':
3519 3519 ui.write(m, label='log.branch')
3520 3520 else:
3521 3521 ui.status(m, label='log.branch')
3522 3522
3523 3523 st = list(repo.status(unknown=True))[:6]
3524 3524
3525 3525 c = repo.dirstate.copies()
3526 3526 copied, renamed = [], []
3527 3527 for d, s in c.iteritems():
3528 3528 if s in st[2]:
3529 3529 st[2].remove(s)
3530 3530 renamed.append(d)
3531 3531 else:
3532 3532 copied.append(d)
3533 3533 if d in st[1]:
3534 3534 st[1].remove(d)
3535 3535 st.insert(3, renamed)
3536 3536 st.insert(4, copied)
3537 3537
3538 3538 ms = mergemod.mergestate(repo)
3539 3539 st.append([f for f in ms if ms[f] == 'u'])
3540 3540
3541 3541 subs = [s for s in ctx.substate if ctx.sub(s).dirty()]
3542 3542 st.append(subs)
3543 3543
3544 3544 labels = [ui.label(_('%d modified'), 'status.modified'),
3545 3545 ui.label(_('%d added'), 'status.added'),
3546 3546 ui.label(_('%d removed'), 'status.removed'),
3547 3547 ui.label(_('%d renamed'), 'status.copied'),
3548 3548 ui.label(_('%d copied'), 'status.copied'),
3549 3549 ui.label(_('%d deleted'), 'status.deleted'),
3550 3550 ui.label(_('%d unknown'), 'status.unknown'),
3551 3551 ui.label(_('%d ignored'), 'status.ignored'),
3552 3552 ui.label(_('%d unresolved'), 'resolve.unresolved'),
3553 3553 ui.label(_('%d subrepos'), 'status.modified')]
3554 3554 t = []
3555 3555 for s, l in zip(st, labels):
3556 3556 if s:
3557 3557 t.append(l % len(s))
3558 3558
3559 3559 t = ', '.join(t)
3560 3560 cleanworkdir = False
3561 3561
3562 3562 if len(parents) > 1:
3563 3563 t += _(' (merge)')
3564 3564 elif branch != parents[0].branch():
3565 3565 t += _(' (new branch)')
3566 3566 elif (parents[0].extra().get('close') and
3567 3567 pnode in repo.branchheads(branch, closed=True)):
3568 3568 t += _(' (head closed)')
3569 3569 elif not (st[0] or st[1] or st[2] or st[3] or st[4] or st[9]):
3570 3570 t += _(' (clean)')
3571 3571 cleanworkdir = True
3572 3572 elif pnode not in bheads:
3573 3573 t += _(' (new branch head)')
3574 3574
3575 3575 if cleanworkdir:
3576 3576 ui.status(_('commit: %s\n') % t.strip())
3577 3577 else:
3578 3578 ui.write(_('commit: %s\n') % t.strip())
3579 3579
3580 3580 # all ancestors of branch heads - all ancestors of parent = new csets
3581 3581 new = [0] * len(repo)
3582 3582 cl = repo.changelog
3583 3583 for a in [cl.rev(n) for n in bheads]:
3584 3584 new[a] = 1
3585 3585 for a in cl.ancestors(*[cl.rev(n) for n in bheads]):
3586 3586 new[a] = 1
3587 3587 for a in [p.rev() for p in parents]:
3588 3588 if a >= 0:
3589 3589 new[a] = 0
3590 3590 for a in cl.ancestors(*[p.rev() for p in parents]):
3591 3591 new[a] = 0
3592 3592 new = sum(new)
3593 3593
3594 3594 if new == 0:
3595 3595 ui.status(_('update: (current)\n'))
3596 3596 elif pnode not in bheads:
3597 3597 ui.write(_('update: %d new changesets (update)\n') % new)
3598 3598 else:
3599 3599 ui.write(_('update: %d new changesets, %d branch heads (merge)\n') %
3600 3600 (new, len(bheads)))
3601 3601
3602 3602 if opts.get('remote'):
3603 3603 t = []
3604 3604 source, branches = hg.parseurl(ui.expandpath('default'))
3605 3605 other = hg.repository(hg.remoteui(repo, {}), source)
3606 3606 revs, checkout = hg.addbranchrevs(repo, other, branches, opts.get('rev'))
3607 3607 ui.debug('comparing with %s\n' % url.hidepassword(source))
3608 3608 repo.ui.pushbuffer()
3609 3609 common, incoming, rheads = discovery.findcommonincoming(repo, other)
3610 3610 repo.ui.popbuffer()
3611 3611 if incoming:
3612 3612 t.append(_('1 or more incoming'))
3613 3613
3614 3614 dest, branches = hg.parseurl(ui.expandpath('default-push', 'default'))
3615 3615 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
3616 3616 other = hg.repository(hg.remoteui(repo, {}), dest)
3617 3617 ui.debug('comparing with %s\n' % url.hidepassword(dest))
3618 3618 repo.ui.pushbuffer()
3619 3619 o = discovery.findoutgoing(repo, other)
3620 3620 repo.ui.popbuffer()
3621 3621 o = repo.changelog.nodesbetween(o, None)[0]
3622 3622 if o:
3623 3623 t.append(_('%d outgoing') % len(o))
3624 3624
3625 3625 if t:
3626 3626 ui.write(_('remote: %s\n') % (', '.join(t)))
3627 3627 else:
3628 3628 ui.status(_('remote: (synced)\n'))
3629 3629
3630 3630 def tag(ui, repo, name1, *names, **opts):
3631 3631 """add one or more tags for the current or given revision
3632 3632
3633 3633 Name a particular revision using <name>.
3634 3634
3635 3635 Tags are used to name particular revisions of the repository and are
3636 3636 very useful to compare different revisions, to go back to significant
3637 3637 earlier versions or to mark branch points as releases, etc.
3638 3638
3639 3639 If no revision is given, the parent of the working directory is
3640 3640 used, or tip if no revision is checked out.
3641 3641
3642 3642 To facilitate version control, distribution, and merging of tags,
3643 3643 they are stored as a file named ".hgtags" which is managed
3644 3644 similarly to other project files and can be hand-edited if
3645 3645 necessary. The file '.hg/localtags' is used for local tags (not
3646 3646 shared among repositories).
3647 3647
3648 3648 See :hg:`help dates` for a list of formats valid for -d/--date.
3649 3649
3650 3650 Since tag names have priority over branch names during revision
3651 3651 lookup, using an existing branch name as a tag name is discouraged.
3652 3652
3653 3653 Returns 0 on success.
3654 3654 """
3655 3655
3656 3656 rev_ = "."
3657 3657 names = [t.strip() for t in (name1,) + names]
3658 3658 if len(names) != len(set(names)):
3659 3659 raise util.Abort(_('tag names must be unique'))
3660 3660 for n in names:
3661 3661 if n in ['tip', '.', 'null']:
3662 3662 raise util.Abort(_('the name \'%s\' is reserved') % n)
3663 if not n:
3664 raise util.Abort(_('tag names cannot consist entirely of whitespace'))
3663 3665 if opts.get('rev') and opts.get('remove'):
3664 3666 raise util.Abort(_("--rev and --remove are incompatible"))
3665 3667 if opts.get('rev'):
3666 3668 rev_ = opts['rev']
3667 3669 message = opts.get('message')
3668 3670 if opts.get('remove'):
3669 3671 expectedtype = opts.get('local') and 'local' or 'global'
3670 3672 for n in names:
3671 3673 if not repo.tagtype(n):
3672 3674 raise util.Abort(_('tag \'%s\' does not exist') % n)
3673 3675 if repo.tagtype(n) != expectedtype:
3674 3676 if expectedtype == 'global':
3675 3677 raise util.Abort(_('tag \'%s\' is not a global tag') % n)
3676 3678 else:
3677 3679 raise util.Abort(_('tag \'%s\' is not a local tag') % n)
3678 3680 rev_ = nullid
3679 3681 if not message:
3680 3682 # we don't translate commit messages
3681 3683 message = 'Removed tag %s' % ', '.join(names)
3682 3684 elif not opts.get('force'):
3683 3685 for n in names:
3684 3686 if n in repo.tags():
3685 3687 raise util.Abort(_('tag \'%s\' already exists '
3686 3688 '(use -f to force)') % n)
3687 3689 if not rev_ and repo.dirstate.parents()[1] != nullid:
3688 3690 raise util.Abort(_('uncommitted merge - please provide a '
3689 3691 'specific revision'))
3690 3692 r = repo[rev_].node()
3691 3693
3692 3694 if not message:
3693 3695 # we don't translate commit messages
3694 3696 message = ('Added tag %s for changeset %s' %
3695 3697 (', '.join(names), short(r)))
3696 3698
3697 3699 date = opts.get('date')
3698 3700 if date:
3699 3701 date = util.parsedate(date)
3700 3702
3701 3703 if opts.get('edit'):
3702 3704 message = ui.edit(message, ui.username())
3703 3705
3704 3706 repo.tag(names, r, message, opts.get('local'), opts.get('user'), date)
3705 3707
3706 3708 def tags(ui, repo):
3707 3709 """list repository tags
3708 3710
3709 3711 This lists both regular and local tags. When the -v/--verbose
3710 3712 switch is used, a third column "local" is printed for local tags.
3711 3713
3712 3714 Returns 0 on success.
3713 3715 """
3714 3716
3715 3717 hexfunc = ui.debugflag and hex or short
3716 3718 tagtype = ""
3717 3719
3718 3720 for t, n in reversed(repo.tagslist()):
3719 3721 if ui.quiet:
3720 3722 ui.write("%s\n" % t)
3721 3723 continue
3722 3724
3723 3725 try:
3724 3726 hn = hexfunc(n)
3725 3727 r = "%5d:%s" % (repo.changelog.rev(n), hn)
3726 3728 except error.LookupError:
3727 3729 r = " ?:%s" % hn
3728 3730 else:
3729 3731 spaces = " " * (30 - encoding.colwidth(t))
3730 3732 if ui.verbose:
3731 3733 if repo.tagtype(t) == 'local':
3732 3734 tagtype = " local"
3733 3735 else:
3734 3736 tagtype = ""
3735 3737 ui.write("%s%s %s%s\n" % (t, spaces, r, tagtype))
3736 3738
3737 3739 def tip(ui, repo, **opts):
3738 3740 """show the tip revision
3739 3741
3740 3742 The tip revision (usually just called the tip) is the changeset
3741 3743 most recently added to the repository (and therefore the most
3742 3744 recently changed head).
3743 3745
3744 3746 If you have just made a commit, that commit will be the tip. If
3745 3747 you have just pulled changes from another repository, the tip of
3746 3748 that repository becomes the current tip. The "tip" tag is special
3747 3749 and cannot be renamed or assigned to a different changeset.
3748 3750
3749 3751 Returns 0 on success.
3750 3752 """
3751 3753 displayer = cmdutil.show_changeset(ui, repo, opts)
3752 3754 displayer.show(repo[len(repo) - 1])
3753 3755 displayer.close()
3754 3756
3755 3757 def unbundle(ui, repo, fname1, *fnames, **opts):
3756 3758 """apply one or more changegroup files
3757 3759
3758 3760 Apply one or more compressed changegroup files generated by the
3759 3761 bundle command.
3760 3762
3761 3763 Returns 0 on success, 1 if an update has unresolved files.
3762 3764 """
3763 3765 fnames = (fname1,) + fnames
3764 3766
3765 3767 lock = repo.lock()
3766 3768 try:
3767 3769 for fname in fnames:
3768 3770 f = url.open(ui, fname)
3769 3771 gen = changegroup.readbundle(f, fname)
3770 3772 modheads = repo.addchangegroup(gen, 'unbundle', 'bundle:' + fname,
3771 3773 lock=lock)
3772 3774 finally:
3773 3775 lock.release()
3774 3776
3775 3777 return postincoming(ui, repo, modheads, opts.get('update'), None)
3776 3778
3777 3779 def update(ui, repo, node=None, rev=None, clean=False, date=None, check=False):
3778 3780 """update working directory (or switch revisions)
3779 3781
3780 3782 Update the repository's working directory to the specified
3781 3783 changeset.
3782 3784
3783 3785 If no changeset is specified, attempt to update to the tip of the
3784 3786 current branch. If this changeset is a descendant of the working
3785 3787 directory's parent, update to it, otherwise abort.
3786 3788
3787 3789 The following rules apply when the working directory contains
3788 3790 uncommitted changes:
3789 3791
3790 3792 1. If neither -c/--check nor -C/--clean is specified, and if
3791 3793 the requested changeset is an ancestor or descendant of
3792 3794 the working directory's parent, the uncommitted changes
3793 3795 are merged into the requested changeset and the merged
3794 3796 result is left uncommitted. If the requested changeset is
3795 3797 not an ancestor or descendant (that is, it is on another
3796 3798 branch), the update is aborted and the uncommitted changes
3797 3799 are preserved.
3798 3800
3799 3801 2. With the -c/--check option, the update is aborted and the
3800 3802 uncommitted changes are preserved.
3801 3803
3802 3804 3. With the -C/--clean option, uncommitted changes are discarded and
3803 3805 the working directory is updated to the requested changeset.
3804 3806
3805 3807 Use null as the changeset to remove the working directory (like
3806 3808 :hg:`clone -U`).
3807 3809
3808 3810 If you want to update just one file to an older changeset, use :hg:`revert`.
3809 3811
3810 3812 See :hg:`help dates` for a list of formats valid for -d/--date.
3811 3813
3812 3814 Returns 0 on success, 1 if there are unresolved files.
3813 3815 """
3814 3816 if rev and node:
3815 3817 raise util.Abort(_("please specify just one revision"))
3816 3818
3817 3819 if not rev:
3818 3820 rev = node
3819 3821
3820 3822 if check and clean:
3821 3823 raise util.Abort(_("cannot specify both -c/--check and -C/--clean"))
3822 3824
3823 3825 if check:
3824 3826 # we could use dirty() but we can ignore merge and branch trivia
3825 3827 c = repo[None]
3826 3828 if c.modified() or c.added() or c.removed():
3827 3829 raise util.Abort(_("uncommitted local changes"))
3828 3830
3829 3831 if date:
3830 3832 if rev:
3831 3833 raise util.Abort(_("you can't specify a revision and a date"))
3832 3834 rev = cmdutil.finddate(ui, repo, date)
3833 3835
3834 3836 if clean or check:
3835 3837 return hg.clean(repo, rev)
3836 3838 else:
3837 3839 return hg.update(repo, rev)
3838 3840
3839 3841 def verify(ui, repo):
3840 3842 """verify the integrity of the repository
3841 3843
3842 3844 Verify the integrity of the current repository.
3843 3845
3844 3846 This will perform an extensive check of the repository's
3845 3847 integrity, validating the hashes and checksums of each entry in
3846 3848 the changelog, manifest, and tracked files, as well as the
3847 3849 integrity of their crosslinks and indices.
3848 3850
3849 3851 Returns 0 on success, 1 if errors are encountered.
3850 3852 """
3851 3853 return hg.verify(repo)
3852 3854
3853 3855 def version_(ui):
3854 3856 """output version and copyright information"""
3855 3857 ui.write(_("Mercurial Distributed SCM (version %s)\n")
3856 3858 % util.version())
3857 3859 ui.status(_(
3858 3860 "\nCopyright (C) 2005-2010 Matt Mackall <mpm@selenic.com> and others\n"
3859 3861 "This is free software; see the source for copying conditions. "
3860 3862 "There is NO\nwarranty; "
3861 3863 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
3862 3864 ))
3863 3865
3864 3866 # Command options and aliases are listed here, alphabetically
3865 3867
3866 3868 globalopts = [
3867 3869 ('R', 'repository', '',
3868 3870 _('repository root directory or name of overlay bundle file'),
3869 3871 _('REPO')),
3870 3872 ('', 'cwd', '',
3871 3873 _('change working directory'), _('DIR')),
3872 3874 ('y', 'noninteractive', None,
3873 3875 _('do not prompt, assume \'yes\' for any required answers')),
3874 3876 ('q', 'quiet', None, _('suppress output')),
3875 3877 ('v', 'verbose', None, _('enable additional output')),
3876 3878 ('', 'config', [],
3877 3879 _('set/override config option (use \'section.name=value\')'),
3878 3880 _('CONFIG')),
3879 3881 ('', 'debug', None, _('enable debugging output')),
3880 3882 ('', 'debugger', None, _('start debugger')),
3881 3883 ('', 'encoding', encoding.encoding, _('set the charset encoding'),
3882 3884 _('ENCODE')),
3883 3885 ('', 'encodingmode', encoding.encodingmode,
3884 3886 _('set the charset encoding mode'), _('MODE')),
3885 3887 ('', 'traceback', None, _('always print a traceback on exception')),
3886 3888 ('', 'time', None, _('time how long the command takes')),
3887 3889 ('', 'profile', None, _('print command execution profile')),
3888 3890 ('', 'version', None, _('output version information and exit')),
3889 3891 ('h', 'help', None, _('display help and exit')),
3890 3892 ]
3891 3893
3892 3894 dryrunopts = [('n', 'dry-run', None,
3893 3895 _('do not perform actions, just print output'))]
3894 3896
3895 3897 remoteopts = [
3896 3898 ('e', 'ssh', '',
3897 3899 _('specify ssh command to use'), _('CMD')),
3898 3900 ('', 'remotecmd', '',
3899 3901 _('specify hg command to run on the remote side'), _('CMD')),
3900 3902 ]
3901 3903
3902 3904 walkopts = [
3903 3905 ('I', 'include', [],
3904 3906 _('include names matching the given patterns'), _('PATTERN')),
3905 3907 ('X', 'exclude', [],
3906 3908 _('exclude names matching the given patterns'), _('PATTERN')),
3907 3909 ]
3908 3910
3909 3911 commitopts = [
3910 3912 ('m', 'message', '',
3911 3913 _('use text as commit message'), _('TEXT')),
3912 3914 ('l', 'logfile', '',
3913 3915 _('read commit message from file'), _('FILE')),
3914 3916 ]
3915 3917
3916 3918 commitopts2 = [
3917 3919 ('d', 'date', '',
3918 3920 _('record datecode as commit date'), _('DATE')),
3919 3921 ('u', 'user', '',
3920 3922 _('record the specified user as committer'), _('USER')),
3921 3923 ]
3922 3924
3923 3925 templateopts = [
3924 3926 ('', 'style', '',
3925 3927 _('display using template map file'), _('STYLE')),
3926 3928 ('', 'template', '',
3927 3929 _('display with template'), _('TEMPLATE')),
3928 3930 ]
3929 3931
3930 3932 logopts = [
3931 3933 ('p', 'patch', None, _('show patch')),
3932 3934 ('g', 'git', None, _('use git extended diff format')),
3933 3935 ('l', 'limit', '',
3934 3936 _('limit number of changes displayed'), _('NUM')),
3935 3937 ('M', 'no-merges', None, _('do not show merges')),
3936 3938 ('', 'stat', None, _('output diffstat-style summary of changes')),
3937 3939 ] + templateopts
3938 3940
3939 3941 diffopts = [
3940 3942 ('a', 'text', None, _('treat all files as text')),
3941 3943 ('g', 'git', None, _('use git extended diff format')),
3942 3944 ('', 'nodates', None, _('omit dates from diff headers'))
3943 3945 ]
3944 3946
3945 3947 diffopts2 = [
3946 3948 ('p', 'show-function', None, _('show which function each change is in')),
3947 3949 ('', 'reverse', None, _('produce a diff that undoes the changes')),
3948 3950 ('w', 'ignore-all-space', None,
3949 3951 _('ignore white space when comparing lines')),
3950 3952 ('b', 'ignore-space-change', None,
3951 3953 _('ignore changes in the amount of white space')),
3952 3954 ('B', 'ignore-blank-lines', None,
3953 3955 _('ignore changes whose lines are all blank')),
3954 3956 ('U', 'unified', '',
3955 3957 _('number of lines of context to show'), _('NUM')),
3956 3958 ('', 'stat', None, _('output diffstat-style summary of changes')),
3957 3959 ]
3958 3960
3959 3961 similarityopts = [
3960 3962 ('s', 'similarity', '',
3961 3963 _('guess renamed files by similarity (0<=s<=100)'), _('SIMILARITY'))
3962 3964 ]
3963 3965
3964 3966 table = {
3965 3967 "^add": (add, walkopts + dryrunopts, _('[OPTION]... [FILE]...')),
3966 3968 "addremove":
3967 3969 (addremove, similarityopts + walkopts + dryrunopts,
3968 3970 _('[OPTION]... [FILE]...')),
3969 3971 "^annotate|blame":
3970 3972 (annotate,
3971 3973 [('r', 'rev', '',
3972 3974 _('annotate the specified revision'), _('REV')),
3973 3975 ('', 'follow', None,
3974 3976 _('follow copies/renames and list the filename (DEPRECATED)')),
3975 3977 ('', 'no-follow', None, _("don't follow copies and renames")),
3976 3978 ('a', 'text', None, _('treat all files as text')),
3977 3979 ('u', 'user', None, _('list the author (long with -v)')),
3978 3980 ('f', 'file', None, _('list the filename')),
3979 3981 ('d', 'date', None, _('list the date (short with -q)')),
3980 3982 ('n', 'number', None, _('list the revision number (default)')),
3981 3983 ('c', 'changeset', None, _('list the changeset')),
3982 3984 ('l', 'line-number', None,
3983 3985 _('show line number at the first appearance'))
3984 3986 ] + walkopts,
3985 3987 _('[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...')),
3986 3988 "archive":
3987 3989 (archive,
3988 3990 [('', 'no-decode', None, _('do not pass files through decoders')),
3989 3991 ('p', 'prefix', '',
3990 3992 _('directory prefix for files in archive'), _('PREFIX')),
3991 3993 ('r', 'rev', '',
3992 3994 _('revision to distribute'), _('REV')),
3993 3995 ('t', 'type', '',
3994 3996 _('type of distribution to create'), _('TYPE')),
3995 3997 ] + walkopts,
3996 3998 _('[OPTION]... DEST')),
3997 3999 "backout":
3998 4000 (backout,
3999 4001 [('', 'merge', None,
4000 4002 _('merge with old dirstate parent after backout')),
4001 4003 ('', 'parent', '',
4002 4004 _('parent to choose when backing out merge'), _('REV')),
4003 4005 ('r', 'rev', '',
4004 4006 _('revision to backout'), _('REV')),
4005 4007 ] + walkopts + commitopts + commitopts2,
4006 4008 _('[OPTION]... [-r] REV')),
4007 4009 "bisect":
4008 4010 (bisect,
4009 4011 [('r', 'reset', False, _('reset bisect state')),
4010 4012 ('g', 'good', False, _('mark changeset good')),
4011 4013 ('b', 'bad', False, _('mark changeset bad')),
4012 4014 ('s', 'skip', False, _('skip testing changeset')),
4013 4015 ('c', 'command', '',
4014 4016 _('use command to check changeset state'), _('CMD')),
4015 4017 ('U', 'noupdate', False, _('do not update to target'))],
4016 4018 _("[-gbsr] [-U] [-c CMD] [REV]")),
4017 4019 "branch":
4018 4020 (branch,
4019 4021 [('f', 'force', None,
4020 4022 _('set branch name even if it shadows an existing branch')),
4021 4023 ('C', 'clean', None, _('reset branch name to parent branch name'))],
4022 4024 _('[-fC] [NAME]')),
4023 4025 "branches":
4024 4026 (branches,
4025 4027 [('a', 'active', False,
4026 4028 _('show only branches that have unmerged heads')),
4027 4029 ('c', 'closed', False,
4028 4030 _('show normal and closed branches'))],
4029 4031 _('[-ac]')),
4030 4032 "bundle":
4031 4033 (bundle,
4032 4034 [('f', 'force', None,
4033 4035 _('run even when the destination is unrelated')),
4034 4036 ('r', 'rev', [],
4035 4037 _('a changeset intended to be added to the destination'),
4036 4038 _('REV')),
4037 4039 ('b', 'branch', [],
4038 4040 _('a specific branch you would like to bundle'),
4039 4041 _('BRANCH')),
4040 4042 ('', 'base', [],
4041 4043 _('a base changeset assumed to be available at the destination'),
4042 4044 _('REV')),
4043 4045 ('a', 'all', None, _('bundle all changesets in the repository')),
4044 4046 ('t', 'type', 'bzip2',
4045 4047 _('bundle compression type to use'), _('TYPE')),
4046 4048 ] + remoteopts,
4047 4049 _('[-f] [-t TYPE] [-a] [-r REV]... [--base REV]... FILE [DEST]')),
4048 4050 "cat":
4049 4051 (cat,
4050 4052 [('o', 'output', '',
4051 4053 _('print output to file with formatted name'), _('FORMAT')),
4052 4054 ('r', 'rev', '',
4053 4055 _('print the given revision'), _('REV')),
4054 4056 ('', 'decode', None, _('apply any matching decode filter')),
4055 4057 ] + walkopts,
4056 4058 _('[OPTION]... FILE...')),
4057 4059 "^clone":
4058 4060 (clone,
4059 4061 [('U', 'noupdate', None,
4060 4062 _('the clone will include an empty working copy (only a repository)')),
4061 4063 ('u', 'updaterev', '',
4062 4064 _('revision, tag or branch to check out'), _('REV')),
4063 4065 ('r', 'rev', [],
4064 4066 _('include the specified changeset'), _('REV')),
4065 4067 ('b', 'branch', [],
4066 4068 _('clone only the specified branch'), _('BRANCH')),
4067 4069 ('', 'pull', None, _('use pull protocol to copy metadata')),
4068 4070 ('', 'uncompressed', None,
4069 4071 _('use uncompressed transfer (fast over LAN)')),
4070 4072 ] + remoteopts,
4071 4073 _('[OPTION]... SOURCE [DEST]')),
4072 4074 "^commit|ci":
4073 4075 (commit,
4074 4076 [('A', 'addremove', None,
4075 4077 _('mark new/missing files as added/removed before committing')),
4076 4078 ('', 'close-branch', None,
4077 4079 _('mark a branch as closed, hiding it from the branch list')),
4078 4080 ] + walkopts + commitopts + commitopts2,
4079 4081 _('[OPTION]... [FILE]...')),
4080 4082 "copy|cp":
4081 4083 (copy,
4082 4084 [('A', 'after', None, _('record a copy that has already occurred')),
4083 4085 ('f', 'force', None,
4084 4086 _('forcibly copy over an existing managed file')),
4085 4087 ] + walkopts + dryrunopts,
4086 4088 _('[OPTION]... [SOURCE]... DEST')),
4087 4089 "debugancestor": (debugancestor, [], _('[INDEX] REV1 REV2')),
4088 4090 "debugbuilddag":
4089 4091 (debugbuilddag,
4090 4092 [('m', 'mergeable-file', None, _('add single file mergeable changes')),
4091 4093 ('a', 'appended-file', None, _('add single file all revs append to')),
4092 4094 ('o', 'overwritten-file', None, _('add single file all revs overwrite')),
4093 4095 ('n', 'new-file', None, _('add new file at each rev')),
4094 4096 ],
4095 4097 _('[OPTION]... TEXT')),
4096 4098 "debugcheckstate": (debugcheckstate, [], ''),
4097 4099 "debugcommands": (debugcommands, [], _('[COMMAND]')),
4098 4100 "debugcomplete":
4099 4101 (debugcomplete,
4100 4102 [('o', 'options', None, _('show the command options'))],
4101 4103 _('[-o] CMD')),
4102 4104 "debugdag":
4103 4105 (debugdag,
4104 4106 [('t', 'tags', None, _('use tags as labels')),
4105 4107 ('b', 'branches', None, _('annotate with branch names')),
4106 4108 ('', 'dots', None, _('use dots for runs')),
4107 4109 ('s', 'spaces', None, _('separate elements by spaces')),
4108 4110 ],
4109 4111 _('[OPTION]... [FILE [REV]...]')),
4110 4112 "debugdate":
4111 4113 (debugdate,
4112 4114 [('e', 'extended', None, _('try extended date formats'))],
4113 4115 _('[-e] DATE [RANGE]')),
4114 4116 "debugdata": (debugdata, [], _('FILE REV')),
4115 4117 "debugfsinfo": (debugfsinfo, [], _('[PATH]')),
4116 4118 "debugindex": (debugindex, [], _('FILE')),
4117 4119 "debugindexdot": (debugindexdot, [], _('FILE')),
4118 4120 "debuginstall": (debuginstall, [], ''),
4119 4121 "debugpushkey": (debugpushkey, [], _('REPO NAMESPACE [KEY OLD NEW]')),
4120 4122 "debugrebuildstate":
4121 4123 (debugrebuildstate,
4122 4124 [('r', 'rev', '',
4123 4125 _('revision to rebuild to'), _('REV'))],
4124 4126 _('[-r REV] [REV]')),
4125 4127 "debugrename":
4126 4128 (debugrename,
4127 4129 [('r', 'rev', '',
4128 4130 _('revision to debug'), _('REV'))],
4129 4131 _('[-r REV] FILE')),
4130 4132 "debugrevspec":
4131 4133 (debugrevspec, [], ('REVSPEC')),
4132 4134 "debugsetparents":
4133 4135 (debugsetparents, [], _('REV1 [REV2]')),
4134 4136 "debugstate":
4135 4137 (debugstate,
4136 4138 [('', 'nodates', None, _('do not display the saved mtime'))],
4137 4139 _('[OPTION]...')),
4138 4140 "debugsub":
4139 4141 (debugsub,
4140 4142 [('r', 'rev', '',
4141 4143 _('revision to check'), _('REV'))],
4142 4144 _('[-r REV] [REV]')),
4143 4145 "debugwalk": (debugwalk, walkopts, _('[OPTION]... [FILE]...')),
4144 4146 "^diff":
4145 4147 (diff,
4146 4148 [('r', 'rev', [],
4147 4149 _('revision'), _('REV')),
4148 4150 ('c', 'change', '',
4149 4151 _('change made by revision'), _('REV'))
4150 4152 ] + diffopts + diffopts2 + walkopts,
4151 4153 _('[OPTION]... ([-c REV] | [-r REV1 [-r REV2]]) [FILE]...')),
4152 4154 "^export":
4153 4155 (export,
4154 4156 [('o', 'output', '',
4155 4157 _('print output to file with formatted name'), _('FORMAT')),
4156 4158 ('', 'switch-parent', None, _('diff against the second parent')),
4157 4159 ('r', 'rev', [],
4158 4160 _('revisions to export'), _('REV')),
4159 4161 ] + diffopts,
4160 4162 _('[OPTION]... [-o OUTFILESPEC] REV...')),
4161 4163 "^forget":
4162 4164 (forget,
4163 4165 [] + walkopts,
4164 4166 _('[OPTION]... FILE...')),
4165 4167 "grep":
4166 4168 (grep,
4167 4169 [('0', 'print0', None, _('end fields with NUL')),
4168 4170 ('', 'all', None, _('print all revisions that match')),
4169 4171 ('f', 'follow', None,
4170 4172 _('follow changeset history,'
4171 4173 ' or file history across copies and renames')),
4172 4174 ('i', 'ignore-case', None, _('ignore case when matching')),
4173 4175 ('l', 'files-with-matches', None,
4174 4176 _('print only filenames and revisions that match')),
4175 4177 ('n', 'line-number', None, _('print matching line numbers')),
4176 4178 ('r', 'rev', [],
4177 4179 _('only search files changed within revision range'), _('REV')),
4178 4180 ('u', 'user', None, _('list the author (long with -v)')),
4179 4181 ('d', 'date', None, _('list the date (short with -q)')),
4180 4182 ] + walkopts,
4181 4183 _('[OPTION]... PATTERN [FILE]...')),
4182 4184 "heads":
4183 4185 (heads,
4184 4186 [('r', 'rev', '',
4185 4187 _('show only heads which are descendants of REV'), _('REV')),
4186 4188 ('t', 'topo', False, _('show topological heads only')),
4187 4189 ('a', 'active', False,
4188 4190 _('show active branchheads only [DEPRECATED]')),
4189 4191 ('c', 'closed', False,
4190 4192 _('show normal and closed branch heads')),
4191 4193 ] + templateopts,
4192 4194 _('[-ac] [-r REV] [REV]...')),
4193 4195 "help": (help_, [], _('[TOPIC]')),
4194 4196 "identify|id":
4195 4197 (identify,
4196 4198 [('r', 'rev', '',
4197 4199 _('identify the specified revision'), _('REV')),
4198 4200 ('n', 'num', None, _('show local revision number')),
4199 4201 ('i', 'id', None, _('show global revision id')),
4200 4202 ('b', 'branch', None, _('show branch')),
4201 4203 ('t', 'tags', None, _('show tags'))],
4202 4204 _('[-nibt] [-r REV] [SOURCE]')),
4203 4205 "import|patch":
4204 4206 (import_,
4205 4207 [('p', 'strip', 1,
4206 4208 _('directory strip option for patch. This has the same '
4207 4209 'meaning as the corresponding patch option'),
4208 4210 _('NUM')),
4209 4211 ('b', 'base', '',
4210 4212 _('base path'), _('PATH')),
4211 4213 ('f', 'force', None,
4212 4214 _('skip check for outstanding uncommitted changes')),
4213 4215 ('', 'no-commit', None,
4214 4216 _("don't commit, just update the working directory")),
4215 4217 ('', 'exact', None,
4216 4218 _('apply patch to the nodes from which it was generated')),
4217 4219 ('', 'import-branch', None,
4218 4220 _('use any branch information in patch (implied by --exact)'))] +
4219 4221 commitopts + commitopts2 + similarityopts,
4220 4222 _('[OPTION]... PATCH...')),
4221 4223 "incoming|in":
4222 4224 (incoming,
4223 4225 [('f', 'force', None,
4224 4226 _('run even if remote repository is unrelated')),
4225 4227 ('n', 'newest-first', None, _('show newest record first')),
4226 4228 ('', 'bundle', '',
4227 4229 _('file to store the bundles into'), _('FILE')),
4228 4230 ('r', 'rev', [],
4229 4231 _('a remote changeset intended to be added'), _('REV')),
4230 4232 ('b', 'branch', [],
4231 4233 _('a specific branch you would like to pull'), _('BRANCH')),
4232 4234 ] + logopts + remoteopts,
4233 4235 _('[-p] [-n] [-M] [-f] [-r REV]...'
4234 4236 ' [--bundle FILENAME] [SOURCE]')),
4235 4237 "^init":
4236 4238 (init,
4237 4239 remoteopts,
4238 4240 _('[-e CMD] [--remotecmd CMD] [DEST]')),
4239 4241 "locate":
4240 4242 (locate,
4241 4243 [('r', 'rev', '',
4242 4244 _('search the repository as it is in REV'), _('REV')),
4243 4245 ('0', 'print0', None,
4244 4246 _('end filenames with NUL, for use with xargs')),
4245 4247 ('f', 'fullpath', None,
4246 4248 _('print complete paths from the filesystem root')),
4247 4249 ] + walkopts,
4248 4250 _('[OPTION]... [PATTERN]...')),
4249 4251 "^log|history":
4250 4252 (log,
4251 4253 [('f', 'follow', None,
4252 4254 _('follow changeset history,'
4253 4255 ' or file history across copies and renames')),
4254 4256 ('', 'follow-first', None,
4255 4257 _('only follow the first parent of merge changesets')),
4256 4258 ('d', 'date', '',
4257 4259 _('show revisions matching date spec'), _('DATE')),
4258 4260 ('C', 'copies', None, _('show copied files')),
4259 4261 ('k', 'keyword', [],
4260 4262 _('do case-insensitive search for a given text'), _('TEXT')),
4261 4263 ('r', 'rev', [],
4262 4264 _('show the specified revision or range'), _('REV')),
4263 4265 ('', 'removed', None, _('include revisions where files were removed')),
4264 4266 ('m', 'only-merges', None, _('show only merges')),
4265 4267 ('u', 'user', [],
4266 4268 _('revisions committed by user'), _('USER')),
4267 4269 ('', 'only-branch', [],
4268 4270 _('show only changesets within the given named branch (DEPRECATED)'),
4269 4271 _('BRANCH')),
4270 4272 ('b', 'branch', [],
4271 4273 _('show changesets within the given named branch'), _('BRANCH')),
4272 4274 ('P', 'prune', [],
4273 4275 _('do not display revision or any of its ancestors'), _('REV')),
4274 4276 ] + logopts + walkopts,
4275 4277 _('[OPTION]... [FILE]')),
4276 4278 "manifest":
4277 4279 (manifest,
4278 4280 [('r', 'rev', '',
4279 4281 _('revision to display'), _('REV'))],
4280 4282 _('[-r REV]')),
4281 4283 "^merge":
4282 4284 (merge,
4283 4285 [('f', 'force', None, _('force a merge with outstanding changes')),
4284 4286 ('r', 'rev', '',
4285 4287 _('revision to merge'), _('REV')),
4286 4288 ('P', 'preview', None,
4287 4289 _('review revisions to merge (no merge is performed)'))],
4288 4290 _('[-P] [-f] [[-r] REV]')),
4289 4291 "outgoing|out":
4290 4292 (outgoing,
4291 4293 [('f', 'force', None,
4292 4294 _('run even when the destination is unrelated')),
4293 4295 ('r', 'rev', [],
4294 4296 _('a changeset intended to be included in the destination'),
4295 4297 _('REV')),
4296 4298 ('n', 'newest-first', None, _('show newest record first')),
4297 4299 ('b', 'branch', [],
4298 4300 _('a specific branch you would like to push'), _('BRANCH')),
4299 4301 ] + logopts + remoteopts,
4300 4302 _('[-M] [-p] [-n] [-f] [-r REV]... [DEST]')),
4301 4303 "parents":
4302 4304 (parents,
4303 4305 [('r', 'rev', '',
4304 4306 _('show parents of the specified revision'), _('REV')),
4305 4307 ] + templateopts,
4306 4308 _('[-r REV] [FILE]')),
4307 4309 "paths": (paths, [], _('[NAME]')),
4308 4310 "^pull":
4309 4311 (pull,
4310 4312 [('u', 'update', None,
4311 4313 _('update to new branch head if changesets were pulled')),
4312 4314 ('f', 'force', None,
4313 4315 _('run even when remote repository is unrelated')),
4314 4316 ('r', 'rev', [],
4315 4317 _('a remote changeset intended to be added'), _('REV')),
4316 4318 ('b', 'branch', [],
4317 4319 _('a specific branch you would like to pull'), _('BRANCH')),
4318 4320 ] + remoteopts,
4319 4321 _('[-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]')),
4320 4322 "^push":
4321 4323 (push,
4322 4324 [('f', 'force', None, _('force push')),
4323 4325 ('r', 'rev', [],
4324 4326 _('a changeset intended to be included in the destination'),
4325 4327 _('REV')),
4326 4328 ('b', 'branch', [],
4327 4329 _('a specific branch you would like to push'), _('BRANCH')),
4328 4330 ('', 'new-branch', False, _('allow pushing a new branch')),
4329 4331 ] + remoteopts,
4330 4332 _('[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]')),
4331 4333 "recover": (recover, []),
4332 4334 "^remove|rm":
4333 4335 (remove,
4334 4336 [('A', 'after', None, _('record delete for missing files')),
4335 4337 ('f', 'force', None,
4336 4338 _('remove (and delete) file even if added or modified')),
4337 4339 ] + walkopts,
4338 4340 _('[OPTION]... FILE...')),
4339 4341 "rename|mv":
4340 4342 (rename,
4341 4343 [('A', 'after', None, _('record a rename that has already occurred')),
4342 4344 ('f', 'force', None,
4343 4345 _('forcibly copy over an existing managed file')),
4344 4346 ] + walkopts + dryrunopts,
4345 4347 _('[OPTION]... SOURCE... DEST')),
4346 4348 "resolve":
4347 4349 (resolve,
4348 4350 [('a', 'all', None, _('select all unresolved files')),
4349 4351 ('l', 'list', None, _('list state of files needing merge')),
4350 4352 ('m', 'mark', None, _('mark files as resolved')),
4351 4353 ('u', 'unmark', None, _('unmark files as resolved')),
4352 4354 ('n', 'no-status', None, _('hide status prefix'))]
4353 4355 + walkopts,
4354 4356 _('[OPTION]... [FILE]...')),
4355 4357 "revert":
4356 4358 (revert,
4357 4359 [('a', 'all', None, _('revert all changes when no arguments given')),
4358 4360 ('d', 'date', '',
4359 4361 _('tipmost revision matching date'), _('DATE')),
4360 4362 ('r', 'rev', '',
4361 4363 _('revert to the specified revision'), _('REV')),
4362 4364 ('', 'no-backup', None, _('do not save backup copies of files')),
4363 4365 ] + walkopts + dryrunopts,
4364 4366 _('[OPTION]... [-r REV] [NAME]...')),
4365 4367 "rollback": (rollback, dryrunopts),
4366 4368 "root": (root, []),
4367 4369 "^serve":
4368 4370 (serve,
4369 4371 [('A', 'accesslog', '',
4370 4372 _('name of access log file to write to'), _('FILE')),
4371 4373 ('d', 'daemon', None, _('run server in background')),
4372 4374 ('', 'daemon-pipefds', '',
4373 4375 _('used internally by daemon mode'), _('NUM')),
4374 4376 ('E', 'errorlog', '',
4375 4377 _('name of error log file to write to'), _('FILE')),
4376 4378 # use string type, then we can check if something was passed
4377 4379 ('p', 'port', '',
4378 4380 _('port to listen on (default: 8000)'), _('PORT')),
4379 4381 ('a', 'address', '',
4380 4382 _('address to listen on (default: all interfaces)'), _('ADDR')),
4381 4383 ('', 'prefix', '',
4382 4384 _('prefix path to serve from (default: server root)'), _('PREFIX')),
4383 4385 ('n', 'name', '',
4384 4386 _('name to show in web pages (default: working directory)'),
4385 4387 _('NAME')),
4386 4388 ('', 'web-conf', '',
4387 4389 _('name of the hgweb config file (serve more than one repository)'),
4388 4390 _('FILE')),
4389 4391 ('', 'webdir-conf', '',
4390 4392 _('name of the hgweb config file (DEPRECATED)'), _('FILE')),
4391 4393 ('', 'pid-file', '',
4392 4394 _('name of file to write process ID to'), _('FILE')),
4393 4395 ('', 'stdio', None, _('for remote clients')),
4394 4396 ('t', 'templates', '',
4395 4397 _('web templates to use'), _('TEMPLATE')),
4396 4398 ('', 'style', '',
4397 4399 _('template style to use'), _('STYLE')),
4398 4400 ('6', 'ipv6', None, _('use IPv6 in addition to IPv4')),
4399 4401 ('', 'certificate', '',
4400 4402 _('SSL certificate file'), _('FILE'))],
4401 4403 _('[OPTION]...')),
4402 4404 "showconfig|debugconfig":
4403 4405 (showconfig,
4404 4406 [('u', 'untrusted', None, _('show untrusted configuration options'))],
4405 4407 _('[-u] [NAME]...')),
4406 4408 "^summary|sum":
4407 4409 (summary,
4408 4410 [('', 'remote', None, _('check for push and pull'))], '[--remote]'),
4409 4411 "^status|st":
4410 4412 (status,
4411 4413 [('A', 'all', None, _('show status of all files')),
4412 4414 ('m', 'modified', None, _('show only modified files')),
4413 4415 ('a', 'added', None, _('show only added files')),
4414 4416 ('r', 'removed', None, _('show only removed files')),
4415 4417 ('d', 'deleted', None, _('show only deleted (but tracked) files')),
4416 4418 ('c', 'clean', None, _('show only files without changes')),
4417 4419 ('u', 'unknown', None, _('show only unknown (not tracked) files')),
4418 4420 ('i', 'ignored', None, _('show only ignored files')),
4419 4421 ('n', 'no-status', None, _('hide status prefix')),
4420 4422 ('C', 'copies', None, _('show source of copied files')),
4421 4423 ('0', 'print0', None,
4422 4424 _('end filenames with NUL, for use with xargs')),
4423 4425 ('', 'rev', [],
4424 4426 _('show difference from revision'), _('REV')),
4425 4427 ('', 'change', '',
4426 4428 _('list the changed files of a revision'), _('REV')),
4427 4429 ] + walkopts,
4428 4430 _('[OPTION]... [FILE]...')),
4429 4431 "tag":
4430 4432 (tag,
4431 4433 [('f', 'force', None, _('replace existing tag')),
4432 4434 ('l', 'local', None, _('make the tag local')),
4433 4435 ('r', 'rev', '',
4434 4436 _('revision to tag'), _('REV')),
4435 4437 ('', 'remove', None, _('remove a tag')),
4436 4438 # -l/--local is already there, commitopts cannot be used
4437 4439 ('e', 'edit', None, _('edit commit message')),
4438 4440 ('m', 'message', '',
4439 4441 _('use <text> as commit message'), _('TEXT')),
4440 4442 ] + commitopts2,
4441 4443 _('[-f] [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME...')),
4442 4444 "tags": (tags, [], ''),
4443 4445 "tip":
4444 4446 (tip,
4445 4447 [('p', 'patch', None, _('show patch')),
4446 4448 ('g', 'git', None, _('use git extended diff format')),
4447 4449 ] + templateopts,
4448 4450 _('[-p] [-g]')),
4449 4451 "unbundle":
4450 4452 (unbundle,
4451 4453 [('u', 'update', None,
4452 4454 _('update to new branch head if changesets were unbundled'))],
4453 4455 _('[-u] FILE...')),
4454 4456 "^update|up|checkout|co":
4455 4457 (update,
4456 4458 [('C', 'clean', None, _('discard uncommitted changes (no backup)')),
4457 4459 ('c', 'check', None, _('check for uncommitted changes')),
4458 4460 ('d', 'date', '',
4459 4461 _('tipmost revision matching date'), _('DATE')),
4460 4462 ('r', 'rev', '',
4461 4463 _('revision'), _('REV'))],
4462 4464 _('[-c] [-C] [-d DATE] [[-r] REV]')),
4463 4465 "verify": (verify, []),
4464 4466 "version": (version_, []),
4465 4467 }
4466 4468
4467 4469 norepo = ("clone init version help debugcommands debugcomplete debugdata"
4468 4470 " debugindex debugindexdot debugdate debuginstall debugfsinfo"
4469 4471 " debugpushkey")
4470 4472 optionalrepo = ("identify paths serve showconfig debugancestor debugdag")
@@ -1,536 +1,541 b''
1 1 # dispatch.py - command dispatching for mercurial
2 2 #
3 3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from i18n import _
9 9 import os, sys, atexit, signal, pdb, socket, errno, shlex, time
10 10 import util, commands, hg, fancyopts, extensions, hook, error
11 11 import cmdutil, encoding
12 12 import ui as uimod
13 13
14 14 def run():
15 15 "run the command in sys.argv"
16 16 sys.exit(dispatch(sys.argv[1:]))
17 17
18 18 def dispatch(args):
19 19 "run the command specified in args"
20 20 try:
21 21 u = uimod.ui()
22 22 if '--traceback' in args:
23 23 u.setconfig('ui', 'traceback', 'on')
24 24 except util.Abort, inst:
25 25 sys.stderr.write(_("abort: %s\n") % inst)
26 26 return -1
27 27 except error.ParseError, inst:
28 28 if len(inst.args) > 1:
29 29 sys.stderr.write(_("hg: parse error at %s: %s\n") %
30 30 (inst.args[1], inst.args[0]))
31 31 else:
32 32 sys.stderr.write(_("hg: parse error: %s\n") % inst.args[0])
33 33 return -1
34 34 return _runcatch(u, args)
35 35
36 36 def _runcatch(ui, args):
37 37 def catchterm(*args):
38 38 raise error.SignalInterrupt
39 39
40 40 try:
41 41 for name in 'SIGBREAK', 'SIGHUP', 'SIGTERM':
42 42 num = getattr(signal, name, None)
43 43 if num:
44 44 signal.signal(num, catchterm)
45 45 except ValueError:
46 46 pass # happens if called in a thread
47 47
48 48 try:
49 49 try:
50 50 # enter the debugger before command execution
51 51 if '--debugger' in args:
52 52 pdb.set_trace()
53 53 try:
54 54 return _dispatch(ui, args)
55 55 finally:
56 56 ui.flush()
57 57 except:
58 58 # enter the debugger when we hit an exception
59 59 if '--debugger' in args:
60 60 pdb.post_mortem(sys.exc_info()[2])
61 61 ui.traceback()
62 62 raise
63 63
64 64 # Global exception handling, alphabetically
65 65 # Mercurial-specific first, followed by built-in and library exceptions
66 66 except error.AmbiguousCommand, inst:
67 67 ui.warn(_("hg: command '%s' is ambiguous:\n %s\n") %
68 68 (inst.args[0], " ".join(inst.args[1])))
69 69 except error.ParseError, inst:
70 70 if len(inst.args) > 1:
71 71 ui.warn(_("hg: parse error at %s: %s\n") %
72 72 (inst.args[1], inst.args[0]))
73 73 else:
74 74 ui.warn(_("hg: parse error: %s\n") % inst.args[0])
75 75 return -1
76 76 except error.LockHeld, inst:
77 77 if inst.errno == errno.ETIMEDOUT:
78 78 reason = _('timed out waiting for lock held by %s') % inst.locker
79 79 else:
80 80 reason = _('lock held by %s') % inst.locker
81 81 ui.warn(_("abort: %s: %s\n") % (inst.desc or inst.filename, reason))
82 82 except error.LockUnavailable, inst:
83 83 ui.warn(_("abort: could not lock %s: %s\n") %
84 84 (inst.desc or inst.filename, inst.strerror))
85 85 except error.CommandError, inst:
86 86 if inst.args[0]:
87 87 ui.warn(_("hg %s: %s\n") % (inst.args[0], inst.args[1]))
88 88 commands.help_(ui, inst.args[0])
89 89 else:
90 90 ui.warn(_("hg: %s\n") % inst.args[1])
91 91 commands.help_(ui, 'shortlist')
92 92 except error.RepoError, inst:
93 93 ui.warn(_("abort: %s!\n") % inst)
94 94 except error.ResponseError, inst:
95 95 ui.warn(_("abort: %s") % inst.args[0])
96 96 if not isinstance(inst.args[1], basestring):
97 97 ui.warn(" %r\n" % (inst.args[1],))
98 98 elif not inst.args[1]:
99 99 ui.warn(_(" empty string\n"))
100 100 else:
101 101 ui.warn("\n%r\n" % util.ellipsis(inst.args[1]))
102 102 except error.RevlogError, inst:
103 103 ui.warn(_("abort: %s!\n") % inst)
104 104 except error.SignalInterrupt:
105 105 ui.warn(_("killed!\n"))
106 106 except error.UnknownCommand, inst:
107 107 ui.warn(_("hg: unknown command '%s'\n") % inst.args[0])
108 108 try:
109 109 # check if the command is in a disabled extension
110 110 # (but don't check for extensions themselves)
111 111 commands.help_(ui, inst.args[0], unknowncmd=True)
112 112 except error.UnknownCommand:
113 113 commands.help_(ui, 'shortlist')
114 114 except util.Abort, inst:
115 115 ui.warn(_("abort: %s\n") % inst)
116 116 except ImportError, inst:
117 117 ui.warn(_("abort: %s!\n") % inst)
118 118 m = str(inst).split()[-1]
119 119 if m in "mpatch bdiff".split():
120 120 ui.warn(_("(did you forget to compile extensions?)\n"))
121 121 elif m in "zlib".split():
122 122 ui.warn(_("(is your Python install correct?)\n"))
123 123 except IOError, inst:
124 124 if hasattr(inst, "code"):
125 125 ui.warn(_("abort: %s\n") % inst)
126 126 elif hasattr(inst, "reason"):
127 127 try: # usually it is in the form (errno, strerror)
128 128 reason = inst.reason.args[1]
129 129 except: # it might be anything, for example a string
130 130 reason = inst.reason
131 131 ui.warn(_("abort: error: %s\n") % reason)
132 132 elif hasattr(inst, "args") and inst.args[0] == errno.EPIPE:
133 133 if ui.debugflag:
134 134 ui.warn(_("broken pipe\n"))
135 135 elif getattr(inst, "strerror", None):
136 136 if getattr(inst, "filename", None):
137 137 ui.warn(_("abort: %s: %s\n") % (inst.strerror, inst.filename))
138 138 else:
139 139 ui.warn(_("abort: %s\n") % inst.strerror)
140 140 else:
141 141 raise
142 142 except OSError, inst:
143 143 if getattr(inst, "filename", None):
144 144 ui.warn(_("abort: %s: %s\n") % (inst.strerror, inst.filename))
145 145 else:
146 146 ui.warn(_("abort: %s\n") % inst.strerror)
147 147 except KeyboardInterrupt:
148 148 try:
149 149 ui.warn(_("interrupted!\n"))
150 150 except IOError, inst:
151 151 if inst.errno == errno.EPIPE:
152 152 if ui.debugflag:
153 153 ui.warn(_("\nbroken pipe\n"))
154 154 else:
155 155 raise
156 156 except MemoryError:
157 157 ui.warn(_("abort: out of memory\n"))
158 158 except SystemExit, inst:
159 159 # Commands shouldn't sys.exit directly, but give a return code.
160 160 # Just in case catch this and and pass exit code to caller.
161 161 return inst.code
162 162 except socket.error, inst:
163 163 ui.warn(_("abort: %s\n") % inst.args[-1])
164 164 except:
165 165 ui.warn(_("** unknown exception encountered, details follow\n"))
166 166 ui.warn(_("** report bug details to "
167 167 "http://mercurial.selenic.com/bts/\n"))
168 168 ui.warn(_("** or mercurial@selenic.com\n"))
169 169 ui.warn(_("** Python %s\n") % sys.version.replace('\n', ''))
170 170 ui.warn(_("** Mercurial Distributed SCM (version %s)\n")
171 171 % util.version())
172 172 ui.warn(_("** Extensions loaded: %s\n")
173 173 % ", ".join([x[0] for x in extensions.extensions()]))
174 174 raise
175 175
176 176 return -1
177 177
178 178 def aliasargs(fn):
179 179 if hasattr(fn, 'args'):
180 180 return fn.args
181 181 return []
182 182
183 183 class cmdalias(object):
184 184 def __init__(self, name, definition, cmdtable):
185 185 self.name = name
186 186 self.definition = definition
187 187 self.args = []
188 188 self.opts = []
189 189 self.help = ''
190 190 self.norepo = True
191 191 self.badalias = False
192 192
193 193 try:
194 194 cmdutil.findcmd(self.name, cmdtable, True)
195 195 self.shadows = True
196 196 except error.UnknownCommand:
197 197 self.shadows = False
198 198
199 199 if not self.definition:
200 200 def fn(ui, *args):
201 201 ui.warn(_("no definition for alias '%s'\n") % self.name)
202 202 return 1
203 203 self.fn = fn
204 204 self.badalias = True
205 205
206 206 return
207 207
208 208 args = shlex.split(self.definition)
209 209 cmd = args.pop(0)
210 210 args = map(util.expandpath, args)
211 211
212 212 try:
213 213 tableentry = cmdutil.findcmd(cmd, cmdtable, False)[1]
214 214 if len(tableentry) > 2:
215 215 self.fn, self.opts, self.help = tableentry
216 216 else:
217 217 self.fn, self.opts = tableentry
218 218
219 219 self.args = aliasargs(self.fn) + args
220 220 if cmd not in commands.norepo.split(' '):
221 221 self.norepo = False
222 222 if self.help.startswith("hg " + cmd):
223 223 # drop prefix in old-style help lines so hg shows the alias
224 224 self.help = self.help[4 + len(cmd):]
225 225 self.__doc__ = self.fn.__doc__
226 226
227 227 except error.UnknownCommand:
228 228 def fn(ui, *args):
229 229 ui.warn(_("alias '%s' resolves to unknown command '%s'\n") \
230 230 % (self.name, cmd))
231 231 try:
232 232 # check if the command is in a disabled extension
233 233 commands.help_(ui, cmd, unknowncmd=True)
234 234 except error.UnknownCommand:
235 235 pass
236 236 return 1
237 237 self.fn = fn
238 238 self.badalias = True
239 239 except error.AmbiguousCommand:
240 240 def fn(ui, *args):
241 241 ui.warn(_("alias '%s' resolves to ambiguous command '%s'\n") \
242 242 % (self.name, cmd))
243 243 return 1
244 244 self.fn = fn
245 245 self.badalias = True
246 246
247 247 def __call__(self, ui, *args, **opts):
248 248 if self.shadows:
249 249 ui.debug("alias '%s' shadows command\n" % self.name)
250 250
251 return self.fn(ui, *args, **opts)
251 return util.checksignature(self.fn)(ui, *args, **opts)
252 252
253 253 def addaliases(ui, cmdtable):
254 254 # aliases are processed after extensions have been loaded, so they
255 255 # may use extension commands. Aliases can also use other alias definitions,
256 256 # but only if they have been defined prior to the current definition.
257 257 for alias, definition in ui.configitems('alias'):
258 258 aliasdef = cmdalias(alias, definition, cmdtable)
259 259 cmdtable[alias] = (aliasdef, aliasdef.opts, aliasdef.help)
260 260 if aliasdef.norepo:
261 261 commands.norepo += ' %s' % alias
262 262
263 263 def _parse(ui, args):
264 264 options = {}
265 265 cmdoptions = {}
266 266
267 267 try:
268 268 args = fancyopts.fancyopts(args, commands.globalopts, options)
269 269 except fancyopts.getopt.GetoptError, inst:
270 270 raise error.CommandError(None, inst)
271 271
272 272 if args:
273 273 cmd, args = args[0], args[1:]
274 274 aliases, entry = cmdutil.findcmd(cmd, commands.table,
275 275 ui.config("ui", "strict"))
276 276 cmd = aliases[0]
277 277 args = aliasargs(entry[0]) + args
278 278 defaults = ui.config("defaults", cmd)
279 279 if defaults:
280 280 args = map(util.expandpath, shlex.split(defaults)) + args
281 281 c = list(entry[1])
282 282 else:
283 283 cmd = None
284 284 c = []
285 285
286 286 # combine global options into local
287 287 for o in commands.globalopts:
288 288 c.append((o[0], o[1], options[o[1]], o[3]))
289 289
290 290 try:
291 291 args = fancyopts.fancyopts(args, c, cmdoptions, True)
292 292 except fancyopts.getopt.GetoptError, inst:
293 293 raise error.CommandError(cmd, inst)
294 294
295 295 # separate global options back out
296 296 for o in commands.globalopts:
297 297 n = o[1]
298 298 options[n] = cmdoptions[n]
299 299 del cmdoptions[n]
300 300
301 301 return (cmd, cmd and entry[0] or None, args, options, cmdoptions)
302 302
303 303 def _parseconfig(ui, config):
304 304 """parse the --config options from the command line"""
305 305 for cfg in config:
306 306 try:
307 307 name, value = cfg.split('=', 1)
308 308 section, name = name.split('.', 1)
309 309 if not section or not name:
310 310 raise IndexError
311 311 ui.setconfig(section, name, value)
312 312 except (IndexError, ValueError):
313 313 raise util.Abort(_('malformed --config option: %r '
314 314 '(use --config section.name=value)') % cfg)
315 315
316 316 def _earlygetopt(aliases, args):
317 317 """Return list of values for an option (or aliases).
318 318
319 319 The values are listed in the order they appear in args.
320 320 The options and values are removed from args.
321 321 """
322 322 try:
323 323 argcount = args.index("--")
324 324 except ValueError:
325 325 argcount = len(args)
326 326 shortopts = [opt for opt in aliases if len(opt) == 2]
327 327 values = []
328 328 pos = 0
329 329 while pos < argcount:
330 330 if args[pos] in aliases:
331 331 if pos + 1 >= argcount:
332 332 # ignore and let getopt report an error if there is no value
333 333 break
334 334 del args[pos]
335 335 values.append(args.pop(pos))
336 336 argcount -= 2
337 337 elif args[pos][:2] in shortopts:
338 338 # short option can have no following space, e.g. hg log -Rfoo
339 339 values.append(args.pop(pos)[2:])
340 340 argcount -= 1
341 341 else:
342 342 pos += 1
343 343 return values
344 344
345 345 def runcommand(lui, repo, cmd, fullargs, ui, options, d, cmdpats, cmdoptions):
346 346 # run pre-hook, and abort if it fails
347 347 ret = hook.hook(lui, repo, "pre-%s" % cmd, False, args=" ".join(fullargs),
348 348 pats=cmdpats, opts=cmdoptions)
349 349 if ret:
350 350 return ret
351 351 ret = _runcommand(ui, options, cmd, d)
352 352 # run post-hook, passing command result
353 353 hook.hook(lui, repo, "post-%s" % cmd, False, args=" ".join(fullargs),
354 354 result=ret, pats=cmdpats, opts=cmdoptions)
355 355 return ret
356 356
357 357 _loaded = set()
358 358 def _dispatch(ui, args):
359 359 # read --config before doing anything else
360 360 # (e.g. to change trust settings for reading .hg/hgrc)
361 361 _parseconfig(ui, _earlygetopt(['--config'], args))
362 362
363 363 # check for cwd
364 364 cwd = _earlygetopt(['--cwd'], args)
365 365 if cwd:
366 366 os.chdir(cwd[-1])
367 367
368 368 # read the local repository .hgrc into a local ui object
369 path = cmdutil.findrepo(os.getcwd()) or ""
369 try:
370 wd = os.getcwd()
371 except OSError, e:
372 raise util.Abort(_("error getting current working directory: %s") %
373 e.strerror)
374 path = cmdutil.findrepo(wd) or ""
370 375 if not path:
371 376 lui = ui
372 377 else:
373 378 try:
374 379 lui = ui.copy()
375 380 lui.readconfig(os.path.join(path, ".hg", "hgrc"))
376 381 except IOError:
377 382 pass
378 383
379 384 # now we can expand paths, even ones in .hg/hgrc
380 385 rpath = _earlygetopt(["-R", "--repository", "--repo"], args)
381 386 if rpath:
382 387 path = lui.expandpath(rpath[-1])
383 388 lui = ui.copy()
384 389 lui.readconfig(os.path.join(path, ".hg", "hgrc"))
385 390
386 391 # Configure extensions in phases: uisetup, extsetup, cmdtable, and
387 392 # reposetup. Programs like TortoiseHg will call _dispatch several
388 393 # times so we keep track of configured extensions in _loaded.
389 394 extensions.loadall(lui)
390 395 exts = [ext for ext in extensions.extensions() if ext[0] not in _loaded]
391 396 # Propagate any changes to lui.__class__ by extensions
392 397 ui.__class__ = lui.__class__
393 398
394 399 # (uisetup and extsetup are handled in extensions.loadall)
395 400
396 401 for name, module in exts:
397 402 cmdtable = getattr(module, 'cmdtable', {})
398 403 overrides = [cmd for cmd in cmdtable if cmd in commands.table]
399 404 if overrides:
400 405 ui.warn(_("extension '%s' overrides commands: %s\n")
401 406 % (name, " ".join(overrides)))
402 407 commands.table.update(cmdtable)
403 408 _loaded.add(name)
404 409
405 410 # (reposetup is handled in hg.repository)
406 411
407 412 addaliases(lui, commands.table)
408 413
409 414 # check for fallback encoding
410 415 fallback = lui.config('ui', 'fallbackencoding')
411 416 if fallback:
412 417 encoding.fallbackencoding = fallback
413 418
414 419 fullargs = args
415 420 cmd, func, args, options, cmdoptions = _parse(lui, args)
416 421
417 422 if options["config"]:
418 423 raise util.Abort(_("Option --config may not be abbreviated!"))
419 424 if options["cwd"]:
420 425 raise util.Abort(_("Option --cwd may not be abbreviated!"))
421 426 if options["repository"]:
422 427 raise util.Abort(_(
423 428 "Option -R has to be separated from other options (e.g. not -qR) "
424 429 "and --repository may only be abbreviated as --repo!"))
425 430
426 431 if options["encoding"]:
427 432 encoding.encoding = options["encoding"]
428 433 if options["encodingmode"]:
429 434 encoding.encodingmode = options["encodingmode"]
430 435 if options["time"]:
431 436 def get_times():
432 437 t = os.times()
433 438 if t[4] == 0.0: # Windows leaves this as zero, so use time.clock()
434 439 t = (t[0], t[1], t[2], t[3], time.clock())
435 440 return t
436 441 s = get_times()
437 442 def print_time():
438 443 t = get_times()
439 444 ui.warn(_("Time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n") %
440 445 (t[4]-s[4], t[0]-s[0], t[2]-s[2], t[1]-s[1], t[3]-s[3]))
441 446 atexit.register(print_time)
442 447
443 448 if options['verbose'] or options['debug'] or options['quiet']:
444 449 ui.setconfig('ui', 'verbose', str(bool(options['verbose'])))
445 450 ui.setconfig('ui', 'debug', str(bool(options['debug'])))
446 451 ui.setconfig('ui', 'quiet', str(bool(options['quiet'])))
447 452 if options['traceback']:
448 453 ui.setconfig('ui', 'traceback', 'on')
449 454 if options['noninteractive']:
450 455 ui.setconfig('ui', 'interactive', 'off')
451 456
452 457 if options['help']:
453 458 return commands.help_(ui, cmd, options['version'])
454 459 elif options['version']:
455 460 return commands.version_(ui)
456 461 elif not cmd:
457 462 return commands.help_(ui, 'shortlist')
458 463
459 464 repo = None
460 465 cmdpats = args[:]
461 466 if cmd not in commands.norepo.split():
462 467 try:
463 468 repo = hg.repository(ui, path=path)
464 469 ui = repo.ui
465 470 if not repo.local():
466 471 raise util.Abort(_("repository '%s' is not local") % path)
467 472 ui.setconfig("bundle", "mainreporoot", repo.root)
468 473 except error.RepoError:
469 474 if cmd not in commands.optionalrepo.split():
470 475 if args and not path: # try to infer -R from command args
471 476 repos = map(cmdutil.findrepo, args)
472 477 guess = repos[0]
473 478 if guess and repos.count(guess) == len(repos):
474 479 return _dispatch(ui, ['--repository', guess] + fullargs)
475 480 if not path:
476 481 raise error.RepoError(_("There is no Mercurial repository"
477 482 " here (.hg not found)"))
478 483 raise
479 484 args.insert(0, repo)
480 485 elif rpath:
481 486 ui.warn(_("warning: --repository ignored\n"))
482 487
483 488 d = lambda: util.checksignature(func)(ui, *args, **cmdoptions)
484 489 return runcommand(lui, repo, cmd, fullargs, ui, options, d,
485 490 cmdpats, cmdoptions)
486 491
487 492 def _runcommand(ui, options, cmd, cmdfunc):
488 493 def checkargs():
489 494 try:
490 495 return cmdfunc()
491 496 except error.SignatureError:
492 497 raise error.CommandError(cmd, _("invalid arguments"))
493 498
494 499 if options['profile']:
495 500 format = ui.config('profiling', 'format', default='text')
496 501
497 502 if not format in ['text', 'kcachegrind']:
498 503 ui.warn(_("unrecognized profiling format '%s'"
499 504 " - Ignored\n") % format)
500 505 format = 'text'
501 506
502 507 output = ui.config('profiling', 'output')
503 508
504 509 if output:
505 510 path = ui.expandpath(output)
506 511 ostream = open(path, 'wb')
507 512 else:
508 513 ostream = sys.stderr
509 514
510 515 try:
511 516 from mercurial import lsprof
512 517 except ImportError:
513 518 raise util.Abort(_(
514 519 'lsprof not available - install from '
515 520 'http://codespeak.net/svn/user/arigo/hack/misc/lsprof/'))
516 521 p = lsprof.Profiler()
517 522 p.enable(subcalls=True)
518 523 try:
519 524 return checkargs()
520 525 finally:
521 526 p.disable()
522 527
523 528 if format == 'kcachegrind':
524 529 import lsprofcalltree
525 530 calltree = lsprofcalltree.KCacheGrind(p)
526 531 calltree.output(ostream)
527 532 else:
528 533 # format == 'text'
529 534 stats = lsprof.Stats(p.getstats())
530 535 stats.sort()
531 536 stats.pprint(top=10, file=ostream, climit=5)
532 537
533 538 if output:
534 539 ostream.close()
535 540 else:
536 541 return checkargs()
@@ -1,352 +1,352 b''
1 1 # hgweb/hgwebdir_mod.py - Web interface for a directory of repositories.
2 2 #
3 3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
4 4 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
5 5 #
6 6 # This software may be used and distributed according to the terms of the
7 7 # GNU General Public License version 2 or any later version.
8 8
9 9 import os, re, time, urlparse
10 10 from mercurial.i18n import _
11 11 from mercurial import ui, hg, util, templater
12 12 from mercurial import error, encoding
13 13 from common import ErrorResponse, get_mtime, staticfile, paritygen, \
14 14 get_contact, HTTP_OK, HTTP_NOT_FOUND, HTTP_SERVER_ERROR
15 15 from hgweb_mod import hgweb
16 16 from request import wsgirequest
17 17 import webutil
18 18
19 19 def cleannames(items):
20 20 return [(util.pconvert(name).strip('/'), path) for name, path in items]
21 21
22 22 def findrepos(paths):
23 23 repos = []
24 24 for prefix, root in cleannames(paths):
25 25 roothead, roottail = os.path.split(root)
26 26 # "foo = /bar/*" makes every subrepo of /bar/ to be
27 27 # mounted as foo/subrepo
28 28 # and "foo = /bar/**" also recurses into the subdirectories,
29 29 # remember to use it without working dir.
30 30 try:
31 31 recurse = {'*': False, '**': True}[roottail]
32 32 except KeyError:
33 33 repos.append((prefix, root))
34 34 continue
35 roothead = os.path.normpath(roothead)
35 roothead = os.path.normpath(os.path.abspath(roothead))
36 36 for path in util.walkrepos(roothead, followsym=True, recurse=recurse):
37 37 path = os.path.normpath(path)
38 38 name = util.pconvert(path[len(roothead):]).strip('/')
39 39 if prefix:
40 40 name = prefix + '/' + name
41 41 repos.append((name, path))
42 42 return repos
43 43
44 44 class hgwebdir(object):
45 45 refreshinterval = 20
46 46
47 47 def __init__(self, conf, baseui=None):
48 48 self.conf = conf
49 49 self.baseui = baseui
50 50 self.lastrefresh = 0
51 51 self.motd = None
52 52 self.refresh()
53 53
54 54 def refresh(self):
55 55 if self.lastrefresh + self.refreshinterval > time.time():
56 56 return
57 57
58 58 if self.baseui:
59 59 u = self.baseui.copy()
60 60 else:
61 61 u = ui.ui()
62 62 u.setconfig('ui', 'report_untrusted', 'off')
63 63 u.setconfig('ui', 'interactive', 'off')
64 64
65 65 if not isinstance(self.conf, (dict, list, tuple)):
66 66 map = {'paths': 'hgweb-paths'}
67 67 u.readconfig(self.conf, remap=map, trust=True)
68 68 paths = u.configitems('hgweb-paths')
69 69 elif isinstance(self.conf, (list, tuple)):
70 70 paths = self.conf
71 71 elif isinstance(self.conf, dict):
72 72 paths = self.conf.items()
73 73
74 74 repos = findrepos(paths)
75 75 for prefix, root in u.configitems('collections'):
76 76 prefix = util.pconvert(prefix)
77 77 for path in util.walkrepos(root, followsym=True):
78 78 repo = os.path.normpath(path)
79 79 name = util.pconvert(repo)
80 80 if name.startswith(prefix):
81 81 name = name[len(prefix):]
82 82 repos.append((name.lstrip('/'), repo))
83 83
84 84 self.repos = repos
85 85 self.ui = u
86 86 encoding.encoding = self.ui.config('web', 'encoding',
87 87 encoding.encoding)
88 88 self.style = self.ui.config('web', 'style', 'paper')
89 89 self.templatepath = self.ui.config('web', 'templates', None)
90 90 self.stripecount = self.ui.config('web', 'stripes', 1)
91 91 if self.stripecount:
92 92 self.stripecount = int(self.stripecount)
93 93 self._baseurl = self.ui.config('web', 'baseurl')
94 94 self.lastrefresh = time.time()
95 95
96 96 def run(self):
97 97 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
98 98 raise RuntimeError("This function is only intended to be "
99 99 "called while running as a CGI script.")
100 100 import mercurial.hgweb.wsgicgi as wsgicgi
101 101 wsgicgi.launch(self)
102 102
103 103 def __call__(self, env, respond):
104 104 req = wsgirequest(env, respond)
105 105 return self.run_wsgi(req)
106 106
107 107 def read_allowed(self, ui, req):
108 108 """Check allow_read and deny_read config options of a repo's ui object
109 109 to determine user permissions. By default, with neither option set (or
110 110 both empty), allow all users to read the repo. There are two ways a
111 111 user can be denied read access: (1) deny_read is not empty, and the
112 112 user is unauthenticated or deny_read contains user (or *), and (2)
113 113 allow_read is not empty and the user is not in allow_read. Return True
114 114 if user is allowed to read the repo, else return False."""
115 115
116 116 user = req.env.get('REMOTE_USER')
117 117
118 118 deny_read = ui.configlist('web', 'deny_read', untrusted=True)
119 119 if deny_read and (not user or deny_read == ['*'] or user in deny_read):
120 120 return False
121 121
122 122 allow_read = ui.configlist('web', 'allow_read', untrusted=True)
123 123 # by default, allow reading if no allow_read option has been set
124 124 if (not allow_read) or (allow_read == ['*']) or (user in allow_read):
125 125 return True
126 126
127 127 return False
128 128
129 129 def run_wsgi(self, req):
130 130 try:
131 131 try:
132 132 self.refresh()
133 133
134 134 virtual = req.env.get("PATH_INFO", "").strip('/')
135 135 tmpl = self.templater(req)
136 136 ctype = tmpl('mimetype', encoding=encoding.encoding)
137 137 ctype = templater.stringify(ctype)
138 138
139 139 # a static file
140 140 if virtual.startswith('static/') or 'static' in req.form:
141 141 if virtual.startswith('static/'):
142 142 fname = virtual[7:]
143 143 else:
144 144 fname = req.form['static'][0]
145 145 static = templater.templatepath('static')
146 146 return (staticfile(static, fname, req),)
147 147
148 148 # top-level index
149 149 elif not virtual:
150 150 req.respond(HTTP_OK, ctype)
151 151 return self.makeindex(req, tmpl)
152 152
153 153 # nested indexes and hgwebs
154 154
155 155 repos = dict(self.repos)
156 156 while virtual:
157 157 real = repos.get(virtual)
158 158 if real:
159 159 req.env['REPO_NAME'] = virtual
160 160 try:
161 161 repo = hg.repository(self.ui, real)
162 162 return hgweb(repo).run_wsgi(req)
163 163 except IOError, inst:
164 164 msg = inst.strerror
165 165 raise ErrorResponse(HTTP_SERVER_ERROR, msg)
166 166 except error.RepoError, inst:
167 167 raise ErrorResponse(HTTP_SERVER_ERROR, str(inst))
168 168
169 169 # browse subdirectories
170 170 subdir = virtual + '/'
171 171 if [r for r in repos if r.startswith(subdir)]:
172 172 req.respond(HTTP_OK, ctype)
173 173 return self.makeindex(req, tmpl, subdir)
174 174
175 175 up = virtual.rfind('/')
176 176 if up < 0:
177 177 break
178 178 virtual = virtual[:up]
179 179
180 180 # prefixes not found
181 181 req.respond(HTTP_NOT_FOUND, ctype)
182 182 return tmpl("notfound", repo=virtual)
183 183
184 184 except ErrorResponse, err:
185 185 req.respond(err, ctype)
186 186 return tmpl('error', error=err.message or '')
187 187 finally:
188 188 tmpl = None
189 189
190 190 def makeindex(self, req, tmpl, subdir=""):
191 191
192 192 def archivelist(ui, nodeid, url):
193 193 allowed = ui.configlist("web", "allow_archive", untrusted=True)
194 194 for i in [('zip', '.zip'), ('gz', '.tar.gz'), ('bz2', '.tar.bz2')]:
195 195 if i[0] in allowed or ui.configbool("web", "allow" + i[0],
196 196 untrusted=True):
197 197 yield {"type" : i[0], "extension": i[1],
198 198 "node": nodeid, "url": url}
199 199
200 200 def rawentries(subdir="", **map):
201 201
202 202 descend = self.ui.configbool('web', 'descend', True)
203 203 for name, path in self.repos:
204 204
205 205 if not name.startswith(subdir):
206 206 continue
207 207 name = name[len(subdir):]
208 208 if not descend and '/' in name:
209 209 continue
210 210
211 211 u = self.ui.copy()
212 212 try:
213 213 u.readconfig(os.path.join(path, '.hg', 'hgrc'))
214 214 except Exception, e:
215 215 u.warn(_('error reading %s/.hg/hgrc: %s\n') % (path, e))
216 216 continue
217 217 def get(section, name, default=None):
218 218 return u.config(section, name, default, untrusted=True)
219 219
220 220 if u.configbool("web", "hidden", untrusted=True):
221 221 continue
222 222
223 223 if not self.read_allowed(u, req):
224 224 continue
225 225
226 226 parts = [name]
227 227 if 'PATH_INFO' in req.env:
228 228 parts.insert(0, req.env['PATH_INFO'].rstrip('/'))
229 229 if req.env['SCRIPT_NAME']:
230 230 parts.insert(0, req.env['SCRIPT_NAME'])
231 231 url = re.sub(r'/+', '/', '/'.join(parts) + '/')
232 232
233 233 # update time with local timezone
234 234 try:
235 235 r = hg.repository(self.ui, path)
236 236 d = (get_mtime(r.spath), util.makedate()[1])
237 237 except OSError:
238 238 continue
239 239
240 240 contact = get_contact(get)
241 241 description = get("web", "description", "")
242 242 name = get("web", "name", name)
243 243 row = dict(contact=contact or "unknown",
244 244 contact_sort=contact.upper() or "unknown",
245 245 name=name,
246 246 name_sort=name,
247 247 url=url,
248 248 description=description or "unknown",
249 249 description_sort=description.upper() or "unknown",
250 250 lastchange=d,
251 251 lastchange_sort=d[1]-d[0],
252 252 archives=archivelist(u, "tip", url))
253 253 yield row
254 254
255 255 sortdefault = None, False
256 256 def entries(sortcolumn="", descending=False, subdir="", **map):
257 257 rows = rawentries(subdir=subdir, **map)
258 258
259 259 if sortcolumn and sortdefault != (sortcolumn, descending):
260 260 sortkey = '%s_sort' % sortcolumn
261 261 rows = sorted(rows, key=lambda x: x[sortkey],
262 262 reverse=descending)
263 263 for row, parity in zip(rows, paritygen(self.stripecount)):
264 264 row['parity'] = parity
265 265 yield row
266 266
267 267 self.refresh()
268 268 sortable = ["name", "description", "contact", "lastchange"]
269 269 sortcolumn, descending = sortdefault
270 270 if 'sort' in req.form:
271 271 sortcolumn = req.form['sort'][0]
272 272 descending = sortcolumn.startswith('-')
273 273 if descending:
274 274 sortcolumn = sortcolumn[1:]
275 275 if sortcolumn not in sortable:
276 276 sortcolumn = ""
277 277
278 278 sort = [("sort_%s" % column,
279 279 "%s%s" % ((not descending and column == sortcolumn)
280 280 and "-" or "", column))
281 281 for column in sortable]
282 282
283 283 self.refresh()
284 284 self.updatereqenv(req.env)
285 285
286 286 return tmpl("index", entries=entries, subdir=subdir,
287 287 sortcolumn=sortcolumn, descending=descending,
288 288 **dict(sort))
289 289
290 290 def templater(self, req):
291 291
292 292 def header(**map):
293 293 yield tmpl('header', encoding=encoding.encoding, **map)
294 294
295 295 def footer(**map):
296 296 yield tmpl("footer", **map)
297 297
298 298 def motd(**map):
299 299 if self.motd is not None:
300 300 yield self.motd
301 301 else:
302 302 yield config('web', 'motd', '')
303 303
304 304 def config(section, name, default=None, untrusted=True):
305 305 return self.ui.config(section, name, default, untrusted)
306 306
307 307 self.updatereqenv(req.env)
308 308
309 309 url = req.env.get('SCRIPT_NAME', '')
310 310 if not url.endswith('/'):
311 311 url += '/'
312 312
313 313 vars = {}
314 314 styles = (
315 315 req.form.get('style', [None])[0],
316 316 config('web', 'style'),
317 317 'paper'
318 318 )
319 319 style, mapfile = templater.stylemap(styles, self.templatepath)
320 320 if style == styles[0]:
321 321 vars['style'] = style
322 322
323 323 start = url[-1] == '?' and '&' or '?'
324 324 sessionvars = webutil.sessionvars(vars, start)
325 325 staticurl = config('web', 'staticurl') or url + 'static/'
326 326 if not staticurl.endswith('/'):
327 327 staticurl += '/'
328 328
329 329 tmpl = templater.templater(mapfile,
330 330 defaults={"header": header,
331 331 "footer": footer,
332 332 "motd": motd,
333 333 "url": url,
334 334 "staticurl": staticurl,
335 335 "sessionvars": sessionvars})
336 336 return tmpl
337 337
338 338 def updatereqenv(self, env):
339 339 def splitnetloc(netloc):
340 340 if ':' in netloc:
341 341 return netloc.split(':', 1)
342 342 else:
343 343 return (netloc, None)
344 344
345 345 if self._baseurl is not None:
346 346 urlcomp = urlparse.urlparse(self._baseurl)
347 347 host, port = splitnetloc(urlcomp[1])
348 348 path = urlcomp[2]
349 349 env['SERVER_NAME'] = host
350 350 if port:
351 351 env['SERVER_PORT'] = port
352 352 env['SCRIPT_NAME'] = path
@@ -1,527 +1,527 b''
1 1 # merge.py - directory-level update/merge handling for Mercurial
2 2 #
3 3 # Copyright 2006, 2007 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from node import nullid, nullrev, hex, bin
9 9 from i18n import _
10 10 import util, filemerge, copies, subrepo
11 11 import errno, os, shutil
12 12
13 13 class mergestate(object):
14 14 '''track 3-way merge state of individual files'''
15 15 def __init__(self, repo):
16 16 self._repo = repo
17 17 self._read()
18 18 def reset(self, node=None):
19 19 self._state = {}
20 20 if node:
21 21 self._local = node
22 22 shutil.rmtree(self._repo.join("merge"), True)
23 23 def _read(self):
24 24 self._state = {}
25 25 try:
26 26 f = self._repo.opener("merge/state")
27 27 for i, l in enumerate(f):
28 28 if i == 0:
29 29 self._local = bin(l[:-1])
30 30 else:
31 31 bits = l[:-1].split("\0")
32 32 self._state[bits[0]] = bits[1:]
33 33 except IOError, err:
34 34 if err.errno != errno.ENOENT:
35 35 raise
36 36 def _write(self):
37 37 f = self._repo.opener("merge/state", "w")
38 38 f.write(hex(self._local) + "\n")
39 39 for d, v in self._state.iteritems():
40 40 f.write("\0".join([d] + v) + "\n")
41 41 def add(self, fcl, fco, fca, fd, flags):
42 42 hash = util.sha1(fcl.path()).hexdigest()
43 43 self._repo.opener("merge/" + hash, "w").write(fcl.data())
44 44 self._state[fd] = ['u', hash, fcl.path(), fca.path(),
45 45 hex(fca.filenode()), fco.path(), flags]
46 46 self._write()
47 47 def __contains__(self, dfile):
48 48 return dfile in self._state
49 49 def __getitem__(self, dfile):
50 50 return self._state[dfile][0]
51 51 def __iter__(self):
52 52 l = self._state.keys()
53 53 l.sort()
54 54 for f in l:
55 55 yield f
56 56 def mark(self, dfile, state):
57 57 self._state[dfile][0] = state
58 58 self._write()
59 59 def resolve(self, dfile, wctx, octx):
60 60 if self[dfile] == 'r':
61 61 return 0
62 62 state, hash, lfile, afile, anode, ofile, flags = self._state[dfile]
63 63 f = self._repo.opener("merge/" + hash)
64 64 self._repo.wwrite(dfile, f.read(), flags)
65 65 fcd = wctx[dfile]
66 66 fco = octx[ofile]
67 67 fca = self._repo.filectx(afile, fileid=anode)
68 68 r = filemerge.filemerge(self._repo, self._local, lfile, fcd, fco, fca)
69 69 if not r:
70 70 self.mark(dfile, 'r')
71 71 return r
72 72
73 73 def _checkunknown(wctx, mctx):
74 74 "check for collisions between unknown files and files in mctx"
75 75 for f in wctx.unknown():
76 76 if f in mctx and mctx[f].cmp(wctx[f].data()):
77 77 raise util.Abort(_("untracked file in working directory differs"
78 78 " from file in requested revision: '%s'") % f)
79 79
80 80 def _checkcollision(mctx):
81 81 "check for case folding collisions in the destination context"
82 82 folded = {}
83 83 for fn in mctx:
84 84 fold = fn.lower()
85 85 if fold in folded:
86 86 raise util.Abort(_("case-folding collision between %s and %s")
87 87 % (fn, folded[fold]))
88 88 folded[fold] = fn
89 89
90 90 def _forgetremoved(wctx, mctx, branchmerge):
91 91 """
92 92 Forget removed files
93 93
94 94 If we're jumping between revisions (as opposed to merging), and if
95 95 neither the working directory nor the target rev has the file,
96 96 then we need to remove it from the dirstate, to prevent the
97 97 dirstate from listing the file when it is no longer in the
98 98 manifest.
99 99
100 100 If we're merging, and the other revision has removed a file
101 101 that is not present in the working directory, we need to mark it
102 102 as removed.
103 103 """
104 104
105 105 action = []
106 106 state = branchmerge and 'r' or 'f'
107 107 for f in wctx.deleted():
108 108 if f not in mctx:
109 109 action.append((f, state))
110 110
111 111 if not branchmerge:
112 112 for f in wctx.removed():
113 113 if f not in mctx:
114 114 action.append((f, "f"))
115 115
116 116 return action
117 117
118 118 def manifestmerge(repo, p1, p2, pa, overwrite, partial):
119 119 """
120 120 Merge p1 and p2 with ancestor ma and generate merge action list
121 121
122 122 overwrite = whether we clobber working files
123 123 partial = function to filter file lists
124 124 """
125 125
126 126 def fmerge(f, f2, fa):
127 127 """merge flags"""
128 128 a, m, n = ma.flags(fa), m1.flags(f), m2.flags(f2)
129 129 if m == n: # flags agree
130 130 return m # unchanged
131 131 if m and n and not a: # flags set, don't agree, differ from parent
132 132 r = repo.ui.promptchoice(
133 133 _(" conflicting flags for %s\n"
134 134 "(n)one, e(x)ec or sym(l)ink?") % f,
135 135 (_("&None"), _("E&xec"), _("Sym&link")), 0)
136 136 if r == 1:
137 137 return "x" # Exec
138 138 if r == 2:
139 139 return "l" # Symlink
140 140 return ""
141 141 if m and m != a: # changed from a to m
142 142 return m
143 143 if n and n != a: # changed from a to n
144 144 return n
145 145 return '' # flag was cleared
146 146
147 147 def act(msg, m, f, *args):
148 148 repo.ui.debug(" %s: %s -> %s\n" % (f, msg, m))
149 149 action.append((f, m) + args)
150 150
151 151 action, copy = [], {}
152 152
153 153 if overwrite:
154 154 pa = p1
155 155 elif pa == p2: # backwards
156 156 pa = p1.p1()
157 157 elif pa and repo.ui.configbool("merge", "followcopies", True):
158 158 dirs = repo.ui.configbool("merge", "followdirs", True)
159 159 copy, diverge = copies.copies(repo, p1, p2, pa, dirs)
160 160 for of, fl in diverge.iteritems():
161 161 act("divergent renames", "dr", of, fl)
162 162
163 163 repo.ui.note(_("resolving manifests\n"))
164 164 repo.ui.debug(" overwrite %s partial %s\n" % (overwrite, bool(partial)))
165 165 repo.ui.debug(" ancestor %s local %s remote %s\n" % (pa, p1, p2))
166 166
167 167 m1, m2, ma = p1.manifest(), p2.manifest(), pa.manifest()
168 168 copied = set(copy.values())
169 169
170 170 if '.hgsubstate' in m1:
171 171 # check whether sub state is modified
172 172 for s in p1.substate:
173 173 if p1.sub(s).dirty():
174 174 m1['.hgsubstate'] += "+"
175 175 break
176 176
177 177 # Compare manifests
178 178 for f, n in m1.iteritems():
179 179 if partial and not partial(f):
180 180 continue
181 181 if f in m2:
182 182 rflags = fmerge(f, f, f)
183 183 a = ma.get(f, nullid)
184 184 if n == m2[f] or m2[f] == a: # same or local newer
185 185 # is file locally modified or flags need changing?
186 186 # dirstate flags may need to be made current
187 187 if m1.flags(f) != rflags or n[20:]:
188 188 act("update permissions", "e", f, rflags)
189 189 elif n == a: # remote newer
190 190 act("remote is newer", "g", f, rflags)
191 191 else: # both changed
192 192 act("versions differ", "m", f, f, f, rflags, False)
193 193 elif f in copied: # files we'll deal with on m2 side
194 194 pass
195 195 elif f in copy:
196 196 f2 = copy[f]
197 197 if f2 not in m2: # directory rename
198 198 act("remote renamed directory to " + f2, "d",
199 199 f, None, f2, m1.flags(f))
200 200 else: # case 2 A,B/B/B or case 4,21 A/B/B
201 201 act("local copied/moved to " + f2, "m",
202 202 f, f2, f, fmerge(f, f2, f2), False)
203 203 elif f in ma: # clean, a different, no remote
204 204 if n != ma[f]:
205 205 if repo.ui.promptchoice(
206 206 _(" local changed %s which remote deleted\n"
207 207 "use (c)hanged version or (d)elete?") % f,
208 208 (_("&Changed"), _("&Delete")), 0):
209 209 act("prompt delete", "r", f)
210 210 else:
211 211 act("prompt keep", "a", f)
212 212 elif n[20:] == "a": # added, no remote
213 213 act("remote deleted", "f", f)
214 214 elif n[20:] != "u":
215 215 act("other deleted", "r", f)
216 216
217 217 for f, n in m2.iteritems():
218 218 if partial and not partial(f):
219 219 continue
220 220 if f in m1 or f in copied: # files already visited
221 221 continue
222 222 if f in copy:
223 223 f2 = copy[f]
224 224 if f2 not in m1: # directory rename
225 225 act("local renamed directory to " + f2, "d",
226 226 None, f, f2, m2.flags(f))
227 227 elif f2 in m2: # rename case 1, A/A,B/A
228 228 act("remote copied to " + f, "m",
229 229 f2, f, f, fmerge(f2, f, f2), False)
230 230 else: # case 3,20 A/B/A
231 231 act("remote moved to " + f, "m",
232 232 f2, f, f, fmerge(f2, f, f2), True)
233 233 elif f not in ma:
234 234 act("remote created", "g", f, m2.flags(f))
235 235 elif n != ma[f]:
236 236 if repo.ui.promptchoice(
237 237 _("remote changed %s which local deleted\n"
238 238 "use (c)hanged version or leave (d)eleted?") % f,
239 239 (_("&Changed"), _("&Deleted")), 0) == 0:
240 240 act("prompt recreating", "g", f, m2.flags(f))
241 241
242 242 return action
243 243
244 244 def actionkey(a):
245 245 return a[1] == 'r' and -1 or 0, a
246 246
247 247 def applyupdates(repo, action, wctx, mctx, actx):
248 248 """apply the merge action list to the working directory
249 249
250 250 wctx is the working copy context
251 251 mctx is the context to be merged into the working copy
252 252 actx is the context of the common ancestor
253 253 """
254 254
255 255 updated, merged, removed, unresolved = 0, 0, 0, 0
256 256 ms = mergestate(repo)
257 257 ms.reset(wctx.parents()[0].node())
258 258 moves = []
259 259 action.sort(key=actionkey)
260 260 substate = wctx.substate # prime
261 261
262 262 # prescan for merges
263 263 u = repo.ui
264 264 for a in action:
265 265 f, m = a[:2]
266 266 if m == 'm': # merge
267 267 f2, fd, flags, move = a[2:]
268 268 if f == '.hgsubstate': # merged internally
269 269 continue
270 270 repo.ui.debug("preserving %s for resolve of %s\n" % (f, fd))
271 271 fcl = wctx[f]
272 272 fco = mctx[f2]
273 273 fca = fcl.ancestor(fco, actx) or repo.filectx(f, fileid=nullrev)
274 274 ms.add(fcl, fco, fca, fd, flags)
275 275 if f != fd and move:
276 276 moves.append(f)
277 277
278 278 # remove renamed files after safely stored
279 279 for f in moves:
280 280 if util.lexists(repo.wjoin(f)):
281 281 repo.ui.debug("removing %s\n" % f)
282 282 os.unlink(repo.wjoin(f))
283 283
284 284 audit_path = util.path_auditor(repo.root)
285 285
286 286 numupdates = len(action)
287 287 for i, a in enumerate(action):
288 288 f, m = a[:2]
289 u.progress('update', i + 1, item=f, total=numupdates, unit='files')
289 u.progress(_('updating'), i + 1, item=f, total=numupdates, unit='files')
290 290 if f and f[0] == "/":
291 291 continue
292 292 if m == "r": # remove
293 293 repo.ui.note(_("removing %s\n") % f)
294 294 audit_path(f)
295 295 if f == '.hgsubstate': # subrepo states need updating
296 296 subrepo.submerge(repo, wctx, mctx, wctx)
297 297 try:
298 298 util.unlink(repo.wjoin(f))
299 299 except OSError, inst:
300 300 if inst.errno != errno.ENOENT:
301 301 repo.ui.warn(_("update failed to remove %s: %s!\n") %
302 302 (f, inst.strerror))
303 303 removed += 1
304 304 elif m == "m": # merge
305 305 if f == '.hgsubstate': # subrepo states need updating
306 306 subrepo.submerge(repo, wctx, mctx, wctx.ancestor(mctx))
307 307 continue
308 308 f2, fd, flags, move = a[2:]
309 309 r = ms.resolve(fd, wctx, mctx)
310 310 if r is not None and r > 0:
311 311 unresolved += 1
312 312 else:
313 313 if r is None:
314 314 updated += 1
315 315 else:
316 316 merged += 1
317 317 util.set_flags(repo.wjoin(fd), 'l' in flags, 'x' in flags)
318 318 if f != fd and move and util.lexists(repo.wjoin(f)):
319 319 repo.ui.debug("removing %s\n" % f)
320 320 os.unlink(repo.wjoin(f))
321 321 elif m == "g": # get
322 322 flags = a[2]
323 323 repo.ui.note(_("getting %s\n") % f)
324 324 t = mctx.filectx(f).data()
325 325 repo.wwrite(f, t, flags)
326 326 updated += 1
327 327 if f == '.hgsubstate': # subrepo states need updating
328 328 subrepo.submerge(repo, wctx, mctx, wctx)
329 329 elif m == "d": # directory rename
330 330 f2, fd, flags = a[2:]
331 331 if f:
332 332 repo.ui.note(_("moving %s to %s\n") % (f, fd))
333 333 t = wctx.filectx(f).data()
334 334 repo.wwrite(fd, t, flags)
335 335 util.unlink(repo.wjoin(f))
336 336 if f2:
337 337 repo.ui.note(_("getting %s to %s\n") % (f2, fd))
338 338 t = mctx.filectx(f2).data()
339 339 repo.wwrite(fd, t, flags)
340 340 updated += 1
341 341 elif m == "dr": # divergent renames
342 342 fl = a[2]
343 343 repo.ui.warn(_("warning: detected divergent renames of %s to:\n") % f)
344 344 for nf in fl:
345 345 repo.ui.warn(" %s\n" % nf)
346 346 elif m == "e": # exec
347 347 flags = a[2]
348 348 util.set_flags(repo.wjoin(f), 'l' in flags, 'x' in flags)
349 u.progress('update', None, total=numupdates, unit='files')
349 u.progress(_('updating'), None, total=numupdates, unit='files')
350 350
351 351 return updated, merged, removed, unresolved
352 352
353 353 def recordupdates(repo, action, branchmerge):
354 354 "record merge actions to the dirstate"
355 355
356 356 for a in action:
357 357 f, m = a[:2]
358 358 if m == "r": # remove
359 359 if branchmerge:
360 360 repo.dirstate.remove(f)
361 361 else:
362 362 repo.dirstate.forget(f)
363 363 elif m == "a": # re-add
364 364 if not branchmerge:
365 365 repo.dirstate.add(f)
366 366 elif m == "f": # forget
367 367 repo.dirstate.forget(f)
368 368 elif m == "e": # exec change
369 369 repo.dirstate.normallookup(f)
370 370 elif m == "g": # get
371 371 if branchmerge:
372 372 repo.dirstate.otherparent(f)
373 373 else:
374 374 repo.dirstate.normal(f)
375 375 elif m == "m": # merge
376 376 f2, fd, flag, move = a[2:]
377 377 if branchmerge:
378 378 # We've done a branch merge, mark this file as merged
379 379 # so that we properly record the merger later
380 380 repo.dirstate.merge(fd)
381 381 if f != f2: # copy/rename
382 382 if move:
383 383 repo.dirstate.remove(f)
384 384 if f != fd:
385 385 repo.dirstate.copy(f, fd)
386 386 else:
387 387 repo.dirstate.copy(f2, fd)
388 388 else:
389 389 # We've update-merged a locally modified file, so
390 390 # we set the dirstate to emulate a normal checkout
391 391 # of that file some time in the past. Thus our
392 392 # merge will appear as a normal local file
393 393 # modification.
394 394 if f2 == fd: # file not locally copied/moved
395 395 repo.dirstate.normallookup(fd)
396 396 if move:
397 397 repo.dirstate.forget(f)
398 398 elif m == "d": # directory rename
399 399 f2, fd, flag = a[2:]
400 400 if not f2 and f not in repo.dirstate:
401 401 # untracked file moved
402 402 continue
403 403 if branchmerge:
404 404 repo.dirstate.add(fd)
405 405 if f:
406 406 repo.dirstate.remove(f)
407 407 repo.dirstate.copy(f, fd)
408 408 if f2:
409 409 repo.dirstate.copy(f2, fd)
410 410 else:
411 411 repo.dirstate.normal(fd)
412 412 if f:
413 413 repo.dirstate.forget(f)
414 414
415 415 def update(repo, node, branchmerge, force, partial):
416 416 """
417 417 Perform a merge between the working directory and the given node
418 418
419 419 node = the node to update to, or None if unspecified
420 420 branchmerge = whether to merge between branches
421 421 force = whether to force branch merging or file overwriting
422 422 partial = a function to filter file lists (dirstate not updated)
423 423
424 424 The table below shows all the behaviors of the update command
425 425 given the -c and -C or no options, whether the working directory
426 426 is dirty, whether a revision is specified, and the relationship of
427 427 the parent rev to the target rev (linear, on the same named
428 428 branch, or on another named branch).
429 429
430 430 This logic is tested by test-update-branches.
431 431
432 432 -c -C dirty rev | linear same cross
433 433 n n n n | ok (1) x
434 434 n n n y | ok ok ok
435 435 n n y * | merge (2) (2)
436 436 n y * * | --- discard ---
437 437 y n y * | --- (3) ---
438 438 y n n * | --- ok ---
439 439 y y * * | --- (4) ---
440 440
441 441 x = can't happen
442 442 * = don't-care
443 443 1 = abort: crosses branches (use 'hg merge' or 'hg update -c')
444 444 2 = abort: crosses branches (use 'hg merge' to merge or
445 445 use 'hg update -C' to discard changes)
446 446 3 = abort: uncommitted local changes
447 447 4 = incompatible options (checked in commands.py)
448 448 """
449 449
450 450 onode = node
451 451 wlock = repo.wlock()
452 452 try:
453 453 wc = repo[None]
454 454 if node is None:
455 455 # tip of current branch
456 456 try:
457 457 node = repo.branchtags()[wc.branch()]
458 458 except KeyError:
459 459 if wc.branch() == "default": # no default branch!
460 460 node = repo.lookup("tip") # update to tip
461 461 else:
462 462 raise util.Abort(_("branch %s not found") % wc.branch())
463 463 overwrite = force and not branchmerge
464 464 pl = wc.parents()
465 465 p1, p2 = pl[0], repo[node]
466 466 pa = p1.ancestor(p2)
467 467 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2)
468 468 fastforward = False
469 469
470 470 ### check phase
471 471 if not overwrite and len(pl) > 1:
472 472 raise util.Abort(_("outstanding uncommitted merges"))
473 473 if branchmerge:
474 474 if pa == p2:
475 475 raise util.Abort(_("merging with a working directory ancestor"
476 476 " has no effect"))
477 477 elif pa == p1:
478 478 if p1.branch() != p2.branch():
479 479 fastforward = True
480 480 else:
481 481 raise util.Abort(_("nothing to merge (use 'hg update'"
482 482 " or check 'hg heads')"))
483 483 if not force and (wc.files() or wc.deleted()):
484 484 raise util.Abort(_("outstanding uncommitted changes "
485 485 "(use 'hg status' to list changes)"))
486 486 elif not overwrite:
487 487 if pa == p1 or pa == p2: # linear
488 488 pass # all good
489 489 elif wc.files() or wc.deleted():
490 490 raise util.Abort(_("crosses branches (use 'hg merge' to merge "
491 491 "or use 'hg update -C' to discard changes)"))
492 492 elif onode is None:
493 493 raise util.Abort(_("crosses branches (use 'hg merge' or use "
494 494 "'hg update -c')"))
495 495 else:
496 496 # Allow jumping branches if clean and specific rev given
497 497 overwrite = True
498 498
499 499 ### calculate phase
500 500 action = []
501 501 wc.status(unknown=True) # prime cache
502 502 if not force:
503 503 _checkunknown(wc, p2)
504 504 if not util.checkcase(repo.path):
505 505 _checkcollision(p2)
506 506 action += _forgetremoved(wc, p2, branchmerge)
507 507 action += manifestmerge(repo, wc, p2, pa, overwrite, partial)
508 508
509 509 ### apply phase
510 510 if not branchmerge: # just jump to the new rev
511 511 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
512 512 if not partial:
513 513 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
514 514
515 515 stats = applyupdates(repo, action, wc, p2, pa)
516 516
517 517 if not partial:
518 518 repo.dirstate.setparents(fp1, fp2)
519 519 recordupdates(repo, action, branchmerge)
520 520 if not branchmerge and not fastforward:
521 521 repo.dirstate.setbranch(p2.branch())
522 522 finally:
523 523 wlock.release()
524 524
525 525 if not partial:
526 526 repo.hook('update', parent1=xp1, parent2=xp2, error=stats[3])
527 527 return stats
@@ -1,66 +1,72 b''
1 1 #!/bin/sh
2 2
3 3 cat >> $HGRCPATH <<EOF
4 4 [alias]
5 5 myinit = init
6 6 cleanstatus = status -c
7 7 unknown = bargle
8 8 ambiguous = s
9 9 recursive = recursive
10 10 nodefinition =
11 11 mylog = log
12 12 lognull = log -r null
13 13 shortlog = log --template '{rev} {node|short} | {date|isodate}\n'
14 14 dln = lognull --debug
15 15 nousage = rollback
16 16 put = export -r 0 -o "\$FOO/%R.diff"
17 rt = root
17 18
18 19 [defaults]
19 20 mylog = -q
20 21 lognull = -q
21 22 log = -v
22 23 EOF
23 24
24 25 echo '% basic'
25 26 hg myinit alias
26 27
27 28 echo '% unknown'
28 29 hg unknown
29 30 hg help unknown
30 31
31 32 echo '% ambiguous'
32 33 hg ambiguous
33 34 hg help ambiguous
34 35
35 36 echo '% recursive'
36 37 hg recursive
37 38 hg help recursive
38 39
39 40 echo '% no definition'
40 41 hg nodef
41 42 hg help nodef
42 43
43 44 cd alias
44 45
45 46 echo '% no usage'
46 47 hg nousage
47 48
48 49 echo foo > foo
49 50 hg ci -Amfoo
50 51
51 52 echo '% with opts'
52 53 hg cleanst
53 54
54 55 echo '% with opts and whitespace'
55 56 hg shortlog
56 57
57 58 echo '% interaction with defaults'
58 59 hg mylog
59 60 hg lognull
60 61
61 62 echo '% properly recursive'
62 63 hg dln
63 64
64 65 echo '% path expanding'
65 66 FOO=`pwd` hg put
66 67 cat 0.diff
68
69 echo '% invalid arguments'
70 hg rt foo
71
72 exit 0
@@ -1,45 +1,58 b''
1 1 % basic
2 2 % unknown
3 3 alias 'unknown' resolves to unknown command 'bargle'
4 4 alias 'unknown' resolves to unknown command 'bargle'
5 5 % ambiguous
6 6 alias 'ambiguous' resolves to ambiguous command 's'
7 7 alias 'ambiguous' resolves to ambiguous command 's'
8 8 % recursive
9 9 alias 'recursive' resolves to unknown command 'recursive'
10 10 alias 'recursive' resolves to unknown command 'recursive'
11 11 % no definition
12 12 no definition for alias 'nodefinition'
13 13 no definition for alias 'nodefinition'
14 14 % no usage
15 15 no rollback information available
16 16 adding foo
17 17 % with opts
18 18 C foo
19 19 % with opts and whitespace
20 20 0 e63c23eaa88a | 1970-01-01 00:00 +0000
21 21 % interaction with defaults
22 22 0:e63c23eaa88a
23 23 -1:000000000000
24 24 % properly recursive
25 25 changeset: -1:0000000000000000000000000000000000000000
26 26 parent: -1:0000000000000000000000000000000000000000
27 27 parent: -1:0000000000000000000000000000000000000000
28 28 manifest: -1:0000000000000000000000000000000000000000
29 29 user:
30 30 date: Thu Jan 01 00:00:00 1970 +0000
31 31 extra: branch=default
32 32
33 33 % path expanding
34 34 # HG changeset patch
35 35 # User test
36 36 # Date 0 0
37 37 # Node ID e63c23eaa88ae77967edcf4ea194d31167c478b0
38 38 # Parent 0000000000000000000000000000000000000000
39 39 foo
40 40
41 41 diff -r 000000000000 -r e63c23eaa88a foo
42 42 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
43 43 +++ b/foo Thu Jan 01 00:00:00 1970 +0000
44 44 @@ -0,0 +1,1 @@
45 45 +foo
46 % invalid arguments
47 hg rt: invalid arguments
48 hg rt
49
50 alias for: hg root
51
52 print the root (top) of the current working directory
53
54 Print the root directory of the current repository.
55
56 Returns 0 on success.
57
58 use "hg -v help rt" to show global options
@@ -1,103 +1,106 b''
1 1 #!/bin/sh
2 2
3 3 echo "[extensions]" >> $HGRCPATH
4 4 echo "bookmarks=" >> $HGRCPATH
5 5
6 6 hg init
7 7
8 8 echo % no bookmarks
9 9 hg bookmarks
10 10
11 11 echo % bookmark rev -1
12 12 hg bookmark X
13 13
14 14 echo % list bookmarks
15 15 hg bookmarks
16 16
17 17 echo % list bookmarks with color
18 18 hg --config extensions.color= --config color.mode=ansi \
19 19 bookmarks --color=always
20 20
21 21 echo a > a
22 22 hg add a
23 23 hg commit -m 0
24 24
25 25 echo % bookmark X moved to rev 0
26 26 hg bookmarks
27 27
28 28 echo % look up bookmark
29 29 hg log -r X
30 30
31 31 echo % second bookmark for rev 0
32 32 hg bookmark X2
33 33
34 34 echo % bookmark rev -1 again
35 35 hg bookmark -r null Y
36 36
37 37 echo % list bookmarks
38 38 hg bookmarks
39 39
40 40 echo b > b
41 41 hg add b
42 42 hg commit -m 1
43 43
44 44 echo % bookmarks X and X2 moved to rev 1, Y at rev -1
45 45 hg bookmarks
46 46
47 47 echo % bookmark rev 0 again
48 48 hg bookmark -r 0 Z
49 49
50 50 echo c > c
51 51 hg add c
52 52 hg commit -m 2
53 53
54 54 echo % bookmarks X and X2 moved to rev 2, Y at rev -1, Z at rev 0
55 55 hg bookmarks
56 56
57 57 echo % rename nonexistent bookmark
58 58 hg bookmark -m A B
59 59
60 60 echo % rename to existent bookmark
61 61 hg bookmark -m X Y
62 62
63 63 echo % force rename to existent bookmark
64 64 hg bookmark -f -m X Y
65 65
66 66 echo % list bookmarks
67 67 hg bookmark
68 68
69 69 echo % rename without new name
70 70 hg bookmark -m Y
71 71
72 72 echo % delete without name
73 73 hg bookmark -d
74 74
75 75 echo % delete nonexistent bookmark
76 76 hg bookmark -d A
77 77
78 78 echo % bookmark name with spaces should be stripped
79 79 hg bookmark ' x y '
80 80
81 81 echo % list bookmarks
82 82 hg bookmarks
83 83
84 84 echo % look up stripped bookmark name
85 85 hg log -r '"x y"'
86 86
87 87 echo % reject bookmark name with newline
88 88 hg bookmark '
89 89 '
90 90
91 91 echo % bookmark with existing name
92 92 hg bookmark Z
93 93
94 94 echo % force bookmark with existing name
95 95 hg bookmark -f Z
96 96
97 97 echo % list bookmarks
98 98 hg bookmark
99 99
100 100 echo % revision but no bookmark name
101 101 hg bookmark -r .
102 102
103 echo % bookmark name with whitespace only
104 hg bookmark ' '
105
103 106 true
@@ -1,76 +1,78 b''
1 1 % no bookmarks
2 2 no bookmarks set
3 3 % bookmark rev -1
4 4 % list bookmarks
5 5 * X -1:000000000000
6 6 % list bookmarks with color
7 7  * X -1:000000000000
8 8 % bookmark X moved to rev 0
9 9 * X 0:f7b1eb17ad24
10 10 % look up bookmark
11 11 changeset: 0:f7b1eb17ad24
12 12 tag: X
13 13 tag: tip
14 14 user: test
15 15 date: Thu Jan 01 00:00:00 1970 +0000
16 16 summary: 0
17 17
18 18 % second bookmark for rev 0
19 19 % bookmark rev -1 again
20 20 % list bookmarks
21 21 * X2 0:f7b1eb17ad24
22 22 * X 0:f7b1eb17ad24
23 23 Y -1:000000000000
24 24 % bookmarks X and X2 moved to rev 1, Y at rev -1
25 25 * X2 1:925d80f479bb
26 26 * X 1:925d80f479bb
27 27 Y -1:000000000000
28 28 % bookmark rev 0 again
29 29 % bookmarks X and X2 moved to rev 2, Y at rev -1, Z at rev 0
30 30 * X2 2:0316ce92851d
31 31 * X 2:0316ce92851d
32 32 Z 0:f7b1eb17ad24
33 33 Y -1:000000000000
34 34 % rename nonexistent bookmark
35 35 abort: a bookmark of this name does not exist
36 36 % rename to existent bookmark
37 37 abort: a bookmark of the same name already exists
38 38 % force rename to existent bookmark
39 39 % list bookmarks
40 40 * X2 2:0316ce92851d
41 41 * Y 2:0316ce92851d
42 42 Z 0:f7b1eb17ad24
43 43 % rename without new name
44 44 abort: new bookmark name required
45 45 % delete without name
46 46 abort: bookmark name required
47 47 % delete nonexistent bookmark
48 48 abort: a bookmark of this name does not exist
49 49 % bookmark name with spaces should be stripped
50 50 % list bookmarks
51 51 * X2 2:0316ce92851d
52 52 * Y 2:0316ce92851d
53 53 Z 0:f7b1eb17ad24
54 54 * x y 2:0316ce92851d
55 55 % look up stripped bookmark name
56 56 changeset: 2:0316ce92851d
57 57 tag: X2
58 58 tag: Y
59 59 tag: tip
60 60 tag: x y
61 61 user: test
62 62 date: Thu Jan 01 00:00:00 1970 +0000
63 63 summary: 2
64 64
65 65 % reject bookmark name with newline
66 66 abort: bookmark name cannot contain newlines
67 67 % bookmark with existing name
68 68 abort: a bookmark of the same name already exists
69 69 % force bookmark with existing name
70 70 % list bookmarks
71 71 * X2 2:0316ce92851d
72 72 * Y 2:0316ce92851d
73 73 * Z 2:0316ce92851d
74 74 * x y 2:0316ce92851d
75 75 % revision but no bookmark name
76 76 abort: bookmark name required
77 % bookmark name with whitespace only
78 abort: bookmark names cannot consist entirely of whitespace
@@ -1,37 +1,37 b''
1 1 1 files updated, 0 files merged, 2 files removed, 0 files unresolved
2 2 searching for copies back to rev 1
3 3 unmatched files in other:
4 4 b
5 5 c
6 6 all copies found (* = to merge, ! = divergent):
7 7 c -> a *
8 8 b -> a *
9 9 checking for directory renames
10 10 resolving manifests
11 11 overwrite None partial False
12 12 ancestor 583c7b748052 local fb3948d97f07+ remote 7f1309517659
13 13 a: remote moved to c -> m
14 14 a: remote moved to b -> m
15 15 preserving a for resolve of b
16 16 preserving a for resolve of c
17 17 removing a
18 update: a 1/2 files (50.00%)
18 updating: a 1/2 files (50.00%)
19 19 picked tool 'internal:merge' for b (binary False symlink False)
20 20 merging a and b to b
21 21 my b@fb3948d97f07+ other b@7f1309517659 ancestor a@583c7b748052
22 22 premerge successful
23 update: a 2/2 files (100.00%)
23 updating: a 2/2 files (100.00%)
24 24 picked tool 'internal:merge' for c (binary False symlink False)
25 25 merging a and c to c
26 26 my c@fb3948d97f07+ other c@7f1309517659 ancestor a@583c7b748052
27 27 premerge successful
28 28 0 files updated, 2 files merged, 0 files removed, 0 files unresolved
29 29 (branch merge, don't forget to commit)
30 30 -- b --
31 31 0
32 32 1
33 33 2
34 34 -- c --
35 35 0
36 36 1
37 37 2
@@ -1,27 +1,33 b''
1 1 #!/bin/sh
2 2 # test command parsing and dispatch
3 3
4 4 "$TESTDIR/hghave" no-outer-repo || exit 80
5 5
6 dir=`pwd`
7
6 8 hg init a
7 9 cd a
8 10 echo a > a
9 11 hg ci -Ama
10 12
11 13 echo "# missing arg"
12 14 hg cat
13 15
14 16 echo '% [defaults]'
15 17 hg cat a
16 18 cat >> $HGRCPATH <<EOF
17 19 [defaults]
18 20 cat = -r null
19 21 EOF
20 22 hg cat a
21 23
24 echo '% working directory removed'
25 rm -rf $dir/a
26 hg --version
27
22 28 echo '% no repo'
23 cd ..
29 cd $dir
24 30 hg cat
25 31
26 32 exit 0
27 33
@@ -1,37 +1,39 b''
1 1 adding a
2 2 # missing arg
3 3 hg cat: invalid arguments
4 4 hg cat [OPTION]... FILE...
5 5
6 6 output the current or given revision of files
7 7
8 8 Print the specified files as they were at the given revision. If no
9 9 revision is given, the parent of the working directory is used, or tip if
10 10 no revision is checked out.
11 11
12 12 Output may be to a file, in which case the name of the file is given using
13 13 a format string. The formatting rules are the same as for the export
14 14 command, with the following additions:
15 15
16 16 "%s" basename of file being printed
17 17 "%d" dirname of file being printed, or '.' if in repository root
18 18 "%p" root-relative path name of file being printed
19 19
20 20 Returns 0 on success.
21 21
22 22 options:
23 23
24 24 -o --output FORMAT print output to file with formatted name
25 25 -r --rev REV print the given revision
26 26 --decode apply any matching decode filter
27 27 -I --include PATTERN [+] include names matching the given patterns
28 28 -X --exclude PATTERN [+] exclude names matching the given patterns
29 29
30 30 [+] marked option can be specified multiple times
31 31
32 32 use "hg -v help cat" to show global options
33 33 % [defaults]
34 34 a
35 35 a: No such file in rev 000000000000
36 % working directory removed
37 abort: error getting current working directory: No such file or directory
36 38 % no repo
37 39 abort: There is no Mercurial repository here (.hg not found)!
@@ -1,39 +1,39 b''
1 1 created new head
2 2 changeset: 1:d9da848d0adf
3 3 user: test
4 4 date: Mon Jan 12 13:46:40 1970 +0000
5 5 summary: cp foo bar; change both
6 6
7 7 searching for copies back to rev 1
8 8 unmatched files in other:
9 9 bar
10 10 all copies found (* = to merge, ! = divergent):
11 11 bar -> foo *
12 12 checking for directory renames
13 13 resolving manifests
14 14 overwrite None partial False
15 15 ancestor 310fd17130da local 2092631ce82b+ remote d9da848d0adf
16 16 foo: versions differ -> m
17 17 foo: remote copied to bar -> m
18 18 preserving foo for resolve of bar
19 19 preserving foo for resolve of foo
20 update: foo 1/2 files (50.00%)
20 updating: foo 1/2 files (50.00%)
21 21 picked tool 'internal:merge' for bar (binary False symlink False)
22 22 merging foo and bar to bar
23 23 my bar@2092631ce82b+ other bar@d9da848d0adf ancestor foo@310fd17130da
24 24 premerge successful
25 update: foo 2/2 files (100.00%)
25 updating: foo 2/2 files (100.00%)
26 26 picked tool 'internal:merge' for foo (binary False symlink False)
27 27 merging foo
28 28 my foo@2092631ce82b+ other foo@d9da848d0adf ancestor foo@310fd17130da
29 29 premerge successful
30 30 0 files updated, 2 files merged, 0 files removed, 0 files unresolved
31 31 (branch merge, don't forget to commit)
32 32 -- foo --
33 33 line 0
34 34 line 1
35 35 line 2-1
36 36 -- bar --
37 37 line 0
38 38 line 1
39 39 line 2-2
@@ -1,123 +1,127 b''
1 1 #!/bin/sh
2 2
3 3 ########################################
4 4
5 5 HGENCODING=utf-8
6 6 export HGENCODING
7 7
8 8 hg init t
9 9 cd t
10 10
11 11 python << EOF
12 12 # (byte, width) = (6, 4)
13 13 s = "\xe7\x9f\xad\xe5\x90\x8d"
14 14 # (byte, width) = (7, 7): odd width is good for alignment test
15 15 m = "MIDDLE_"
16 16 # (byte, width) = (18, 12)
17 17 l = "\xe9\x95\xb7\xe3\x81\x84\xe9\x95\xb7\xe3\x81\x84\xe5\x90\x8d\xe5\x89\x8d"
18 18
19 19 f = file('s', 'w'); f.write(s); f.close()
20 20 f = file('m', 'w'); f.write(m); f.close()
21 21 f = file('l', 'w'); f.write(l); f.close()
22 22
23 23 # instant extension to show list of options
24 24 f = file('showoptlist.py', 'w'); f.write("""# encoding: utf-8
25 25 def showoptlist(ui, repo, *pats, **opts):
26 26 '''dummy command to show option descriptions'''
27 27 return 0
28 28
29 29 cmdtable = {
30 30 'showoptlist':
31 31 (showoptlist,
32 32 [('s', 'opt1', '', 'short width', '""" + s + """'),
33 33 ('m', 'opt2', '', 'middle width', '""" + m + """'),
34 34 ('l', 'opt3', '', 'long width', '""" + l + """')
35 35 ],
36 36 ""
37 37 )
38 38 }
39 39 """)
40 40 f.close()
41 41 EOF
42 42
43 43 S=`cat s`
44 44 M=`cat m`
45 45 L=`cat l`
46 46
47 47 ########################################
48 48 #### alignment of:
49 49 #### - option descriptions in help
50 50
51 51 cat <<EOF > .hg/hgrc
52 52 [extensions]
53 53 ja_ext = `pwd`/showoptlist.py
54 54 EOF
55 55 echo '% check alignment of option descriptions in help'
56 56 hg help showoptlist
57 57
58 58 ########################################
59 59 #### alignment of:
60 60 #### - user names in annotate
61 61 #### - file names in diffstat
62 62
63 rm -f s; touch s
64 rm -f m; touch m
65 rm -f l; touch l
66
63 67 #### add files
64 68
65 touch $S
69 cp s $S
66 70 hg add $S
67 touch $M
71 cp m $M
68 72 hg add $M
69 touch $L
73 cp l $L
70 74 hg add $L
71 75
72 76 #### commit(1)
73 77
74 echo 'first line(1)' >> $S
75 echo 'first line(2)' >> $M
76 echo 'first line(3)' >> $L
78 echo 'first line(1)' >> s; cp s $S
79 echo 'first line(2)' >> m; cp m $M
80 echo 'first line(3)' >> l; cp l $L
77 81 hg commit -m 'first commit' -u $S -d "1000000 0"
78 82
79 83 #### commit(2)
80 84
81 echo 'second line(1)' >> $S
82 echo 'second line(2)' >> $M
83 echo 'second line(3)' >> $L
85 echo 'second line(1)' >> s; cp s $S
86 echo 'second line(2)' >> m; cp m $M
87 echo 'second line(3)' >> l; cp l $L
84 88 hg commit -m 'second commit' -u $M -d "1000000 0"
85 89
86 90 #### commit(3)
87 91
88 echo 'third line(1)' >> $S
89 echo 'third line(2)' >> $M
90 echo 'third line(3)' >> $L
92 echo 'third line(1)' >> s; cp s $S
93 echo 'third line(2)' >> m; cp m $M
94 echo 'third line(3)' >> l; cp l $L
91 95 hg commit -m 'third commit' -u $L -d "1000000 0"
92 96
93 97 #### check
94 98
95 99 echo '% check alignment of user names in annotate'
96 100 hg annotate -u $M
97 101 echo '% check alignment of filenames in diffstat'
98 102 hg diff -c tip --stat
99 103
100 104 ########################################
101 105 #### alignment of:
102 106 #### - branch names in list
103 107 #### - tag names in list
104 108
105 109 #### add branches/tags
106 110
107 111 hg branch $S
108 112 hg tag -d "1000000 0" $S
109 113 hg branch $M
110 114 hg tag -d "1000000 0" $M
111 115 hg branch $L
112 116 hg tag -d "1000000 0" $L
113 117
114 118 #### check
115 119
116 120 echo '% check alignment of branches'
117 121 hg tags
118 122 echo '% check alignment of tags'
119 123 hg tags
120 124
121 125 ########################################
122 126
123 127 exit 0
@@ -1,161 +1,163 b''
1 1 #!/bin/sh
2 2 # Tests some basic hgwebdir functionality. Tests setting up paths and
3 3 # collection, different forms of 404s and the subdirectory support.
4 4
5 5 mkdir webdir
6 6 cd webdir
7 7
8 8 hg init a
9 9 echo a > a/a
10 10 hg --cwd a ci -Ama -d'1 0'
11 11 # create a mercurial queue repository
12 12 hg --cwd a qinit --config extensions.hgext.mq= -c
13 13
14 14 hg init b
15 15 echo b > b/b
16 16 hg --cwd b ci -Amb -d'2 0'
17 17
18 18 # create a nested repository
19 19 cd b
20 20 hg init d
21 21 echo d > d/d
22 22 hg --cwd d ci -Amd -d'3 0'
23 23 cd ..
24 24
25 25 hg init c
26 26 echo c > c/c
27 27 hg --cwd c ci -Amc -d'3 0'
28 28
29 29 root=`pwd`
30 30 cd ..
31 31
32 32
33 33 cat > paths.conf <<EOF
34 34 [paths]
35 35 a=$root/a
36 36 b=$root/b
37 37 EOF
38 38
39 39 hg serve -p $HGPORT -d --pid-file=hg.pid --webdir-conf paths.conf \
40 40 -A access-paths.log -E error-paths-1.log
41 41 cat hg.pid >> $DAEMON_PIDS
42 42
43 43 echo % should give a 404 - file does not exist
44 44 "$TESTDIR/get-with-headers.py" localhost:$HGPORT '/a/file/tip/bork?style=raw'
45 45
46 46 echo % should succeed
47 47 "$TESTDIR/get-with-headers.py" localhost:$HGPORT '/?style=raw'
48 48 "$TESTDIR/get-with-headers.py" localhost:$HGPORT '/a/file/tip/a?style=raw'
49 49 "$TESTDIR/get-with-headers.py" localhost:$HGPORT '/b/file/tip/b?style=raw'
50 50
51 51 echo % should give a 404 - repo is not published
52 52 "$TESTDIR/get-with-headers.py" localhost:$HGPORT '/c/file/tip/c?style=raw'
53 53
54 54 echo % atom-log without basedir
55 55 "$TESTDIR/get-with-headers.py" localhost:$HGPORT '/a/atom-log' \
56 56 | grep '<link' | sed 's|//[.a-zA-Z0-9_-]*:[0-9][0-9]*/|//example.com:8080/|'
57 57
58 58 echo % rss-log without basedir
59 59 "$TESTDIR/get-with-headers.py" localhost:$HGPORT '/a/rss-log' \
60 60 | grep '<guid' | sed 's|//[.a-zA-Z0-9_-]*:[0-9][0-9]*/|//example.com:8080/|'
61 61
62 62 cat > paths.conf <<EOF
63 63 [paths]
64 64 t/a/=$root/a
65 65 b=$root/b
66 66 coll=$root/*
67 67 rcoll=$root/**
68 star=*
69 starstar=**
68 70 EOF
69 71
70 72 hg serve -p $HGPORT1 -d --pid-file=hg.pid --webdir-conf paths.conf \
71 73 -A access-paths.log -E error-paths-2.log
72 74 cat hg.pid >> $DAEMON_PIDS
73 75
74 76 echo % should succeed, slashy names
75 77 "$TESTDIR/get-with-headers.py" localhost:$HGPORT1 '/?style=raw'
76 78 "$TESTDIR/get-with-headers.py" localhost:$HGPORT1 '/?style=paper' \
77 79 | sed "s/[0-9]\{1,\} seconds\{0,1\} ago/seconds ago/"
78 80 "$TESTDIR/get-with-headers.py" localhost:$HGPORT1 '/t?style=raw'
79 81 "$TESTDIR/get-with-headers.py" localhost:$HGPORT1 '/t/?style=raw'
80 82 "$TESTDIR/get-with-headers.py" localhost:$HGPORT1 '/t/?style=paper' \
81 83 | sed "s/[0-9]\{1,\} seconds\{0,1\} ago/seconds ago/"
82 84 "$TESTDIR/get-with-headers.py" localhost:$HGPORT1 '/t/a?style=atom' \
83 85 | sed "s/http:\/\/[^/]*\//http:\/\/127.0.0.1\//"
84 86 "$TESTDIR/get-with-headers.py" localhost:$HGPORT1 '/t/a/?style=atom' \
85 87 | sed "s/http:\/\/[^/]*\//http:\/\/127.0.0.1\//"
86 88 "$TESTDIR/get-with-headers.py" localhost:$HGPORT1 '/t/a/file/tip/a?style=raw'
87 89 # Test [paths] '*' extension
88 90 "$TESTDIR/get-with-headers.py" localhost:$HGPORT1 '/coll/?style=raw'
89 91 "$TESTDIR/get-with-headers.py" localhost:$HGPORT1 '/coll/a/file/tip/a?style=raw'
90 92 #test [paths] '**' extension
91 93 "$TESTDIR/get-with-headers.py" localhost:$HGPORT1 '/rcoll/?style=raw'
92 94 "$TESTDIR/get-with-headers.py" localhost:$HGPORT1 '/rcoll/b/d/file/tip/d?style=raw'
93 95
94 96
95 97 "$TESTDIR/killdaemons.py"
96 98 cat > paths.conf <<EOF
97 99 [paths]
98 100 t/a = $root/a
99 101 t/b = $root/b
100 102 c = $root/c
101 103 [web]
102 104 descend=false
103 105 EOF
104 106
105 107 hg serve -p $HGPORT1 -d --pid-file=hg.pid --webdir-conf paths.conf \
106 108 -A access-paths.log -E error-paths-3.log
107 109 cat hg.pid >> $DAEMON_PIDS
108 110 echo % test descend = False
109 111 "$TESTDIR/get-with-headers.py" localhost:$HGPORT1 '/?style=raw'
110 112 "$TESTDIR/get-with-headers.py" localhost:$HGPORT1 '/t/?style=raw'
111 113
112 114
113 115 cat > collections.conf <<EOF
114 116 [collections]
115 117 $root=$root
116 118 EOF
117 119
118 120 hg serve --config web.baseurl=http://hg.example.com:8080/ -p $HGPORT2 -d \
119 121 --pid-file=hg.pid --webdir-conf collections.conf \
120 122 -A access-collections.log -E error-collections.log
121 123 cat hg.pid >> $DAEMON_PIDS
122 124
123 125 echo % collections: should succeed
124 126 "$TESTDIR/get-with-headers.py" localhost:$HGPORT2 '/?style=raw'
125 127 "$TESTDIR/get-with-headers.py" localhost:$HGPORT2 '/a/file/tip/a?style=raw'
126 128 "$TESTDIR/get-with-headers.py" localhost:$HGPORT2 '/b/file/tip/b?style=raw'
127 129 "$TESTDIR/get-with-headers.py" localhost:$HGPORT2 '/c/file/tip/c?style=raw'
128 130
129 131 echo % atom-log with basedir /
130 132 "$TESTDIR/get-with-headers.py" localhost:$HGPORT2 '/a/atom-log' \
131 133 | grep '<link' | sed 's|//[.a-zA-Z0-9_-]*:[0-9][0-9]*/|//example.com:8080/|'
132 134
133 135 echo % rss-log with basedir /
134 136 "$TESTDIR/get-with-headers.py" localhost:$HGPORT2 '/a/rss-log' \
135 137 | grep '<guid' | sed 's|//[.a-zA-Z0-9_-]*:[0-9][0-9]*/|//example.com:8080/|'
136 138
137 139 "$TESTDIR/killdaemons.py"
138 140
139 141 hg serve --config web.baseurl=http://hg.example.com:8080/foo/ -p $HGPORT2 -d \
140 142 --pid-file=hg.pid --webdir-conf collections.conf \
141 143 -A access-collections-2.log -E error-collections-2.log
142 144 cat hg.pid >> $DAEMON_PIDS
143 145
144 146 echo % atom-log with basedir /foo/
145 147 "$TESTDIR/get-with-headers.py" localhost:$HGPORT2 '/a/atom-log' \
146 148 | grep '<link' | sed 's|//[.a-zA-Z0-9_-]*:[0-9][0-9]*/|//example.com:8080/|'
147 149
148 150 echo % rss-log with basedir /foo/
149 151 "$TESTDIR/get-with-headers.py" localhost:$HGPORT2 '/a/rss-log' \
150 152 | grep '<guid' | sed 's|//[.a-zA-Z0-9_-]*:[0-9][0-9]*/|//example.com:8080/|'
151 153
152 154 echo % paths errors 1
153 155 cat error-paths-1.log
154 156 echo % paths errors 2
155 157 cat error-paths-2.log
156 158 echo % paths errors 3
157 159 cat error-paths-3.log
158 160 echo % collections errors
159 161 cat error-collections.log
160 162 echo % collections errors 2
161 163 cat error-collections-2.log
@@ -1,362 +1,443 b''
1 1 adding a
2 2 adding b
3 3 adding d
4 4 adding c
5 5 % should give a 404 - file does not exist
6 6 404 Not Found
7 7
8 8
9 9 error: bork@8580ff50825a: not found in manifest
10 10 % should succeed
11 11 200 Script output follows
12 12
13 13
14 14 /a/
15 15 /b/
16 16
17 17 200 Script output follows
18 18
19 19 a
20 20 200 Script output follows
21 21
22 22 b
23 23 % should give a 404 - repo is not published
24 24 404 Not Found
25 25
26 26
27 27 error: repository c not found
28 28 % atom-log without basedir
29 29 <link rel="self" href="http://example.com:8080/a/atom-log"/>
30 30 <link rel="alternate" href="http://example.com:8080/a/"/>
31 31 <link href="http://example.com:8080/a/rev/8580ff50825a"/>
32 32 % rss-log without basedir
33 33 <guid isPermaLink="true">http://example.com:8080/a/rev/8580ff50825a</guid>
34 34 % should succeed, slashy names
35 35 200 Script output follows
36 36
37 37
38 38 /t/a/
39 39 /b/
40 40 /coll/a/
41 41 /coll/a/.hg/patches/
42 42 /coll/b/
43 43 /coll/c/
44 44 /rcoll/a/
45 45 /rcoll/a/.hg/patches/
46 46 /rcoll/b/
47 47 /rcoll/b/d/
48 48 /rcoll/c/
49 /star/webdir/a/
50 /star/webdir/a/.hg/patches/
51 /star/webdir/b/
52 /star/webdir/c/
53 /starstar/webdir/a/
54 /starstar/webdir/a/.hg/patches/
55 /starstar/webdir/b/
56 /starstar/webdir/b/d/
57 /starstar/webdir/c/
49 58
50 59 200 Script output follows
51 60
52 61 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
53 62 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US">
54 63 <head>
55 64 <link rel="icon" href="/static/hgicon.png" type="image/png" />
56 65 <meta name="robots" content="index, nofollow" />
57 66 <link rel="stylesheet" href="/static/style-paper.css" type="text/css" />
58 67
59 68 <title>Mercurial repositories index</title>
60 69 </head>
61 70 <body>
62 71
63 72 <div class="container">
64 73 <div class="menu">
65 74 <a href="http://mercurial.selenic.com/">
66 75 <img src="/static/hglogo.png" width=75 height=90 border=0 alt="mercurial" /></a>
67 76 </div>
68 77 <div class="main">
69 78 <h2>Mercurial Repositories</h2>
70 79
71 80 <table class="bigtable">
72 81 <tr>
73 82 <th><a href="?sort=name">Name</a></th>
74 83 <th><a href="?sort=description">Description</a></th>
75 84 <th><a href="?sort=contact">Contact</a></th>
76 85 <th><a href="?sort=lastchange">Last modified</a></th>
77 86 <th>&nbsp;</th>
78 87 </tr>
79 88
80 89 <tr class="parity0">
81 90 <td><a href="/t/a/?style=paper">t/a</a></td>
82 91 <td>unknown</td>
83 92 <td>&#70;&#111;&#111;&#32;&#66;&#97;&#114;&#32;&#60;&#102;&#111;&#111;&#46;&#98;&#97;&#114;&#64;&#101;&#120;&#97;&#109;&#112;&#108;&#101;&#46;&#99;&#111;&#109;&#62;</td>
84 93 <td class="age">seconds ago</td>
85 94 <td class="indexlinks"></td>
86 95 </tr>
87 96
88 97 <tr class="parity1">
89 98 <td><a href="/b/?style=paper">b</a></td>
90 99 <td>unknown</td>
91 100 <td>&#70;&#111;&#111;&#32;&#66;&#97;&#114;&#32;&#60;&#102;&#111;&#111;&#46;&#98;&#97;&#114;&#64;&#101;&#120;&#97;&#109;&#112;&#108;&#101;&#46;&#99;&#111;&#109;&#62;</td>
92 101 <td class="age">seconds ago</td>
93 102 <td class="indexlinks"></td>
94 103 </tr>
95 104
96 105 <tr class="parity0">
97 106 <td><a href="/coll/a/?style=paper">coll/a</a></td>
98 107 <td>unknown</td>
99 108 <td>&#70;&#111;&#111;&#32;&#66;&#97;&#114;&#32;&#60;&#102;&#111;&#111;&#46;&#98;&#97;&#114;&#64;&#101;&#120;&#97;&#109;&#112;&#108;&#101;&#46;&#99;&#111;&#109;&#62;</td>
100 109 <td class="age">seconds ago</td>
101 110 <td class="indexlinks"></td>
102 111 </tr>
103 112
104 113 <tr class="parity1">
105 114 <td><a href="/coll/a/.hg/patches/?style=paper">coll/a/.hg/patches</a></td>
106 115 <td>unknown</td>
107 116 <td>&#70;&#111;&#111;&#32;&#66;&#97;&#114;&#32;&#60;&#102;&#111;&#111;&#46;&#98;&#97;&#114;&#64;&#101;&#120;&#97;&#109;&#112;&#108;&#101;&#46;&#99;&#111;&#109;&#62;</td>
108 117 <td class="age">seconds ago</td>
109 118 <td class="indexlinks"></td>
110 119 </tr>
111 120
112 121 <tr class="parity0">
113 122 <td><a href="/coll/b/?style=paper">coll/b</a></td>
114 123 <td>unknown</td>
115 124 <td>&#70;&#111;&#111;&#32;&#66;&#97;&#114;&#32;&#60;&#102;&#111;&#111;&#46;&#98;&#97;&#114;&#64;&#101;&#120;&#97;&#109;&#112;&#108;&#101;&#46;&#99;&#111;&#109;&#62;</td>
116 125 <td class="age">seconds ago</td>
117 126 <td class="indexlinks"></td>
118 127 </tr>
119 128
120 129 <tr class="parity1">
121 130 <td><a href="/coll/c/?style=paper">coll/c</a></td>
122 131 <td>unknown</td>
123 132 <td>&#70;&#111;&#111;&#32;&#66;&#97;&#114;&#32;&#60;&#102;&#111;&#111;&#46;&#98;&#97;&#114;&#64;&#101;&#120;&#97;&#109;&#112;&#108;&#101;&#46;&#99;&#111;&#109;&#62;</td>
124 133 <td class="age">seconds ago</td>
125 134 <td class="indexlinks"></td>
126 135 </tr>
127 136
128 137 <tr class="parity0">
129 138 <td><a href="/rcoll/a/?style=paper">rcoll/a</a></td>
130 139 <td>unknown</td>
131 140 <td>&#70;&#111;&#111;&#32;&#66;&#97;&#114;&#32;&#60;&#102;&#111;&#111;&#46;&#98;&#97;&#114;&#64;&#101;&#120;&#97;&#109;&#112;&#108;&#101;&#46;&#99;&#111;&#109;&#62;</td>
132 141 <td class="age">seconds ago</td>
133 142 <td class="indexlinks"></td>
134 143 </tr>
135 144
136 145 <tr class="parity1">
137 146 <td><a href="/rcoll/a/.hg/patches/?style=paper">rcoll/a/.hg/patches</a></td>
138 147 <td>unknown</td>
139 148 <td>&#70;&#111;&#111;&#32;&#66;&#97;&#114;&#32;&#60;&#102;&#111;&#111;&#46;&#98;&#97;&#114;&#64;&#101;&#120;&#97;&#109;&#112;&#108;&#101;&#46;&#99;&#111;&#109;&#62;</td>
140 149 <td class="age">seconds ago</td>
141 150 <td class="indexlinks"></td>
142 151 </tr>
143 152
144 153 <tr class="parity0">
145 154 <td><a href="/rcoll/b/?style=paper">rcoll/b</a></td>
146 155 <td>unknown</td>
147 156 <td>&#70;&#111;&#111;&#32;&#66;&#97;&#114;&#32;&#60;&#102;&#111;&#111;&#46;&#98;&#97;&#114;&#64;&#101;&#120;&#97;&#109;&#112;&#108;&#101;&#46;&#99;&#111;&#109;&#62;</td>
148 157 <td class="age">seconds ago</td>
149 158 <td class="indexlinks"></td>
150 159 </tr>
151 160
152 161 <tr class="parity1">
153 162 <td><a href="/rcoll/b/d/?style=paper">rcoll/b/d</a></td>
154 163 <td>unknown</td>
155 164 <td>&#70;&#111;&#111;&#32;&#66;&#97;&#114;&#32;&#60;&#102;&#111;&#111;&#46;&#98;&#97;&#114;&#64;&#101;&#120;&#97;&#109;&#112;&#108;&#101;&#46;&#99;&#111;&#109;&#62;</td>
156 165 <td class="age">seconds ago</td>
157 166 <td class="indexlinks"></td>
158 167 </tr>
159 168
160 169 <tr class="parity0">
161 170 <td><a href="/rcoll/c/?style=paper">rcoll/c</a></td>
162 171 <td>unknown</td>
163 172 <td>&#70;&#111;&#111;&#32;&#66;&#97;&#114;&#32;&#60;&#102;&#111;&#111;&#46;&#98;&#97;&#114;&#64;&#101;&#120;&#97;&#109;&#112;&#108;&#101;&#46;&#99;&#111;&#109;&#62;</td>
164 173 <td class="age">seconds ago</td>
165 174 <td class="indexlinks"></td>
166 175 </tr>
167 176
177 <tr class="parity1">
178 <td><a href="/star/webdir/a/?style=paper">star/webdir/a</a></td>
179 <td>unknown</td>
180 <td>&#70;&#111;&#111;&#32;&#66;&#97;&#114;&#32;&#60;&#102;&#111;&#111;&#46;&#98;&#97;&#114;&#64;&#101;&#120;&#97;&#109;&#112;&#108;&#101;&#46;&#99;&#111;&#109;&#62;</td>
181 <td class="age">seconds ago</td>
182 <td class="indexlinks"></td>
183 </tr>
184
185 <tr class="parity0">
186 <td><a href="/star/webdir/a/.hg/patches/?style=paper">star/webdir/a/.hg/patches</a></td>
187 <td>unknown</td>
188 <td>&#70;&#111;&#111;&#32;&#66;&#97;&#114;&#32;&#60;&#102;&#111;&#111;&#46;&#98;&#97;&#114;&#64;&#101;&#120;&#97;&#109;&#112;&#108;&#101;&#46;&#99;&#111;&#109;&#62;</td>
189 <td class="age">seconds ago</td>
190 <td class="indexlinks"></td>
191 </tr>
192
193 <tr class="parity1">
194 <td><a href="/star/webdir/b/?style=paper">star/webdir/b</a></td>
195 <td>unknown</td>
196 <td>&#70;&#111;&#111;&#32;&#66;&#97;&#114;&#32;&#60;&#102;&#111;&#111;&#46;&#98;&#97;&#114;&#64;&#101;&#120;&#97;&#109;&#112;&#108;&#101;&#46;&#99;&#111;&#109;&#62;</td>
197 <td class="age">seconds ago</td>
198 <td class="indexlinks"></td>
199 </tr>
200
201 <tr class="parity0">
202 <td><a href="/star/webdir/c/?style=paper">star/webdir/c</a></td>
203 <td>unknown</td>
204 <td>&#70;&#111;&#111;&#32;&#66;&#97;&#114;&#32;&#60;&#102;&#111;&#111;&#46;&#98;&#97;&#114;&#64;&#101;&#120;&#97;&#109;&#112;&#108;&#101;&#46;&#99;&#111;&#109;&#62;</td>
205 <td class="age">seconds ago</td>
206 <td class="indexlinks"></td>
207 </tr>
208
209 <tr class="parity1">
210 <td><a href="/starstar/webdir/a/?style=paper">starstar/webdir/a</a></td>
211 <td>unknown</td>
212 <td>&#70;&#111;&#111;&#32;&#66;&#97;&#114;&#32;&#60;&#102;&#111;&#111;&#46;&#98;&#97;&#114;&#64;&#101;&#120;&#97;&#109;&#112;&#108;&#101;&#46;&#99;&#111;&#109;&#62;</td>
213 <td class="age">seconds ago</td>
214 <td class="indexlinks"></td>
215 </tr>
216
217 <tr class="parity0">
218 <td><a href="/starstar/webdir/a/.hg/patches/?style=paper">starstar/webdir/a/.hg/patches</a></td>
219 <td>unknown</td>
220 <td>&#70;&#111;&#111;&#32;&#66;&#97;&#114;&#32;&#60;&#102;&#111;&#111;&#46;&#98;&#97;&#114;&#64;&#101;&#120;&#97;&#109;&#112;&#108;&#101;&#46;&#99;&#111;&#109;&#62;</td>
221 <td class="age">seconds ago</td>
222 <td class="indexlinks"></td>
223 </tr>
224
225 <tr class="parity1">
226 <td><a href="/starstar/webdir/b/?style=paper">starstar/webdir/b</a></td>
227 <td>unknown</td>
228 <td>&#70;&#111;&#111;&#32;&#66;&#97;&#114;&#32;&#60;&#102;&#111;&#111;&#46;&#98;&#97;&#114;&#64;&#101;&#120;&#97;&#109;&#112;&#108;&#101;&#46;&#99;&#111;&#109;&#62;</td>
229 <td class="age">seconds ago</td>
230 <td class="indexlinks"></td>
231 </tr>
232
233 <tr class="parity0">
234 <td><a href="/starstar/webdir/b/d/?style=paper">starstar/webdir/b/d</a></td>
235 <td>unknown</td>
236 <td>&#70;&#111;&#111;&#32;&#66;&#97;&#114;&#32;&#60;&#102;&#111;&#111;&#46;&#98;&#97;&#114;&#64;&#101;&#120;&#97;&#109;&#112;&#108;&#101;&#46;&#99;&#111;&#109;&#62;</td>
237 <td class="age">seconds ago</td>
238 <td class="indexlinks"></td>
239 </tr>
240
241 <tr class="parity1">
242 <td><a href="/starstar/webdir/c/?style=paper">starstar/webdir/c</a></td>
243 <td>unknown</td>
244 <td>&#70;&#111;&#111;&#32;&#66;&#97;&#114;&#32;&#60;&#102;&#111;&#111;&#46;&#98;&#97;&#114;&#64;&#101;&#120;&#97;&#109;&#112;&#108;&#101;&#46;&#99;&#111;&#109;&#62;</td>
245 <td class="age">seconds ago</td>
246 <td class="indexlinks"></td>
247 </tr>
248
168 249 </table>
169 250 </div>
170 251 </div>
171 252
172 253
173 254 </body>
174 255 </html>
175 256
176 257 200 Script output follows
177 258
178 259
179 260 /t/a/
180 261
181 262 200 Script output follows
182 263
183 264
184 265 /t/a/
185 266
186 267 200 Script output follows
187 268
188 269 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
189 270 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US">
190 271 <head>
191 272 <link rel="icon" href="/static/hgicon.png" type="image/png" />
192 273 <meta name="robots" content="index, nofollow" />
193 274 <link rel="stylesheet" href="/static/style-paper.css" type="text/css" />
194 275
195 276 <title>Mercurial repositories index</title>
196 277 </head>
197 278 <body>
198 279
199 280 <div class="container">
200 281 <div class="menu">
201 282 <a href="http://mercurial.selenic.com/">
202 283 <img src="/static/hglogo.png" width=75 height=90 border=0 alt="mercurial" /></a>
203 284 </div>
204 285 <div class="main">
205 286 <h2>Mercurial Repositories</h2>
206 287
207 288 <table class="bigtable">
208 289 <tr>
209 290 <th><a href="?sort=name">Name</a></th>
210 291 <th><a href="?sort=description">Description</a></th>
211 292 <th><a href="?sort=contact">Contact</a></th>
212 293 <th><a href="?sort=lastchange">Last modified</a></th>
213 294 <th>&nbsp;</th>
214 295 </tr>
215 296
216 297 <tr class="parity0">
217 298 <td><a href="/t/a/?style=paper">a</a></td>
218 299 <td>unknown</td>
219 300 <td>&#70;&#111;&#111;&#32;&#66;&#97;&#114;&#32;&#60;&#102;&#111;&#111;&#46;&#98;&#97;&#114;&#64;&#101;&#120;&#97;&#109;&#112;&#108;&#101;&#46;&#99;&#111;&#109;&#62;</td>
220 301 <td class="age">seconds ago</td>
221 302 <td class="indexlinks"></td>
222 303 </tr>
223 304
224 305 </table>
225 306 </div>
226 307 </div>
227 308
228 309
229 310 </body>
230 311 </html>
231 312
232 313 200 Script output follows
233 314
234 315 <?xml version="1.0" encoding="ascii"?>
235 316 <feed xmlns="http://127.0.0.1/2005/Atom">
236 317 <!-- Changelog -->
237 318 <id>http://127.0.0.1/t/a/</id>
238 319 <link rel="self" href="http://127.0.0.1/t/a/atom-log"/>
239 320 <link rel="alternate" href="http://127.0.0.1/t/a/"/>
240 321 <title>t/a Changelog</title>
241 322 <updated>1970-01-01T00:00:01+00:00</updated>
242 323
243 324 <entry>
244 325 <title>a</title>
245 326 <id>http://127.0.0.1/t/a/#changeset-8580ff50825a50c8f716709acdf8de0deddcd6ab</id>
246 327 <link href="http://127.0.0.1/t/a/rev/8580ff50825a"/>
247 328 <author>
248 329 <name>test</name>
249 330 <email>&#116;&#101;&#115;&#116;</email>
250 331 </author>
251 332 <updated>1970-01-01T00:00:01+00:00</updated>
252 333 <published>1970-01-01T00:00:01+00:00</published>
253 334 <content type="xhtml">
254 335 <div xmlns="http://127.0.0.1/1999/xhtml">
255 336 <pre xml:space="preserve">a</pre>
256 337 </div>
257 338 </content>
258 339 </entry>
259 340
260 341 </feed>
261 342 200 Script output follows
262 343
263 344 <?xml version="1.0" encoding="ascii"?>
264 345 <feed xmlns="http://127.0.0.1/2005/Atom">
265 346 <!-- Changelog -->
266 347 <id>http://127.0.0.1/t/a/</id>
267 348 <link rel="self" href="http://127.0.0.1/t/a/atom-log"/>
268 349 <link rel="alternate" href="http://127.0.0.1/t/a/"/>
269 350 <title>t/a Changelog</title>
270 351 <updated>1970-01-01T00:00:01+00:00</updated>
271 352
272 353 <entry>
273 354 <title>a</title>
274 355 <id>http://127.0.0.1/t/a/#changeset-8580ff50825a50c8f716709acdf8de0deddcd6ab</id>
275 356 <link href="http://127.0.0.1/t/a/rev/8580ff50825a"/>
276 357 <author>
277 358 <name>test</name>
278 359 <email>&#116;&#101;&#115;&#116;</email>
279 360 </author>
280 361 <updated>1970-01-01T00:00:01+00:00</updated>
281 362 <published>1970-01-01T00:00:01+00:00</published>
282 363 <content type="xhtml">
283 364 <div xmlns="http://127.0.0.1/1999/xhtml">
284 365 <pre xml:space="preserve">a</pre>
285 366 </div>
286 367 </content>
287 368 </entry>
288 369
289 370 </feed>
290 371 200 Script output follows
291 372
292 373 a
293 374 200 Script output follows
294 375
295 376
296 377 /coll/a/
297 378 /coll/a/.hg/patches/
298 379 /coll/b/
299 380 /coll/c/
300 381
301 382 200 Script output follows
302 383
303 384 a
304 385 200 Script output follows
305 386
306 387
307 388 /rcoll/a/
308 389 /rcoll/a/.hg/patches/
309 390 /rcoll/b/
310 391 /rcoll/b/d/
311 392 /rcoll/c/
312 393
313 394 200 Script output follows
314 395
315 396 d
316 397 % test descend = False
317 398 200 Script output follows
318 399
319 400
320 401 /c/
321 402
322 403 200 Script output follows
323 404
324 405
325 406 /t/a/
326 407 /t/b/
327 408
328 409 % collections: should succeed
329 410 200 Script output follows
330 411
331 412
332 413 /a/
333 414 /a/.hg/patches/
334 415 /b/
335 416 /c/
336 417
337 418 200 Script output follows
338 419
339 420 a
340 421 200 Script output follows
341 422
342 423 b
343 424 200 Script output follows
344 425
345 426 c
346 427 % atom-log with basedir /
347 428 <link rel="self" href="http://example.com:8080/a/atom-log"/>
348 429 <link rel="alternate" href="http://example.com:8080/a/"/>
349 430 <link href="http://example.com:8080/a/rev/8580ff50825a"/>
350 431 % rss-log with basedir /
351 432 <guid isPermaLink="true">http://example.com:8080/a/rev/8580ff50825a</guid>
352 433 % atom-log with basedir /foo/
353 434 <link rel="self" href="http://example.com:8080/foo/a/atom-log"/>
354 435 <link rel="alternate" href="http://example.com:8080/foo/a/"/>
355 436 <link href="http://example.com:8080/foo/a/rev/8580ff50825a"/>
356 437 % rss-log with basedir /foo/
357 438 <guid isPermaLink="true">http://example.com:8080/foo/a/rev/8580ff50825a</guid>
358 439 % paths errors 1
359 440 % paths errors 2
360 441 % paths errors 3
361 442 % collections errors
362 443 % collections errors 2
@@ -1,21 +1,21 b''
1 1 reverting foo
2 2 changeset 2:4d9e78aaceee backs out changeset 1:b515023e500e
3 3 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
4 4 searching for copies back to rev 1
5 5 unmatched files in local:
6 6 bar
7 7 resolving manifests
8 8 overwrite None partial False
9 9 ancestor bbd179dfa0a7 local 71766447bdbb+ remote 4d9e78aaceee
10 10 foo: remote is newer -> g
11 update: foo 1/1 files (100.00%)
11 updating: foo 1/1 files (100.00%)
12 12 getting foo
13 13 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
14 14 (branch merge, don't forget to commit)
15 15 n 0 -2 unset foo
16 16 M foo
17 17 c6fc755d7e68f49f880599da29f15add41f42f5a 644 foo
18 18 rev offset length base linkrev nodeid p1 p2
19 19 0 0 5 0 0 2ed2a3912a0b 000000000000 000000000000
20 20 1 5 9 1 1 6f4310b00b9a 2ed2a3912a0b 000000000000
21 21 2 14 5 2 2 c6fc755d7e68 6f4310b00b9a 000000000000
@@ -1,62 +1,62 b''
1 1 adding 1
2 2 adding 2
3 3 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
4 4 created new head
5 5 searching for copies back to rev 1
6 6 unmatched files in other:
7 7 1a
8 8 all copies found (* = to merge, ! = divergent):
9 9 1a -> 1
10 10 checking for directory renames
11 11 resolving manifests
12 12 overwrite None partial False
13 13 ancestor 81f4b099af3d local c64f439569a9+ remote c12dcd37c90a
14 14 1: other deleted -> r
15 15 1a: remote created -> g
16 update: 1 1/2 files (50.00%)
16 updating: 1 1/2 files (50.00%)
17 17 removing 1
18 update: 1a 2/2 files (100.00%)
18 updating: 1a 2/2 files (100.00%)
19 19 getting 1a
20 20 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
21 21 (branch merge, don't forget to commit)
22 22 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
23 23 created new head
24 24 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
25 25 searching for copies back to rev 1
26 26 unmatched files in local:
27 27 1a
28 28 all copies found (* = to merge, ! = divergent):
29 29 1a -> 1 *
30 30 checking for directory renames
31 31 resolving manifests
32 32 overwrite None partial False
33 33 ancestor c64f439569a9 local e327dca35ac8+ remote 746e9549ea96
34 34 1a: local copied/moved to 1 -> m
35 35 preserving 1a for resolve of 1a
36 update: 1a 1/1 files (100.00%)
36 updating: 1a 1/1 files (100.00%)
37 37 picked tool 'internal:merge' for 1a (binary False symlink False)
38 38 merging 1a and 1 to 1a
39 39 my 1a@e327dca35ac8+ other 1@746e9549ea96 ancestor 1@81f4b099af3d
40 40 premerge successful
41 41 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
42 42 (branch merge, don't forget to commit)
43 43 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
44 44 searching for copies back to rev 1
45 45 unmatched files in other:
46 46 1a
47 47 all copies found (* = to merge, ! = divergent):
48 48 1a -> 1 *
49 49 checking for directory renames
50 50 resolving manifests
51 51 overwrite None partial False
52 52 ancestor c64f439569a9 local 746e9549ea96+ remote e327dca35ac8
53 53 1: remote moved to 1a -> m
54 54 preserving 1 for resolve of 1a
55 55 removing 1
56 update: 1 1/1 files (100.00%)
56 updating: 1 1/1 files (100.00%)
57 57 picked tool 'internal:merge' for 1a (binary False symlink False)
58 58 merging 1 and 1a to 1a
59 59 my 1a@746e9549ea96+ other 1a@e327dca35ac8 ancestor 1@81f4b099af3d
60 60 premerge successful
61 61 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
62 62 (branch merge, don't forget to commit)
@@ -1,409 +1,418 b''
1 1 #!/bin/sh
2 2
3 3 cat <<EOF >> $HGRCPATH
4 4 [extensions]
5 5 keyword =
6 6 mq =
7 7 notify =
8 8 record =
9 9 transplant =
10 10 [ui]
11 11 interactive = true
12 12 EOF
13 13
14 14 # demo before [keyword] files are set up
15 15 # would succeed without uisetup otherwise
16 16 echo % hg kwdemo
17 17 hg --quiet kwdemo \
18 18 | sed -e 's![^ ][^ ]*demo.txt,v!/TMP/demo.txt,v!' \
19 19 -e 's/,v [a-z0-9][a-z0-9]* /,v xxxxxxxxxxxx /' \
20 20 -e '/[$]Revision/ s/: [a-z0-9][a-z0-9]* /: xxxxxxxxxxxx /' \
21 21 -e 's! 20[0-9][0-9]/[01][0-9]/[0-3][0-9] [0-2][0-9]:[0-6][0-9]:[0-6][0-9]! 2000/00/00 00:00:00!'
22 22
23 23 hg --quiet kwdemo "Branch = {branches}"
24 24
25 25 cat <<EOF >> $HGRCPATH
26 26 [keyword]
27 27 ** =
28 28 b = ignore
29 29 [hooks]
30 30 commit=
31 31 commit.test=cp a hooktest
32 32 EOF
33 33
34 34 hg init Test-bndl
35 35 cd Test-bndl
36 36
37 37 echo % kwshrink should exit silently in empty/invalid repo
38 38 hg kwshrink
39 39
40 40 # Symlinks cannot be created on Windows. The bundle was made with:
41 41 #
42 42 # hg init t
43 43 # cd t
44 44 # echo a > a
45 45 # ln -s a sym
46 46 # hg add sym
47 47 # hg ci -m addsym -u mercurial
48 48 # hg bundle --base null ../test-keyword.hg
49 49 #
50 50 hg pull -u "$TESTDIR/test-keyword.hg" \
51 51 | sed 's/pulling from.*test-keyword.hg/pulling from test-keyword.hg/'
52 52
53 53 echo 'expand $Id$' > a
54 54 echo 'do not process $Id:' >> a
55 55 echo 'xxx $' >> a
56 56 echo 'ignore $Id$' > b
57 57 echo % cat
58 58 cat a b
59 59
60 60 echo % no kwfiles
61 61 hg kwfiles
62 62 echo % untracked candidates
63 63 hg -v kwfiles --unknown
64 64
65 65 echo % addremove
66 66 hg addremove
67 67 echo % status
68 68 hg status
69 69
70 70 echo % default keyword expansion including commit hook
71 71 echo % interrupted commit should not change state or run commit hook
72 72 hg --debug commit
73 73 echo % status
74 74 hg status
75 75
76 76 echo % commit
77 77 hg --debug commit -mabsym -u 'User Name <user@example.com>'
78 78 echo % status
79 79 hg status
80 80 echo % identify
81 81 hg debugrebuildstate
82 82 hg --quiet identify
83 83 echo % cat
84 84 cat a b
85 85 echo % hg cat
86 86 hg cat sym a b
87 87
88 88 echo
89 89 echo % diff a hooktest
90 90 diff a hooktest
91 91
92 92 echo % removing commit hook from config
93 93 sed -e '/\[hooks\]/,$ d' "$HGRCPATH" > $HGRCPATH.nohook
94 94 mv "$HGRCPATH".nohook "$HGRCPATH"
95 95 rm hooktest
96 96
97 97 echo % bundle
98 98 hg bundle --base null ../kw.hg
99 99
100 100 cd ..
101 101 hg init Test
102 102 cd Test
103 103
104 104 echo % notify on pull to check whether keywords stay as is in email
105 105 echo % ie. if patch.diff wrapper acts as it should
106 106
107 107 cat <<EOF >> $HGRCPATH
108 108 [hooks]
109 109 incoming.notify = python:hgext.notify.hook
110 110 [notify]
111 111 sources = pull
112 112 diffstat = False
113 113 [reposubs]
114 114 * = Test
115 115 EOF
116 116
117 117 echo % pull from bundle
118 118 hg pull -u ../kw.hg 2>&1 | sed -e '/^Content-Type:/,/^diffs (/ d'
119 119
120 120 echo % remove notify config
121 121 sed -e '/\[hooks\]/,$ d' "$HGRCPATH" > $HGRCPATH.nonotify
122 122 mv "$HGRCPATH".nonotify "$HGRCPATH"
123 123
124 124 echo % touch
125 125 touch a b
126 126 echo % status
127 127 hg status
128 128
129 129 rm sym a b
130 130 echo % update
131 131 hg update -C
132 132 echo % cat
133 133 cat a b
134 134
135 135 echo % check whether expansion is filewise
136 136 echo '$Id$' > c
137 137 echo 'tests for different changenodes' >> c
138 138 echo % commit c
139 139 hg commit -A -mcndiff -d '1 0' -u 'User Name <user@example.com>'
140 140 echo % force expansion
141 141 hg -v kwexpand
142 142 echo % compare changenodes in a c
143 143 cat a c
144 144
145 145 echo % record chunk
146 146 python -c \
147 147 'l=open("a").readlines();l.insert(1,"foo\n");l.append("bar\n");open("a","w").writelines(l);'
148 148 hg record -d '1 10' -m rectest<<EOF
149 149 y
150 150 y
151 151 n
152 152 EOF
153 153 echo
154 154 hg identify
155 155 hg status
156 156 echo % cat modified file
157 157 cat a
158 158 hg diff | grep -v 'b/a'
159 159 hg rollback
160 160
161 161 echo % record file
162 162 echo foo > msg
163 163 # do not use "hg record -m" here!
164 164 hg record -l msg -d '1 11'<<EOF
165 165 y
166 166 y
167 167 y
168 168 EOF
169 169 echo % a should be clean
170 170 hg status -A a
171 171 rm msg
172 172 hg rollback
173 173 hg update -C
174 174
175 175 echo % init --mq
176 176 hg init --mq
177 177 echo % qimport
178 178 hg qimport -r tip -n mqtest.diff
179 179 echo % commit --mq
180 180 hg commit --mq -m mqtest
181 181 echo % keywords should not be expanded in patch
182 182 cat .hg/patches/mqtest.diff
183 183 echo % qpop
184 184 hg qpop
185 185 echo % qgoto - should imply qpush
186 186 hg qgoto mqtest.diff
187 187 echo % cat
188 188 cat c
189 189 echo % hg cat
190 190 hg cat c
191 191 echo % keyword should not be expanded in filelog
192 192 hg --config 'extensions.keyword=!' cat c
193 193 echo % qpop and move on
194 194 hg qpop
195 195
196 196 echo % copy
197 197 hg cp a c
198 198
199 199 echo % kwfiles added
200 200 hg kwfiles
201 201
202 202 echo % commit
203 203 hg --debug commit -ma2c -d '1 0' -u 'User Name <user@example.com>'
204 204 echo % cat a c
205 205 cat a c
206 206 echo % touch copied c
207 207 touch c
208 208 echo % status
209 209 hg status
210 210
211 211 echo % kwfiles
212 212 hg kwfiles
213 213 echo % ignored files
214 214 hg -v kwfiles --ignore
215 215 echo % all files
216 216 hg kwfiles --all
217 217
218 218 echo % diff --rev
219 219 hg diff --rev 1 | grep -v 'b/c'
220 220
221 221 echo % rollback
222 222 hg rollback
223 223 echo % status
224 224 hg status
225 225 echo % update -C
226 226 hg update --clean
227 227
228 228 echo % custom keyword expansion
229 229 echo % try with kwdemo
230 230 hg --quiet kwdemo "Xinfo = {author}: {desc}"
231 231
232 232 cat <<EOF >>$HGRCPATH
233 233 [keywordmaps]
234 234 Id = {file} {node|short} {date|rfc822date} {author|user}
235 235 Xinfo = {author}: {desc}
236 236 EOF
237 237
238 238 echo % cat
239 239 cat a b
240 240 echo % hg cat
241 241 hg cat sym a b
242 242
243 243 echo
244 244 echo '$Xinfo$' >> a
245 245 cat <<EOF >> log
246 246 firstline
247 247 secondline
248 248 EOF
249 249
250 250 echo % interrupted commit should not change state
251 251 hg commit
252 252 echo % status
253 253 hg status
254 254
255 255 echo % commit
256 256 hg --debug commit -l log -d '2 0' -u 'User Name <user@example.com>'
257 257 rm log
258 258 echo % status
259 259 hg status
260 260 echo % verify
261 261 hg verify
262 262
263 263 echo % cat
264 264 cat a b
265 265 echo % hg cat
266 266 hg cat sym a b
267 267 echo
268 268 echo % annotate
269 269 hg annotate a
270 270
271 271 echo % remove
272 272 hg debugrebuildstate
273 273 hg remove a
274 274 hg --debug commit -m rma
275 275 echo % status
276 276 hg status
277 277 echo % rollback
278 278 hg rollback
279 279 echo % status
280 280 hg status
281 281 echo % revert a
282 282 hg revert --no-backup --rev tip a
283 283 echo % cat a
284 284 cat a
285 285
286 echo % clone
287 cd ..
288
289 echo % expansion in dest
290 hg --quiet clone Test globalconf
291 cat globalconf/a
292 echo % no expansion in dest
293 hg --quiet --config 'keyword.**=ignore' clone Test localconf
294 cat localconf/a
295
286 296 echo % clone to test incoming
287 cd ..
288 297 hg clone -r1 Test Test-a
289 298 cd Test-a
290 299 cat <<EOF >> .hg/hgrc
291 300 [paths]
292 301 default = ../Test
293 302 EOF
294 303 echo % incoming
295 304 # remove path to temp dir
296 305 hg incoming | sed -e 's/^\(comparing with \).*\(test-keyword.*\)/\1\2/'
297 306
298 307 sed -e 's/Id.*/& rejecttest/' a > a.new
299 308 mv a.new a
300 309 echo % commit rejecttest
301 310 hg --debug commit -m'rejects?' -d '3 0' -u 'User Name <user@example.com>'
302 311 echo % export
303 312 hg export -o ../rejecttest.diff tip
304 313
305 314 cd ../Test
306 315 echo % import
307 316 hg import ../rejecttest.diff
308 317 echo % cat
309 318 cat a b
310 319 echo
311 320 echo % rollback
312 321 hg rollback
313 322 echo % clean update
314 323 hg update --clean
315 324
316 325 echo % kwexpand/kwshrink on selected files
317 326 mkdir x
318 327 echo % copy a x/a
319 328 hg copy a x/a
320 329 echo % kwexpand a
321 330 hg --verbose kwexpand a
322 331 echo % kwexpand x/a should abort
323 332 hg --verbose kwexpand x/a
324 333 cd x
325 334 hg --debug commit -m xa -d '3 0' -u 'User Name <user@example.com>'
326 335 echo % cat a
327 336 cat a
328 337 echo % kwshrink a inside directory x
329 338 hg --verbose kwshrink a
330 339 echo % cat a
331 340 cat a
332 341 cd ..
333 342
334 343 echo % kwexpand nonexistent
335 344 hg kwexpand nonexistent 2>&1 | sed 's/nonexistent:.*/nonexistent:/'
336 345
337 346 echo % hg serve
338 347 hg serve -p $HGPORT -d --pid-file=hg.pid -A access.log -E errors.log
339 348 cat hg.pid >> $DAEMON_PIDS
340 349 echo % expansion
341 350 echo % hgweb file
342 351 ("$TESTDIR/get-with-headers.py" localhost:$HGPORT '/file/tip/a/?style=raw')
343 352 echo % no expansion
344 353 echo % hgweb annotate
345 354 ("$TESTDIR/get-with-headers.py" localhost:$HGPORT '/annotate/tip/a/?style=raw')
346 355 echo % hgweb changeset
347 356 ("$TESTDIR/get-with-headers.py" localhost:$HGPORT '/rev/tip/?style=raw')
348 357 echo % hgweb filediff
349 358 ("$TESTDIR/get-with-headers.py" localhost:$HGPORT '/diff/bb948857c743/a?style=raw')
350 359 echo % errors encountered
351 360 cat errors.log
352 361
353 362 echo % merge/resolve
354 363 echo '$Id$' > m
355 364 hg add m
356 365 hg commit -m 4kw
357 366 echo foo >> m
358 367 hg commit -m 5foo
359 368 echo % simplemerge
360 369 hg update 4
361 370 echo foo >> m
362 371 hg commit -m 6foo
363 372 hg merge
364 373 hg commit -m simplemerge
365 374 cat m
366 375 echo % conflict
367 376 hg update 4
368 377 echo bar >> m
369 378 hg commit -m 8bar
370 379 hg merge
371 380 echo % keyword stays outside conflict zone
372 381 cat m
373 382 echo % resolve to local
374 383 HGMERGE=internal:local hg resolve -a
375 384 hg commit -m localresolve
376 385 cat m
377 386
378 387 echo % test restricted mode with transplant -b
379 388 hg update 6
380 389 hg branch foo
381 390 mv a a.bak
382 391 echo foobranch > a
383 392 cat a.bak >> a
384 393 rm a.bak
385 394 hg commit -m 9foobranch
386 395 hg update default
387 396 hg -y transplant -b foo tip
388 397 echo % no expansion in changeset
389 398 hg tip -p
390 399 echo % expansion in file
391 400 head -n 2 a
392 401 hg -q rollback
393 402 hg -q update -C
394 403
395 404 echo % switch off expansion
396 405 echo % kwshrink with unknown file u
397 406 cp a u
398 407 hg --verbose kwshrink
399 408 echo % cat
400 409 cat a b
401 410 echo % hg cat
402 411 hg cat sym a b
403 412 echo
404 413 rm "$HGRCPATH"
405 414 echo % cat
406 415 cat a b
407 416 echo % hg cat
408 417 hg cat sym a b
409 418 echo
@@ -1,536 +1,547 b''
1 1 % hg kwdemo
2 2 [extensions]
3 3 keyword =
4 4 [keyword]
5 5 demo.txt =
6 6 [keywordmaps]
7 7 Author = {author|user}
8 8 Date = {date|utcdate}
9 9 Header = {root}/{file},v {node|short} {date|utcdate} {author|user}
10 10 Id = {file|basename},v {node|short} {date|utcdate} {author|user}
11 11 RCSFile = {file|basename},v
12 12 RCSfile = {file|basename},v
13 13 Revision = {node|short}
14 14 Source = {root}/{file},v
15 15 $Author: test $
16 16 $Date: 2000/00/00 00:00:00 $
17 17 $Header: /TMP/demo.txt,v xxxxxxxxxxxx 2000/00/00 00:00:00 test $
18 18 $Id: demo.txt,v xxxxxxxxxxxx 2000/00/00 00:00:00 test $
19 19 $RCSFile: demo.txt,v $
20 20 $RCSfile: demo.txt,v $
21 21 $Revision: xxxxxxxxxxxx $
22 22 $Source: /TMP/demo.txt,v $
23 23 [extensions]
24 24 keyword =
25 25 [keyword]
26 26 demo.txt =
27 27 [keywordmaps]
28 28 Branch = {branches}
29 29 $Branch: demobranch $
30 30 % kwshrink should exit silently in empty/invalid repo
31 31 pulling from test-keyword.hg
32 32 requesting all changes
33 33 adding changesets
34 34 adding manifests
35 35 adding file changes
36 36 added 1 changesets with 1 changes to 1 files
37 37 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
38 38 % cat
39 39 expand $Id$
40 40 do not process $Id:
41 41 xxx $
42 42 ignore $Id$
43 43 % no kwfiles
44 44 % untracked candidates
45 45 k a
46 46 % addremove
47 47 adding a
48 48 adding b
49 49 % status
50 50 A a
51 51 A b
52 52 % default keyword expansion including commit hook
53 53 % interrupted commit should not change state or run commit hook
54 54 abort: empty commit message
55 55 % status
56 56 A a
57 57 A b
58 58 % commit
59 59 a
60 60 b
61 61 overwriting a expanding keywords
62 62 running hook commit.test: cp a hooktest
63 63 committed changeset 1:ef63ca68695bc9495032c6fda1350c71e6d256e9
64 64 % status
65 65 ? hooktest
66 66 % identify
67 67 ef63ca68695b
68 68 % cat
69 69 expand $Id: a,v ef63ca68695b 1970/01/01 00:00:00 user $
70 70 do not process $Id:
71 71 xxx $
72 72 ignore $Id$
73 73 % hg cat
74 74 expand $Id: a,v ef63ca68695b 1970/01/01 00:00:00 user $
75 75 do not process $Id:
76 76 xxx $
77 77 ignore $Id$
78 78 a
79 79 % diff a hooktest
80 80 % removing commit hook from config
81 81 % bundle
82 82 2 changesets found
83 83 % notify on pull to check whether keywords stay as is in email
84 84 % ie. if patch.diff wrapper acts as it should
85 85 % pull from bundle
86 86 pulling from ../kw.hg
87 87 requesting all changes
88 88 adding changesets
89 89 adding manifests
90 90 adding file changes
91 91 added 2 changesets with 3 changes to 3 files
92 92
93 93 diff -r 000000000000 -r a2392c293916 sym
94 94 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
95 95 +++ b/sym Sat Feb 09 20:25:47 2008 +0100
96 96 @@ -0,0 +1,1 @@
97 97 +a
98 98 \ No newline at end of file
99 99
100 100 diff -r a2392c293916 -r ef63ca68695b a
101 101 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
102 102 +++ b/a Thu Jan 01 00:00:00 1970 +0000
103 103 @@ -0,0 +1,3 @@
104 104 +expand $Id$
105 105 +do not process $Id:
106 106 +xxx $
107 107 diff -r a2392c293916 -r ef63ca68695b b
108 108 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
109 109 +++ b/b Thu Jan 01 00:00:00 1970 +0000
110 110 @@ -0,0 +1,1 @@
111 111 +ignore $Id$
112 112 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
113 113 % remove notify config
114 114 % touch
115 115 % status
116 116 % update
117 117 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
118 118 % cat
119 119 expand $Id: a,v ef63ca68695b 1970/01/01 00:00:00 user $
120 120 do not process $Id:
121 121 xxx $
122 122 ignore $Id$
123 123 % check whether expansion is filewise
124 124 % commit c
125 125 adding c
126 126 % force expansion
127 127 overwriting a expanding keywords
128 128 overwriting c expanding keywords
129 129 % compare changenodes in a c
130 130 expand $Id: a,v ef63ca68695b 1970/01/01 00:00:00 user $
131 131 do not process $Id:
132 132 xxx $
133 133 $Id: c,v 40a904bbbe4c 1970/01/01 00:00:01 user $
134 134 tests for different changenodes
135 135 % record chunk
136 136 diff --git a/a b/a
137 137 2 hunks, 2 lines changed
138 138 examine changes to 'a'? [Ynsfdaq?]
139 139 @@ -1,3 +1,4 @@
140 140 expand $Id$
141 141 +foo
142 142 do not process $Id:
143 143 xxx $
144 144 record change 1/2 to 'a'? [Ynsfdaq?]
145 145 @@ -2,2 +3,3 @@
146 146 do not process $Id:
147 147 xxx $
148 148 +bar
149 149 record change 2/2 to 'a'? [Ynsfdaq?]
150 150
151 151 d17e03c92c97+ tip
152 152 M a
153 153 % cat modified file
154 154 expand $Id: a,v d17e03c92c97 1970/01/01 00:00:01 test $
155 155 foo
156 156 do not process $Id:
157 157 xxx $
158 158 bar
159 159 diff -r d17e03c92c97 a
160 160 --- a/a Wed Dec 31 23:59:51 1969 -0000
161 161 @@ -2,3 +2,4 @@
162 162 foo
163 163 do not process $Id:
164 164 xxx $
165 165 +bar
166 166 rolling back to revision 2 (undo commit)
167 167 % record file
168 168 diff --git a/a b/a
169 169 2 hunks, 2 lines changed
170 170 examine changes to 'a'? [Ynsfdaq?]
171 171 @@ -1,3 +1,4 @@
172 172 expand $Id$
173 173 +foo
174 174 do not process $Id:
175 175 xxx $
176 176 record change 1/2 to 'a'? [Ynsfdaq?]
177 177 @@ -2,2 +3,3 @@
178 178 do not process $Id:
179 179 xxx $
180 180 +bar
181 181 record change 2/2 to 'a'? [Ynsfdaq?]
182 182 % a should be clean
183 183 C a
184 184 rolling back to revision 2 (undo commit)
185 185 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
186 186 % init --mq
187 187 % qimport
188 188 % commit --mq
189 189 % keywords should not be expanded in patch
190 190 # HG changeset patch
191 191 # User User Name <user@example.com>
192 192 # Date 1 0
193 193 # Node ID 40a904bbbe4cd4ab0a1f28411e35db26341a40ad
194 194 # Parent ef63ca68695bc9495032c6fda1350c71e6d256e9
195 195 cndiff
196 196
197 197 diff -r ef63ca68695b -r 40a904bbbe4c c
198 198 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
199 199 +++ b/c Thu Jan 01 00:00:01 1970 +0000
200 200 @@ -0,0 +1,2 @@
201 201 +$Id$
202 202 +tests for different changenodes
203 203 % qpop
204 204 popping mqtest.diff
205 205 patch queue now empty
206 206 % qgoto - should imply qpush
207 207 applying mqtest.diff
208 208 now at: mqtest.diff
209 209 % cat
210 210 $Id: c,v 40a904bbbe4c 1970/01/01 00:00:01 user $
211 211 tests for different changenodes
212 212 % hg cat
213 213 $Id: c,v 40a904bbbe4c 1970/01/01 00:00:01 user $
214 214 tests for different changenodes
215 215 % keyword should not be expanded in filelog
216 216 $Id$
217 217 tests for different changenodes
218 218 % qpop and move on
219 219 popping mqtest.diff
220 220 patch queue now empty
221 221 % copy
222 222 % kwfiles added
223 223 a
224 224 c
225 225 % commit
226 226 c
227 227 c: copy a:0045e12f6c5791aac80ca6cbfd97709a88307292
228 228 overwriting c expanding keywords
229 229 committed changeset 2:25736cf2f5cbe41f6be4e6784ef6ecf9f3bbcc7d
230 230 % cat a c
231 231 expand $Id: a,v ef63ca68695b 1970/01/01 00:00:00 user $
232 232 do not process $Id:
233 233 xxx $
234 234 expand $Id: c,v 25736cf2f5cb 1970/01/01 00:00:01 user $
235 235 do not process $Id:
236 236 xxx $
237 237 % touch copied c
238 238 % status
239 239 % kwfiles
240 240 a
241 241 c
242 242 % ignored files
243 243 I b
244 244 I sym
245 245 % all files
246 246 K a
247 247 K c
248 248 I b
249 249 I sym
250 250 % diff --rev
251 251 diff -r ef63ca68695b c
252 252 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
253 253 @@ -0,0 +1,3 @@
254 254 +expand $Id$
255 255 +do not process $Id:
256 256 +xxx $
257 257 % rollback
258 258 rolling back to revision 1 (undo commit)
259 259 % status
260 260 A c
261 261 % update -C
262 262 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
263 263 % custom keyword expansion
264 264 % try with kwdemo
265 265 [extensions]
266 266 keyword =
267 267 [keyword]
268 268 ** =
269 269 b = ignore
270 270 demo.txt =
271 271 [keywordmaps]
272 272 Xinfo = {author}: {desc}
273 273 $Xinfo: test: hg keyword configuration and expansion example $
274 274 % cat
275 275 expand $Id: a,v ef63ca68695b 1970/01/01 00:00:00 user $
276 276 do not process $Id:
277 277 xxx $
278 278 ignore $Id$
279 279 % hg cat
280 280 expand $Id: a ef63ca68695b Thu, 01 Jan 1970 00:00:00 +0000 user $
281 281 do not process $Id:
282 282 xxx $
283 283 ignore $Id$
284 284 a
285 285 % interrupted commit should not change state
286 286 abort: empty commit message
287 287 % status
288 288 M a
289 289 ? c
290 290 ? log
291 291 % commit
292 292 a
293 293 overwriting a expanding keywords
294 294 committed changeset 2:bb948857c743469b22bbf51f7ec8112279ca5d83
295 295 % status
296 296 ? c
297 297 % verify
298 298 checking changesets
299 299 checking manifests
300 300 crosschecking files in changesets and manifests
301 301 checking files
302 302 3 files, 3 changesets, 4 total revisions
303 303 % cat
304 304 expand $Id: a bb948857c743 Thu, 01 Jan 1970 00:00:02 +0000 user $
305 305 do not process $Id:
306 306 xxx $
307 307 $Xinfo: User Name <user@example.com>: firstline $
308 308 ignore $Id$
309 309 % hg cat
310 310 expand $Id: a bb948857c743 Thu, 01 Jan 1970 00:00:02 +0000 user $
311 311 do not process $Id:
312 312 xxx $
313 313 $Xinfo: User Name <user@example.com>: firstline $
314 314 ignore $Id$
315 315 a
316 316 % annotate
317 317 1: expand $Id$
318 318 1: do not process $Id:
319 319 1: xxx $
320 320 2: $Xinfo$
321 321 % remove
322 322 committed changeset 3:d14c712653769de926994cf7fbb06c8fbd68f012
323 323 % status
324 324 ? c
325 325 % rollback
326 326 rolling back to revision 2 (undo commit)
327 327 % status
328 328 R a
329 329 ? c
330 330 % revert a
331 331 % cat a
332 332 expand $Id: a bb948857c743 Thu, 01 Jan 1970 00:00:02 +0000 user $
333 333 do not process $Id:
334 334 xxx $
335 335 $Xinfo: User Name <user@example.com>: firstline $
336 % clone
337 % expansion in dest
338 expand $Id: a bb948857c743 Thu, 01 Jan 1970 00:00:02 +0000 user $
339 do not process $Id:
340 xxx $
341 $Xinfo: User Name <user@example.com>: firstline $
342 % no expansion in dest
343 expand $Id$
344 do not process $Id:
345 xxx $
346 $Xinfo$
336 347 % clone to test incoming
337 348 requesting all changes
338 349 adding changesets
339 350 adding manifests
340 351 adding file changes
341 352 added 2 changesets with 3 changes to 3 files
342 353 updating to branch default
343 354 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
344 355 % incoming
345 356 comparing with test-keyword/Test
346 357 searching for changes
347 358 changeset: 2:bb948857c743
348 359 tag: tip
349 360 user: User Name <user@example.com>
350 361 date: Thu Jan 01 00:00:02 1970 +0000
351 362 summary: firstline
352 363
353 364 % commit rejecttest
354 365 a
355 366 overwriting a expanding keywords
356 367 committed changeset 2:85e279d709ffc28c9fdd1b868570985fc3d87082
357 368 % export
358 369 % import
359 370 applying ../rejecttest.diff
360 371 % cat
361 372 expand $Id: a 4e0994474d25 Thu, 01 Jan 1970 00:00:03 +0000 user $ rejecttest
362 373 do not process $Id: rejecttest
363 374 xxx $
364 375 $Xinfo: User Name <user@example.com>: rejects? $
365 376 ignore $Id$
366 377
367 378 % rollback
368 379 rolling back to revision 2 (undo commit)
369 380 % clean update
370 381 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
371 382 % kwexpand/kwshrink on selected files
372 383 % copy a x/a
373 384 % kwexpand a
374 385 overwriting a expanding keywords
375 386 % kwexpand x/a should abort
376 387 abort: outstanding uncommitted changes
377 388 x/a
378 389 x/a: copy a:779c764182ce5d43e2b1eb66ce06d7b47bfe342e
379 390 overwriting x/a expanding keywords
380 391 committed changeset 3:b4560182a3f9a358179fd2d835c15e9da379c1e4
381 392 % cat a
382 393 expand $Id: x/a b4560182a3f9 Thu, 01 Jan 1970 00:00:03 +0000 user $
383 394 do not process $Id:
384 395 xxx $
385 396 $Xinfo: User Name <user@example.com>: xa $
386 397 % kwshrink a inside directory x
387 398 overwriting x/a shrinking keywords
388 399 % cat a
389 400 expand $Id$
390 401 do not process $Id:
391 402 xxx $
392 403 $Xinfo$
393 404 % kwexpand nonexistent
394 405 nonexistent:
395 406 % hg serve
396 407 % expansion
397 408 % hgweb file
398 409 200 Script output follows
399 410
400 411 expand $Id: a bb948857c743 Thu, 01 Jan 1970 00:00:02 +0000 user $
401 412 do not process $Id:
402 413 xxx $
403 414 $Xinfo: User Name <user@example.com>: firstline $
404 415 % no expansion
405 416 % hgweb annotate
406 417 200 Script output follows
407 418
408 419
409 420 user@1: expand $Id$
410 421 user@1: do not process $Id:
411 422 user@1: xxx $
412 423 user@2: $Xinfo$
413 424
414 425
415 426
416 427
417 428 % hgweb changeset
418 429 200 Script output follows
419 430
420 431
421 432 # HG changeset patch
422 433 # User User Name <user@example.com>
423 434 # Date 3 0
424 435 # Node ID b4560182a3f9a358179fd2d835c15e9da379c1e4
425 436 # Parent bb948857c743469b22bbf51f7ec8112279ca5d83
426 437 xa
427 438
428 439 diff -r bb948857c743 -r b4560182a3f9 x/a
429 440 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
430 441 +++ b/x/a Thu Jan 01 00:00:03 1970 +0000
431 442 @@ -0,0 +1,4 @@
432 443 +expand $Id$
433 444 +do not process $Id:
434 445 +xxx $
435 446 +$Xinfo$
436 447
437 448 % hgweb filediff
438 449 200 Script output follows
439 450
440 451
441 452 diff -r ef63ca68695b -r bb948857c743 a
442 453 --- a/a Thu Jan 01 00:00:00 1970 +0000
443 454 +++ b/a Thu Jan 01 00:00:02 1970 +0000
444 455 @@ -1,3 +1,4 @@
445 456 expand $Id$
446 457 do not process $Id:
447 458 xxx $
448 459 +$Xinfo$
449 460
450 461
451 462
452 463
453 464 % errors encountered
454 465 % merge/resolve
455 466 % simplemerge
456 467 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
457 468 created new head
458 469 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
459 470 (branch merge, don't forget to commit)
460 471 $Id: m 27d48ee14f67 Thu, 01 Jan 1970 00:00:00 +0000 test $
461 472 foo
462 473 % conflict
463 474 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
464 475 created new head
465 476 merging m
466 477 warning: conflicts during merge.
467 478 merging m failed!
468 479 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
469 480 use 'hg resolve' to retry unresolved file merges or 'hg update -C' to abandon
470 481 % keyword stays outside conflict zone
471 482 $Id$
472 483 <<<<<<< local
473 484 bar
474 485 =======
475 486 foo
476 487 >>>>>>> other
477 488 % resolve to local
478 489 $Id: m 41efa6d38e9b Thu, 01 Jan 1970 00:00:00 +0000 test $
479 490 bar
480 491 % test restricted mode with transplant -b
481 492 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
482 493 marked working directory as branch foo
483 494 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
484 495 applying 4aa30d025d50
485 496 4aa30d025d50 transplanted to 5a4da427c162
486 497 % no expansion in changeset
487 498 changeset: 11:5a4da427c162
488 499 tag: tip
489 500 parent: 9:41efa6d38e9b
490 501 user: test
491 502 date: Thu Jan 01 00:00:00 1970 +0000
492 503 summary: 9foobranch
493 504
494 505 diff -r 41efa6d38e9b -r 5a4da427c162 a
495 506 --- a/a Thu Jan 01 00:00:00 1970 +0000
496 507 +++ b/a Thu Jan 01 00:00:00 1970 +0000
497 508 @@ -1,3 +1,4 @@
498 509 +foobranch
499 510 expand $Id$
500 511 do not process $Id:
501 512 xxx $
502 513
503 514 % expansion in file
504 515 foobranch
505 516 expand $Id: a 5a4da427c162 Thu, 01 Jan 1970 00:00:00 +0000 test $
506 517 % switch off expansion
507 518 % kwshrink with unknown file u
508 519 overwriting a shrinking keywords
509 520 overwriting m shrinking keywords
510 521 overwriting x/a shrinking keywords
511 522 % cat
512 523 expand $Id$
513 524 do not process $Id:
514 525 xxx $
515 526 $Xinfo$
516 527 ignore $Id$
517 528 % hg cat
518 529 expand $Id: a bb948857c743 Thu, 01 Jan 1970 00:00:02 +0000 user $
519 530 do not process $Id:
520 531 xxx $
521 532 $Xinfo: User Name <user@example.com>: firstline $
522 533 ignore $Id$
523 534 a
524 535 % cat
525 536 expand $Id$
526 537 do not process $Id:
527 538 xxx $
528 539 $Xinfo$
529 540 ignore $Id$
530 541 % hg cat
531 542 expand $Id$
532 543 do not process $Id:
533 544 xxx $
534 545 $Xinfo$
535 546 ignore $Id$
536 547 a
@@ -1,96 +1,96 b''
1 1 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
2 2 created new head
3 3 merging bar and foo to bar
4 4 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
5 5 (branch merge, don't forget to commit)
6 6 % contents of bar should be line0 line1 line2
7 7 line0
8 8 line1
9 9 line2
10 10 rev offset length base linkrev nodeid p1 p2
11 11 0 0 77 0 2 d35118874825 000000000000 000000000000
12 12 1 77 76 0 3 5345f5ab8abd 000000000000 d35118874825
13 13 bar renamed from foo:9e25c27b87571a1edee5ae4dddee5687746cc8e2
14 14 rev offset length base linkrev nodeid p1 p2
15 15 0 0 7 0 0 690b295714ae 000000000000 000000000000
16 16 1 7 13 1 1 9e25c27b8757 690b295714ae 000000000000
17 17 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
18 18 created new head
19 19 4:2263c1be0967 2:0f2ff26688b9
20 20 3:0555950ead28 2:0f2ff26688b9 1:5cd961e4045d
21 21 2:0f2ff26688b9 0:2665aaee66e9
22 22 1:5cd961e4045d
23 23 0:2665aaee66e9
24 24 % this should use bar@rev2 as the ancestor
25 25 searching for copies back to rev 1
26 26 resolving manifests
27 27 overwrite None partial False
28 28 ancestor 0f2ff26688b9 local 2263c1be0967+ remote 0555950ead28
29 29 bar: versions differ -> m
30 30 preserving bar for resolve of bar
31 update: bar 1/1 files (100.00%)
31 updating: bar 1/1 files (100.00%)
32 32 picked tool 'internal:merge' for bar (binary False symlink False)
33 33 merging bar
34 34 my bar@2263c1be0967+ other bar@0555950ead28 ancestor bar@0f2ff26688b9
35 35 premerge successful
36 36 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
37 37 (branch merge, don't forget to commit)
38 38 % contents of bar should be line1 line2
39 39 line1
40 40 line2
41 41 rev offset length base linkrev nodeid p1 p2
42 42 0 0 77 0 2 d35118874825 000000000000 000000000000
43 43 1 77 76 0 3 5345f5ab8abd 000000000000 d35118874825
44 44 2 153 7 2 4 ff4b45017382 d35118874825 000000000000
45 45 3 160 13 3 5 3701b4893544 ff4b45017382 5345f5ab8abd
46 46
47 47
48 48 requesting all changes
49 49 adding changesets
50 50 adding manifests
51 51 adding file changes
52 52 added 3 changesets with 3 changes to 2 files (+1 heads)
53 53 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
54 54 merging foo and bar to bar
55 55 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
56 56 (branch merge, don't forget to commit)
57 57 % contents of bar should be line0 line1 line2
58 58 line0
59 59 line1
60 60 line2
61 61 rev offset length base linkrev nodeid p1 p2
62 62 0 0 77 0 2 d35118874825 000000000000 000000000000
63 63 1 77 76 0 3 5345f5ab8abd 000000000000 d35118874825
64 64 bar renamed from foo:9e25c27b87571a1edee5ae4dddee5687746cc8e2
65 65 rev offset length base linkrev nodeid p1 p2
66 66 0 0 7 0 0 690b295714ae 000000000000 000000000000
67 67 1 7 13 1 1 9e25c27b8757 690b295714ae 000000000000
68 68 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
69 69 created new head
70 70 4:2263c1be0967 2:0f2ff26688b9
71 71 3:3ffa6b9e35f0 1:5cd961e4045d 2:0f2ff26688b9
72 72 2:0f2ff26688b9 0:2665aaee66e9
73 73 1:5cd961e4045d
74 74 0:2665aaee66e9
75 75 % this should use bar@rev2 as the ancestor
76 76 searching for copies back to rev 1
77 77 resolving manifests
78 78 overwrite None partial False
79 79 ancestor 0f2ff26688b9 local 2263c1be0967+ remote 3ffa6b9e35f0
80 80 bar: versions differ -> m
81 81 preserving bar for resolve of bar
82 update: bar 1/1 files (100.00%)
82 updating: bar 1/1 files (100.00%)
83 83 picked tool 'internal:merge' for bar (binary False symlink False)
84 84 merging bar
85 85 my bar@2263c1be0967+ other bar@3ffa6b9e35f0 ancestor bar@0f2ff26688b9
86 86 premerge successful
87 87 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
88 88 (branch merge, don't forget to commit)
89 89 % contents of bar should be line1 line2
90 90 line1
91 91 line2
92 92 rev offset length base linkrev nodeid p1 p2
93 93 0 0 77 0 2 d35118874825 000000000000 000000000000
94 94 1 77 76 0 3 5345f5ab8abd 000000000000 d35118874825
95 95 2 153 7 2 4 ff4b45017382 d35118874825 000000000000
96 96 3 160 13 3 5 3701b4893544 ff4b45017382 5345f5ab8abd
@@ -1,29 +1,29 b''
1 1 adding a
2 2 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
3 3 created new head
4 4 searching for copies back to rev 1
5 5 resolving manifests
6 6 overwrite None partial False
7 7 ancestor c334dc3be0da local 521a1e40188f+ remote 3574f3e69b1c
8 8 conflicting flags for a
9 9 (n)one, e(x)ec or sym(l)ink? n
10 10 a: update permissions -> e
11 update: a 1/1 files (100.00%)
11 updating: a 1/1 files (100.00%)
12 12 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
13 13 (branch merge, don't forget to commit)
14 14 % symlink is local parent, executable is other
15 15 a has no flags (default for conflicts)
16 16 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
17 17 searching for copies back to rev 1
18 18 resolving manifests
19 19 overwrite None partial False
20 20 ancestor c334dc3be0da local 3574f3e69b1c+ remote 521a1e40188f
21 21 conflicting flags for a
22 22 (n)one, e(x)ec or sym(l)ink? n
23 23 a: remote is newer -> g
24 update: a 1/1 files (100.00%)
24 updating: a 1/1 files (100.00%)
25 25 getting a
26 26 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
27 27 (branch merge, don't forget to commit)
28 28 % symlink is other parent, executable is local
29 29 a has no flags (default for conflicts)
@@ -1,78 +1,78 b''
1 1 updating to branch default
2 2 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
3 3 pulling from ../test-a
4 4 searching for changes
5 5 adding changesets
6 6 adding manifests
7 7 adding file changes
8 8 added 1 changesets with 1 changes to 1 files (+1 heads)
9 9 (run 'hg heads' to see heads, 'hg merge' to merge)
10 10 merging test.txt
11 11 warning: conflicts during merge.
12 12 merging test.txt failed!
13 13 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
14 14 use 'hg resolve' to retry unresolved file merges or 'hg update -C' to abandon
15 15 pulling from ../test-a
16 16 searching for changes
17 17 adding changesets
18 18 adding manifests
19 19 adding file changes
20 20 added 1 changesets with 1 changes to 1 files (+1 heads)
21 21 (run 'hg heads' to see heads, 'hg merge' to merge)
22 22 searching for copies back to rev 1
23 23 resolving manifests
24 24 overwrite None partial False
25 25 ancestor faaea63e63a9 local 451c744aabcc+ remote a070d41e8360
26 26 test.txt: versions differ -> m
27 27 preserving test.txt for resolve of test.txt
28 update: test.txt 1/1 files (100.00%)
28 updating: test.txt 1/1 files (100.00%)
29 29 picked tool 'internal:merge' for test.txt (binary False symlink False)
30 30 merging test.txt
31 31 my test.txt@451c744aabcc+ other test.txt@a070d41e8360 ancestor test.txt@faaea63e63a9
32 32 warning: conflicts during merge.
33 33 merging test.txt failed!
34 34 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
35 35 use 'hg resolve' to retry unresolved file merges or 'hg update -C' to abandon
36 36 one
37 37 <<<<<<< local
38 38 two-point-five
39 39 =======
40 40 two-point-one
41 41 >>>>>>> other
42 42 three
43 43 rev offset length base linkrev nodeid p1 p2
44 44 0 0 7 0 0 01365c4cca56 000000000000 000000000000
45 45 1 7 9 1 1 7b013192566a 01365c4cca56 000000000000
46 46 2 16 15 2 2 8fe46a3eb557 01365c4cca56 000000000000
47 47 3 31 27 2 3 fc3148072371 7b013192566a 8fe46a3eb557
48 48 4 58 25 4 4 d40249267ae3 8fe46a3eb557 000000000000
49 49 changeset: 4:a070d41e8360
50 50 tag: tip
51 51 parent: 2:faaea63e63a9
52 52 user: test
53 53 date: Mon Jan 12 13:46:40 1970 +0000
54 54 summary: two -> two-point-one
55 55
56 56 changeset: 3:451c744aabcc
57 57 parent: 1:e409be6afcc0
58 58 parent: 2:faaea63e63a9
59 59 user: test
60 60 date: Mon Jan 12 13:46:40 1970 +0000
61 61 summary: Merge 1
62 62
63 63 changeset: 2:faaea63e63a9
64 64 parent: 0:095c92b91f1a
65 65 user: test
66 66 date: Mon Jan 12 13:46:40 1970 +0000
67 67 summary: Numbers as words
68 68
69 69 changeset: 1:e409be6afcc0
70 70 user: test
71 71 date: Mon Jan 12 13:46:40 1970 +0000
72 72 summary: 2 -> 2.5
73 73
74 74 changeset: 0:095c92b91f1a
75 75 user: test
76 76 date: Mon Jan 12 13:46:40 1970 +0000
77 77 summary: Initial
78 78
@@ -1,625 +1,642 b''
1 1 #!/bin/sh
2 2
3 3 . $TESTDIR/helpers.sh
4 4
5 5 checkundo()
6 6 {
7 7 if [ -f .hg/store/undo ]; then
8 8 echo ".hg/store/undo still exists after $1"
9 9 fi
10 10 }
11 11
12 12 echo "[extensions]" >> $HGRCPATH
13 13 echo "mq=" >> $HGRCPATH
14 14
15 15 echo "[mq]" >> $HGRCPATH
16 16 echo "plain=true" >> $HGRCPATH
17 17
18 18 echo % help
19 19 hg help mq
20 20
21 21 hg init a
22 22 cd a
23 23 echo a > a
24 24 hg ci -Ama
25 25
26 26 hg clone . ../k
27 27
28 28 mkdir b
29 29 echo z > b/z
30 30 hg ci -Ama
31 31
32 32 echo % qinit
33 33
34 34 hg qinit
35 35
36 36 cd ..
37 37 hg init b
38 38
39 39 echo % -R qinit
40 40
41 41 hg -R b qinit
42 42
43 43 hg init c
44 44
45 45 echo % qinit -c
46 46
47 47 hg --cwd c qinit -c
48 48 hg -R c/.hg/patches st
49 49
50 50 echo '% qinit; qinit -c'
51 51 hg init d
52 52 cd d
53 53 hg qinit
54 54 hg qinit -c
55 55 # qinit -c should create both files if they don't exist
56 56 echo ' .hgignore:'
57 57 cat .hg/patches/.hgignore
58 58 echo ' series:'
59 59 cat .hg/patches/series
60 60 hg qinit -c 2>&1 | sed -e 's/repository.*already/repository already/'
61 61 cd ..
62 62
63 63 echo '% qinit; <stuff>; qinit -c'
64 64 hg init e
65 65 cd e
66 66 hg qnew A
67 67 checkundo qnew
68 68 echo foo > foo
69 69 hg add foo
70 70 hg qrefresh
71 71 hg qnew B
72 72 echo >> foo
73 73 hg qrefresh
74 74 echo status >> .hg/patches/.hgignore
75 75 echo bleh >> .hg/patches/.hgignore
76 76 hg qinit -c
77 77 hg -R .hg/patches status
78 78 # qinit -c shouldn't touch these files if they already exist
79 79 echo ' .hgignore:'
80 80 cat .hg/patches/.hgignore
81 81 echo ' series:'
82 82 cat .hg/patches/series
83 83
84 84 echo '% status --mq with color (issue2096)'
85 85 hg status --mq --config extensions.color= --color=always
86 86 cd ..
87 87
88 88 echo '% init --mq without repo'
89 89 mkdir f
90 90 cd f
91 91 hg init --mq
92 92 cd ..
93 93
94 94 echo '% init --mq with repo path'
95 95 hg init g
96 96 hg init --mq g
97 97 test -d g/.hg/patches/.hg && echo "ok" || echo "failed"
98 98
99 99 echo '% init --mq with nonexistent directory'
100 100 hg init --mq nonexistentdir
101 101
102 102 echo '% init --mq with bundle (non "local")'
103 103 hg -R a bundle --all a.bundle >/dev/null
104 104 hg init --mq a.bundle
105 105
106 106 cd a
107 107
108 108 hg qnew -m 'foo bar' test.patch
109 109
110 echo '# comment' > .hg/patches/series.tmp
111 echo >> .hg/patches/series.tmp # empty line
112 cat .hg/patches/series >> .hg/patches/series.tmp
113 mv .hg/patches/series.tmp .hg/patches/series
114
110 115 echo % qrefresh
111 116
112 117 echo a >> a
113 118 hg qrefresh
114 119 sed -e "s/^\(diff -r \)\([a-f0-9]* \)/\1 x/" \
115 120 -e "s/\(+++ [a-zA-Z0-9_/.-]*\).*/\1/" \
116 121 -e "s/\(--- [a-zA-Z0-9_/.-]*\).*/\1/" .hg/patches/test.patch
117 122
118 123 echo % empty qrefresh
119 124
120 125 hg qrefresh -X a
121 126 echo 'revision:'
122 127 hg diff -r -2 -r -1
123 128 echo 'patch:'
124 129 cat .hg/patches/test.patch
125 130 echo 'working dir diff:'
126 131 hg diff --nodates -q
127 132 # restore things
128 133 hg qrefresh
129 134 checkundo qrefresh
130 135
131 136 echo % qpop
132 137
133 138 hg qpop
134 139 checkundo qpop
135 140
136 141 echo % qpush with dump of tag cache
137 142
138 143 # Dump the tag cache to ensure that it has exactly one head after qpush.
139 144 rm -f .hg/tags.cache
140 145 hg tags > /dev/null
141 146 echo ".hg/tags.cache (pre qpush):"
142 147 sed 's/ [0-9a-f]*//' .hg/tags.cache
143 148 hg qpush
144 149 hg tags > /dev/null
145 150 echo ".hg/tags.cache (post qpush):"
146 151 sed 's/ [0-9a-f]*//' .hg/tags.cache
147 152
148 153 checkundo qpush
149 154
150 155 cd ..
151 156
152 157 echo % pop/push outside repo
153 158
154 159 hg -R a qpop
155 160 hg -R a qpush
156 161
157 162 cd a
158 163 hg qnew test2.patch
159 164
160 165 echo % qrefresh in subdir
161 166
162 167 cd b
163 168 echo a > a
164 169 hg add a
165 170 hg qrefresh
166 171
167 172 echo % pop/push -a in subdir
168 173
169 174 hg qpop -a
170 175 hg --traceback qpush -a
171 176
172 177 # setting columns & formatted tests truncating (issue1912)
173 178 echo % qseries
174 179 COLUMNS=4 hg qseries --config ui.formatted=true
175 180 COLUMNS=20 hg qseries --config ui.formatted=true -vs
176 181 hg qpop
177 182 hg qseries -vs
178 183 hg sum | grep mq
179 184 hg qpush
180 185 hg sum | grep mq
181 186
182 187 echo % qapplied
183 188 hg qapplied
184 189
185 190 echo % qtop
186 191 hg qtop
187 192
188 193 echo % prev
189 194 hg qapp -1
190 195
191 196 echo % next
192 197 hg qunapp -1
193 198
194 199 hg qpop
195 200 echo % commit should fail
196 201 hg commit
197 202
198 203 echo % push should fail
199 204 hg push ../../k
200 205
201 206 echo % import should fail
202 207 hg st .
203 208 echo foo >> ../a
204 209 hg diff > ../../import.diff
205 210 hg revert --no-backup ../a
206 211 hg import ../../import.diff
207 212 hg st
208 213 echo % import --no-commit should succeed
209 214 hg import --no-commit ../../import.diff
210 215 hg st
211 216 hg revert --no-backup ../a
212 217
213 218 echo % qunapplied
214 219 hg qunapplied
215 220
216 221 echo % qpush/qpop with index
217 222 hg qnew test1b.patch
218 223 echo 1b > 1b
219 224 hg add 1b
220 225 hg qrefresh
221 226 hg qpush 2
222 227 hg qpop 0
223 228 hg qpush test.patch+1
224 229 hg qpush test.patch+2
225 230 hg qpop test2.patch-1
226 231 hg qpop test2.patch-2
227 232 hg qpush test1b.patch+1
228 233
229 234 echo % qpush --move
230 235 hg qpop -a
236 hg qguard test1b.patch -- -negguard
237 hg qguard test2.patch -- +posguard
238 hg qpush --move test2.patch # can't move guarded patch
239 hg qselect posguard
231 240 hg qpush --move test2.patch # move to front
232 hg qpush --move test1b.patch
241 hg qpush --move test1b.patch # negative guard unselected
233 242 hg qpush --move test.patch # noop move
234 243 hg qseries -v
235 244 hg qpop -a
236 hg qpush --move test.patch # cleaning up
245 # cleaning up
246 hg qselect --none
247 hg qguard --none test1b.patch
248 hg qguard --none test2.patch
249 hg qpush --move test.patch
237 250 hg qpush --move test1b.patch
238 251 hg qpush --move bogus # nonexistent patch
252 hg qpush --move # no patch
239 253 hg qpush --move test.patch # already applied
240 254 hg qpush
241 255
256 echo % series after move
257 cat `hg root`/.hg/patches/series
258
242 259 echo % pop, qapplied, qunapplied
243 260 hg qseries -v
244 261 echo % qapplied -1 test.patch
245 262 hg qapplied -1 test.patch
246 263 echo % qapplied -1 test1b.patch
247 264 hg qapplied -1 test1b.patch
248 265 echo % qapplied -1 test2.patch
249 266 hg qapplied -1 test2.patch
250 267 echo % qapplied -1
251 268 hg qapplied -1
252 269 echo % qapplied
253 270 hg qapplied
254 271 echo % qapplied test1b.patch
255 272 hg qapplied test1b.patch
256 273 echo % qunapplied -1
257 274 hg qunapplied -1
258 275 echo % qunapplied
259 276 hg qunapplied
260 277 echo % popping
261 278 hg qpop
262 279 echo % qunapplied -1
263 280 hg qunapplied -1
264 281 echo % qunapplied
265 282 hg qunapplied
266 283 echo % qunapplied test2.patch
267 284 hg qunapplied test2.patch
268 285 echo % qunapplied -1 test2.patch
269 286 hg qunapplied -1 test2.patch
270 287 echo % popping -a
271 288 hg qpop -a
272 289 echo % qapplied
273 290 hg qapplied
274 291 echo % qapplied -1
275 292 hg qapplied -1
276 293 hg qpush
277 294
278 295 echo % push should succeed
279 296 hg qpop -a
280 297 hg push ../../k
281 298
282 299 echo % qpush/qpop error codes
283 300 errorcode()
284 301 {
285 302 hg "$@" && echo " $@ succeeds" || echo " $@ fails"
286 303 }
287 304
288 305 # we want to start with some patches applied
289 306 hg qpush -a
290 307 echo " % pops all patches and succeeds"
291 308 errorcode qpop -a
292 309 echo " % does nothing and succeeds"
293 310 errorcode qpop -a
294 311 echo " % fails - nothing else to pop"
295 312 errorcode qpop
296 313 echo " % pushes a patch and succeeds"
297 314 errorcode qpush
298 315 echo " % pops a patch and succeeds"
299 316 errorcode qpop
300 317 echo " % pushes up to test1b.patch and succeeds"
301 318 errorcode qpush test1b.patch
302 319 echo " % does nothing and succeeds"
303 320 errorcode qpush test1b.patch
304 321 echo " % does nothing and succeeds"
305 322 errorcode qpop test1b.patch
306 323 echo " % fails - can't push to this patch"
307 324 errorcode qpush test.patch
308 325 echo " % fails - can't pop to this patch"
309 326 errorcode qpop test2.patch
310 327 echo " % pops up to test.patch and succeeds"
311 328 errorcode qpop test.patch
312 329 echo " % pushes all patches and succeeds"
313 330 errorcode qpush -a
314 331 echo " % does nothing and succeeds"
315 332 errorcode qpush -a
316 333 echo " % fails - nothing else to push"
317 334 errorcode qpush
318 335 echo " % does nothing and succeeds"
319 336 errorcode qpush test2.patch
320 337
321 338
322 339 echo % strip
323 340 cd ../../b
324 341 echo x>x
325 342 hg ci -Ama
326 343 hg strip tip | hidebackup
327 344 hg unbundle .hg/strip-backup/*
328 345
329 346 echo % strip with local changes, should complain
330 347 hg up
331 348 echo y>y
332 349 hg add y
333 350 hg strip tip | hidebackup
334 351 echo % --force strip with local changes
335 352 hg strip -f tip | hidebackup
336 353
337 354 echo '% cd b; hg qrefresh'
338 355 hg init refresh
339 356 cd refresh
340 357 echo a > a
341 358 hg ci -Ama
342 359 hg qnew -mfoo foo
343 360 echo a >> a
344 361 hg qrefresh
345 362 mkdir b
346 363 cd b
347 364 echo f > f
348 365 hg add f
349 366 hg qrefresh
350 367 sed -e "s/\(+++ [a-zA-Z0-9_/.-]*\).*/\1/" \
351 368 -e "s/\(--- [a-zA-Z0-9_/.-]*\).*/\1/" ../.hg/patches/foo
352 369 echo % hg qrefresh .
353 370 hg qrefresh .
354 371 sed -e "s/\(+++ [a-zA-Z0-9_/.-]*\).*/\1/" \
355 372 -e "s/\(--- [a-zA-Z0-9_/.-]*\).*/\1/" ../.hg/patches/foo
356 373 hg status
357 374
358 375 echo % qpush failure
359 376 cd ..
360 377 hg qrefresh
361 378 hg qnew -mbar bar
362 379 echo foo > foo
363 380 echo bar > bar
364 381 hg add foo bar
365 382 hg qrefresh
366 383 hg qpop -a
367 384 echo bar > foo
368 385 hg qpush -a
369 386 hg st
370 387
371 388 echo % mq tags
372 389 hg log --template '{rev} {tags}\n' -r qparent:qtip
373 390
374 391 echo % bad node in status
375 392 hg qpop
376 393 hg strip -qn tip
377 394 hg tip 2>&1 | sed -e 's/unknown node .*/unknown node/'
378 395 hg branches 2>&1 | sed -e 's/unknown node .*/unknown node/'
379 396 hg qpop 2>&1 | sed -e 's/unknown node .*/unknown node/'
380 397
381 398 cat >>$HGRCPATH <<EOF
382 399 [diff]
383 400 git = True
384 401 EOF
385 402 cd ..
386 403 hg init git
387 404 cd git
388 405 hg qinit
389 406
390 407 hg qnew -m'new file' new
391 408 echo foo > new
392 409 chmod +x new
393 410 hg add new
394 411 hg qrefresh
395 412 sed -e "s/\(+++ [a-zA-Z0-9_/.-]*\).*/\1/" \
396 413 -e "s/\(--- [a-zA-Z0-9_/.-]*\).*/\1/" .hg/patches/new
397 414
398 415 hg qnew -m'copy file' copy
399 416 hg cp new copy
400 417 hg qrefresh
401 418 sed -e "s/\(+++ [a-zA-Z0-9_/.-]*\).*/\1/" \
402 419 -e "s/\(--- [a-zA-Z0-9_/.-]*\).*/\1/" .hg/patches/copy
403 420
404 421 hg qpop
405 422 hg qpush
406 423 hg qdiff
407 424 cat >>$HGRCPATH <<EOF
408 425 [diff]
409 426 git = False
410 427 EOF
411 428 hg qdiff --git
412 429 cd ..
413 430
414 431 echo % test file addition in slow path
415 432 hg init slow
416 433 cd slow
417 434 hg qinit
418 435 echo foo > foo
419 436 hg add foo
420 437 hg ci -m 'add foo'
421 438 hg qnew bar
422 439 echo bar > bar
423 440 hg add bar
424 441 hg mv foo baz
425 442 hg qrefresh --git
426 443 hg up -C 0
427 444 echo >> foo
428 445 hg ci -m 'change foo'
429 446 hg up -C 1
430 447 hg qrefresh --git 2>&1 | grep -v 'saving bundle'
431 448 cat .hg/patches/bar
432 449 hg log -v --template '{rev} {file_copies}\n' -r .
433 450 hg qrefresh --git
434 451 cat .hg/patches/bar
435 452 hg log -v --template '{rev} {file_copies}\n' -r .
436 453 hg qrefresh
437 454 grep 'diff --git' .hg/patches/bar
438 455
439 456 echo % test file move chains in the slow path
440 457 hg up -C 1
441 458 echo >> foo
442 459 hg ci -m 'change foo again'
443 460 hg up -C 2
444 461 hg mv bar quux
445 462 hg mv baz bleh
446 463 hg qrefresh --git 2>&1 | grep -v 'saving bundle'
447 464 cat .hg/patches/bar
448 465 hg log -v --template '{rev} {file_copies}\n' -r .
449 466 hg mv quux fred
450 467 hg mv bleh barney
451 468 hg qrefresh --git
452 469 cat .hg/patches/bar
453 470 hg log -v --template '{rev} {file_copies}\n' -r .
454 471
455 472 echo % refresh omitting an added file
456 473 hg qnew baz
457 474 echo newfile > newfile
458 475 hg add newfile
459 476 hg qrefresh
460 477 hg st -A newfile
461 478 hg qrefresh -X newfile
462 479 hg st -A newfile
463 480 hg revert newfile
464 481 rm newfile
465 482 hg qpop
466 483 hg qdel baz
467 484
468 485 echo % create a git patch
469 486 echo a > alexander
470 487 hg add alexander
471 488 hg qnew -f --git addalexander
472 489 grep diff .hg/patches/addalexander
473 490
474 491 echo % create a git binary patch
475 492 cat > writebin.py <<EOF
476 493 import sys
477 494 path = sys.argv[1]
478 495 open(path, 'wb').write('BIN\x00ARY')
479 496 EOF
480 497 python writebin.py bucephalus
481 498
482 499 python "$TESTDIR/md5sum.py" bucephalus
483 500 hg add bucephalus
484 501 hg qnew -f --git addbucephalus
485 502 grep diff .hg/patches/addbucephalus
486 503
487 504 echo % check binary patches can be popped and pushed
488 505 hg qpop
489 506 test -f bucephalus && echo % bucephalus should not be there
490 507 hg qpush
491 508 test -f bucephalus || echo % bucephalus should be there
492 509 python "$TESTDIR/md5sum.py" bucephalus
493 510
494 511
495 512 echo '% strip again'
496 513 cd ..
497 514 hg init strip
498 515 cd strip
499 516 touch foo
500 517 hg add foo
501 518 hg ci -m 'add foo'
502 519 echo >> foo
503 520 hg ci -m 'change foo 1'
504 521 hg up -C 0
505 522 echo 1 >> foo
506 523 hg ci -m 'change foo 2'
507 524 HGMERGE=true hg merge
508 525 hg ci -m merge
509 526 hg log
510 527 hg strip 1 | hidebackup
511 528 checkundo strip
512 529 hg log
513 530 cd ..
514 531
515 532 echo '% qclone'
516 533 qlog()
517 534 {
518 535 echo 'main repo:'
519 536 hg log --template ' rev {rev}: {desc}\n'
520 537 echo 'patch repo:'
521 538 hg -R .hg/patches log --template ' rev {rev}: {desc}\n'
522 539 }
523 540 hg init qclonesource
524 541 cd qclonesource
525 542 echo foo > foo
526 543 hg add foo
527 544 hg ci -m 'add foo'
528 545 hg qinit
529 546 hg qnew patch1
530 547 echo bar >> foo
531 548 hg qrefresh -m 'change foo'
532 549 cd ..
533 550
534 551 # repo with unversioned patch dir
535 552 hg qclone qclonesource failure
536 553
537 554 cd qclonesource
538 555 hg qinit -c
539 556 hg qci -m checkpoint
540 557 qlog
541 558 cd ..
542 559
543 560 # repo with patches applied
544 561 hg qclone qclonesource qclonedest
545 562 cd qclonedest
546 563 qlog
547 564 cd ..
548 565
549 566 # repo with patches unapplied
550 567 cd qclonesource
551 568 hg qpop -a
552 569 qlog
553 570 cd ..
554 571 hg qclone qclonesource qclonedest2
555 572 cd qclonedest2
556 573 qlog
557 574 cd ..
558 575
559 576 echo % 'test applying on an empty file (issue 1033)'
560 577 hg init empty
561 578 cd empty
562 579 touch a
563 580 hg ci -Am addempty
564 581 echo a > a
565 582 hg qnew -f -e changea
566 583 hg qpop
567 584 hg qpush
568 585 cd ..
569 586
570 587 echo % test qpush with --force, issue1087
571 588 hg init forcepush
572 589 cd forcepush
573 590 echo hello > hello.txt
574 591 echo bye > bye.txt
575 592 hg ci -Ama
576 593 hg qnew -d '0 0' empty
577 594 hg qpop
578 595 echo world >> hello.txt
579 596
580 597 echo % qpush should fail, local changes
581 598 hg qpush
582 599
583 600 echo % apply force, should not discard changes with empty patch
584 601 hg qpush -f 2>&1 | sed 's,^.*/patch,patch,g'
585 602 hg diff --config diff.nodates=True
586 603 hg qdiff --config diff.nodates=True
587 604 hg log -l1 -p
588 605 hg qref -d '0 0'
589 606 hg qpop
590 607 echo universe >> hello.txt
591 608 echo universe >> bye.txt
592 609
593 610 echo % qpush should fail, local changes
594 611 hg qpush
595 612
596 613 echo % apply force, should discard changes in hello, but not bye
597 614 hg qpush -f
598 615 hg st
599 616 hg diff --config diff.nodates=True
600 617 hg qdiff --config diff.nodates=True
601 618
602 619 echo % test popping revisions not in working dir ancestry
603 620 hg qseries -v
604 621 hg up qparent
605 622 hg qpop
606 623
607 624 cd ..
608 625 hg init deletion-order
609 626 cd deletion-order
610 627
611 628 touch a
612 629 hg ci -Aqm0
613 630
614 631 hg qnew rename-dir
615 632 hg rm a
616 633 hg qrefresh
617 634
618 635 mkdir a b
619 636 touch a/a b/b
620 637 hg add -q a b
621 638 hg qrefresh
622 639
623 640 echo % test popping must remove files added in subdirectories first
624 641 hg qpop
625 642 cd ..
@@ -1,10 +1,10 b''
1 1 adding a
2 2
3 3 #empty series
4 4
5 5 #qimport valid patch followed by invalid patch
6 6 adding b.patch to series file
7 abort: unable to read fakepatch
7 abort: unable to read file fakepatch
8 8
9 9 #valid patches before fail added to series
10 10 b.patch
@@ -1,54 +1,54 b''
1 1 % qimport non-existing-file
2 abort: unable to read non-existing-file
2 abort: unable to read file non-existing-file
3 3 % import email
4 4 adding email to series file
5 5 applying email
6 6 now at: email
7 7 % hg tip -v
8 8 changeset: 0:1a706973a7d8
9 9 tag: email
10 10 tag: qbase
11 11 tag: qtip
12 12 tag: tip
13 13 user: Username in patch <test@example.net>
14 14 date: Thu Jan 01 00:00:00 1970 +0000
15 15 files: x
16 16 description:
17 17 First line of commit message.
18 18
19 19 More text in commit message.
20 20
21 21
22 22 popping email
23 23 patch queue now empty
24 24 % import URL
25 25 adding url.diff to series file
26 26 url.diff
27 27 % import patch that already exists
28 28 abort: patch "url.diff" already exists
29 29 applying url.diff
30 30 now at: url.diff
31 31 foo
32 32 popping url.diff
33 33 patch queue now empty
34 34 % qimport -f
35 35 adding url.diff to series file
36 36 applying url.diff
37 37 now at: url.diff
38 38 foo2
39 39 popping url.diff
40 40 patch queue now empty
41 41 % build diff with CRLF
42 42 adding b
43 43 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
44 44 % qimport CRLF diff
45 45 adding b.diff to series file
46 46 applying b.diff
47 47 now at: b.diff
48 48 % try to import --push
49 49 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
50 50 adding another.diff to series file
51 51 applying another.diff
52 52 now at: another.diff
53 53 patch b.diff finalized without changeset message
54 54 patch another.diff finalized without changeset message
@@ -1,642 +1,653 b''
1 1 % help
2 2 mq extension - manage a stack of patches
3 3
4 4 This extension lets you work with a stack of patches in a Mercurial
5 5 repository. It manages two stacks of patches - all known patches, and applied
6 6 patches (subset of known patches).
7 7
8 8 Known patches are represented as patch files in the .hg/patches directory.
9 9 Applied patches are both patch files and changesets.
10 10
11 11 Common tasks (use "hg help command" for more details):
12 12
13 13 create new patch qnew
14 14 import existing patch qimport
15 15
16 16 print patch series qseries
17 17 print applied patches qapplied
18 18
19 19 add known patch to applied stack qpush
20 20 remove patch from applied stack qpop
21 21 refresh contents of top applied patch qrefresh
22 22
23 23 By default, mq will automatically use git patches when required to avoid
24 24 losing file mode changes, copy records, binary files or empty files creations
25 25 or deletions. This behaviour can be configured with:
26 26
27 27 [mq]
28 28 git = auto/keep/yes/no
29 29
30 30 If set to 'keep', mq will obey the [diff] section configuration while
31 31 preserving existing git patches upon qrefresh. If set to 'yes' or 'no', mq
32 32 will override the [diff] section and always generate git or regular patches,
33 33 possibly losing data in the second case.
34 34
35 35 You will by default be managing a patch queue named "patches". You can create
36 36 other, independent patch queues with the "hg qqueue" command.
37 37
38 38 list of commands:
39 39
40 40 qapplied print the patches already applied
41 41 qclone clone main and patch repository at same time
42 42 qdelete remove patches from queue
43 43 qdiff diff of the current patch and subsequent modifications
44 44 qfinish move applied patches into repository history
45 45 qfold fold the named patches into the current patch
46 46 qgoto push or pop patches until named patch is at top of stack
47 47 qguard set or print guards for a patch
48 48 qheader print the header of the topmost or specified patch
49 49 qimport import a patch
50 50 qnew create a new patch
51 51 qnext print the name of the next patch
52 52 qpop pop the current patch off the stack
53 53 qprev print the name of the previous patch
54 54 qpush push the next patch onto the stack
55 55 qqueue manage multiple patch queues
56 56 qrefresh update the current patch
57 57 qrename rename a patch
58 58 qselect set or print guarded patches to push
59 59 qseries print the entire series file
60 60 qtop print the name of the current patch
61 61 qunapplied print the patches not yet applied
62 62 strip strip a changeset and all its descendants from the repository
63 63
64 64 use "hg -v help mq" to show aliases and global options
65 65 adding a
66 66 updating to branch default
67 67 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
68 68 adding b/z
69 69 % qinit
70 70 % -R qinit
71 71 % qinit -c
72 72 A .hgignore
73 73 A series
74 74 % qinit; qinit -c
75 75 .hgignore:
76 76 ^\.hg
77 77 ^\.mq
78 78 syntax: glob
79 79 status
80 80 guards
81 81 series:
82 82 abort: repository already exists!
83 83 % qinit; <stuff>; qinit -c
84 84 adding .hg/patches/A
85 85 adding .hg/patches/B
86 86 A .hgignore
87 87 A A
88 88 A B
89 89 A series
90 90 .hgignore:
91 91 status
92 92 bleh
93 93 series:
94 94 A
95 95 B
96 96 % status --mq with color (issue2096)
97 97 A .hgignore
98 98 A A
99 99 A B
100 100 A series
101 101 % init --mq without repo
102 102 abort: There is no Mercurial repository here (.hg not found)
103 103 % init --mq with repo path
104 104 ok
105 105 % init --mq with nonexistent directory
106 106 abort: repository nonexistentdir not found!
107 107 % init --mq with bundle (non "local")
108 108 abort: only a local queue repository may be initialized
109 109 % qrefresh
110 110 foo bar
111 111
112 112 diff -r xa
113 113 --- a/a
114 114 +++ b/a
115 115 @@ -1,1 +1,2 @@
116 116 a
117 117 +a
118 118 % empty qrefresh
119 119 revision:
120 120 patch:
121 121 foo bar
122 122
123 123 working dir diff:
124 124 --- a/a
125 125 +++ b/a
126 126 @@ -1,1 +1,2 @@
127 127 a
128 128 +a
129 129 % qpop
130 130 popping test.patch
131 131 patch queue now empty
132 132 % qpush with dump of tag cache
133 133 .hg/tags.cache (pre qpush):
134 134 1
135 135
136 136 applying test.patch
137 137 now at: test.patch
138 138 .hg/tags.cache (post qpush):
139 139 2
140 140
141 141 % pop/push outside repo
142 142 popping test.patch
143 143 patch queue now empty
144 144 applying test.patch
145 145 now at: test.patch
146 146 % qrefresh in subdir
147 147 % pop/push -a in subdir
148 148 popping test2.patch
149 149 popping test.patch
150 150 patch queue now empty
151 151 applying test.patch
152 152 applying test2.patch
153 153 now at: test2.patch
154 154 % qseries
155 155 test.patch
156 156 test2.patch
157 157 0 A test.patch: f...
158 158 1 A test2.patch:
159 159 popping test2.patch
160 160 now at: test.patch
161 161 0 A test.patch: foo bar
162 162 1 U test2.patch:
163 163 mq: 1 applied, 1 unapplied
164 164 applying test2.patch
165 165 now at: test2.patch
166 166 mq: 2 applied
167 167 % qapplied
168 168 test.patch
169 169 test2.patch
170 170 % qtop
171 171 test2.patch
172 172 % prev
173 173 test.patch
174 174 % next
175 175 all patches applied
176 176 popping test2.patch
177 177 now at: test.patch
178 178 % commit should fail
179 179 abort: cannot commit over an applied mq patch
180 180 % push should fail
181 181 pushing to ../../k
182 182 abort: source has mq patches applied
183 183 % import should fail
184 184 abort: cannot import over an applied patch
185 185 % import --no-commit should succeed
186 186 applying ../../import.diff
187 187 M a
188 188 % qunapplied
189 189 test2.patch
190 190 % qpush/qpop with index
191 191 applying test2.patch
192 192 now at: test2.patch
193 193 popping test2.patch
194 194 popping test1b.patch
195 195 now at: test.patch
196 196 applying test1b.patch
197 197 now at: test1b.patch
198 198 applying test2.patch
199 199 now at: test2.patch
200 200 popping test2.patch
201 201 now at: test1b.patch
202 202 popping test1b.patch
203 203 now at: test.patch
204 204 applying test1b.patch
205 205 applying test2.patch
206 206 now at: test2.patch
207 207 % qpush --move
208 208 popping test2.patch
209 209 popping test1b.patch
210 210 popping test.patch
211 211 patch queue now empty
212 cannot push 'test2.patch' - guarded by ['+posguard']
213 number of unguarded, unapplied patches has changed from 2 to 3
212 214 applying test2.patch
213 215 now at: test2.patch
214 216 applying test1b.patch
215 217 now at: test1b.patch
216 218 applying test.patch
217 219 now at: test.patch
218 220 0 A test2.patch
219 221 1 A test1b.patch
220 222 2 A test.patch
221 223 popping test.patch
222 224 popping test1b.patch
223 225 popping test2.patch
224 226 patch queue now empty
227 guards deactivated
228 number of unguarded, unapplied patches has changed from 3 to 2
225 229 applying test.patch
226 230 now at: test.patch
227 231 applying test1b.patch
228 232 now at: test1b.patch
229 233 abort: patch bogus not in series
234 abort: please specify the patch to move
230 235 abort: cannot push to a previous patch: test.patch
231 236 applying test2.patch
232 237 now at: test2.patch
238 % series after move
239 test.patch
240 test1b.patch
241 test2.patch
242 # comment
243
233 244 % pop, qapplied, qunapplied
234 245 0 A test.patch
235 246 1 A test1b.patch
236 247 2 A test2.patch
237 248 % qapplied -1 test.patch
238 249 only one patch applied
239 250 % qapplied -1 test1b.patch
240 251 test.patch
241 252 % qapplied -1 test2.patch
242 253 test1b.patch
243 254 % qapplied -1
244 255 test1b.patch
245 256 % qapplied
246 257 test.patch
247 258 test1b.patch
248 259 test2.patch
249 260 % qapplied test1b.patch
250 261 test.patch
251 262 test1b.patch
252 263 % qunapplied -1
253 264 all patches applied
254 265 % qunapplied
255 266 % popping
256 267 popping test2.patch
257 268 now at: test1b.patch
258 269 % qunapplied -1
259 270 test2.patch
260 271 % qunapplied
261 272 test2.patch
262 273 % qunapplied test2.patch
263 274 % qunapplied -1 test2.patch
264 275 all patches applied
265 276 % popping -a
266 277 popping test1b.patch
267 278 popping test.patch
268 279 patch queue now empty
269 280 % qapplied
270 281 % qapplied -1
271 282 no patches applied
272 283 applying test.patch
273 284 now at: test.patch
274 285 % push should succeed
275 286 popping test.patch
276 287 patch queue now empty
277 288 pushing to ../../k
278 289 searching for changes
279 290 adding changesets
280 291 adding manifests
281 292 adding file changes
282 293 added 1 changesets with 1 changes to 1 files
283 294 % qpush/qpop error codes
284 295 applying test.patch
285 296 applying test1b.patch
286 297 applying test2.patch
287 298 now at: test2.patch
288 299 % pops all patches and succeeds
289 300 popping test2.patch
290 301 popping test1b.patch
291 302 popping test.patch
292 303 patch queue now empty
293 304 qpop -a succeeds
294 305 % does nothing and succeeds
295 306 no patches applied
296 307 qpop -a succeeds
297 308 % fails - nothing else to pop
298 309 no patches applied
299 310 qpop fails
300 311 % pushes a patch and succeeds
301 312 applying test.patch
302 313 now at: test.patch
303 314 qpush succeeds
304 315 % pops a patch and succeeds
305 316 popping test.patch
306 317 patch queue now empty
307 318 qpop succeeds
308 319 % pushes up to test1b.patch and succeeds
309 320 applying test.patch
310 321 applying test1b.patch
311 322 now at: test1b.patch
312 323 qpush test1b.patch succeeds
313 324 % does nothing and succeeds
314 325 qpush: test1b.patch is already at the top
315 326 qpush test1b.patch succeeds
316 327 % does nothing and succeeds
317 328 qpop: test1b.patch is already at the top
318 329 qpop test1b.patch succeeds
319 330 % fails - can't push to this patch
320 331 abort: cannot push to a previous patch: test.patch
321 332 qpush test.patch fails
322 333 % fails - can't pop to this patch
323 334 abort: patch test2.patch is not applied
324 335 qpop test2.patch fails
325 336 % pops up to test.patch and succeeds
326 337 popping test1b.patch
327 338 now at: test.patch
328 339 qpop test.patch succeeds
329 340 % pushes all patches and succeeds
330 341 applying test1b.patch
331 342 applying test2.patch
332 343 now at: test2.patch
333 344 qpush -a succeeds
334 345 % does nothing and succeeds
335 346 all patches are currently applied
336 347 qpush -a succeeds
337 348 % fails - nothing else to push
338 349 patch series already fully applied
339 350 qpush fails
340 351 % does nothing and succeeds
341 352 qpush: test2.patch is already at the top
342 353 qpush test2.patch succeeds
343 354 % strip
344 355 adding x
345 356 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
346 357 saved backup bundle to
347 358 adding changesets
348 359 adding manifests
349 360 adding file changes
350 361 added 1 changesets with 1 changes to 1 files
351 362 (run 'hg update' to get a working copy)
352 363 % strip with local changes, should complain
353 364 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
354 365 abort: local changes found
355 366 % --force strip with local changes
356 367 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
357 368 saved backup bundle to
358 369 % cd b; hg qrefresh
359 370 adding a
360 371 foo
361 372
362 373 diff -r cb9a9f314b8b a
363 374 --- a/a
364 375 +++ b/a
365 376 @@ -1,1 +1,2 @@
366 377 a
367 378 +a
368 379 diff -r cb9a9f314b8b b/f
369 380 --- /dev/null
370 381 +++ b/b/f
371 382 @@ -0,0 +1,1 @@
372 383 +f
373 384 % hg qrefresh .
374 385 foo
375 386
376 387 diff -r cb9a9f314b8b b/f
377 388 --- /dev/null
378 389 +++ b/b/f
379 390 @@ -0,0 +1,1 @@
380 391 +f
381 392 M a
382 393 % qpush failure
383 394 popping bar
384 395 popping foo
385 396 patch queue now empty
386 397 applying foo
387 398 applying bar
388 399 file foo already exists
389 400 1 out of 1 hunks FAILED -- saving rejects to file foo.rej
390 401 patch failed, unable to continue (try -v)
391 402 patch failed, rejects left in working dir
392 403 errors during apply, please fix and refresh bar
393 404 ? foo
394 405 ? foo.rej
395 406 % mq tags
396 407 0 qparent
397 408 1 foo qbase
398 409 2 bar qtip tip
399 410 % bad node in status
400 411 popping bar
401 412 now at: foo
402 413 changeset: 0:cb9a9f314b8b
403 414 tag: tip
404 415 user: test
405 416 date: Thu Jan 01 00:00:00 1970 +0000
406 417 summary: a
407 418
408 419 default 0:cb9a9f314b8b
409 420 no patches applied
410 421 new file
411 422
412 423 diff --git a/new b/new
413 424 new file mode 100755
414 425 --- /dev/null
415 426 +++ b/new
416 427 @@ -0,0 +1,1 @@
417 428 +foo
418 429 copy file
419 430
420 431 diff --git a/new b/copy
421 432 copy from new
422 433 copy to copy
423 434 popping copy
424 435 now at: new
425 436 applying copy
426 437 now at: copy
427 438 diff --git a/new b/copy
428 439 copy from new
429 440 copy to copy
430 441 diff --git a/new b/copy
431 442 copy from new
432 443 copy to copy
433 444 % test file addition in slow path
434 445 1 files updated, 0 files merged, 2 files removed, 0 files unresolved
435 446 created new head
436 447 2 files updated, 0 files merged, 1 files removed, 0 files unresolved
437 448 diff --git a/bar b/bar
438 449 new file mode 100644
439 450 --- /dev/null
440 451 +++ b/bar
441 452 @@ -0,0 +1,1 @@
442 453 +bar
443 454 diff --git a/foo b/baz
444 455 rename from foo
445 456 rename to baz
446 457 2 baz (foo)
447 458 diff --git a/bar b/bar
448 459 new file mode 100644
449 460 --- /dev/null
450 461 +++ b/bar
451 462 @@ -0,0 +1,1 @@
452 463 +bar
453 464 diff --git a/foo b/baz
454 465 rename from foo
455 466 rename to baz
456 467 2 baz (foo)
457 468 diff --git a/bar b/bar
458 469 diff --git a/foo b/baz
459 470 % test file move chains in the slow path
460 471 1 files updated, 0 files merged, 2 files removed, 0 files unresolved
461 472 2 files updated, 0 files merged, 1 files removed, 0 files unresolved
462 473 diff --git a/foo b/bleh
463 474 rename from foo
464 475 rename to bleh
465 476 diff --git a/quux b/quux
466 477 new file mode 100644
467 478 --- /dev/null
468 479 +++ b/quux
469 480 @@ -0,0 +1,1 @@
470 481 +bar
471 482 3 bleh (foo)
472 483 diff --git a/foo b/barney
473 484 rename from foo
474 485 rename to barney
475 486 diff --git a/fred b/fred
476 487 new file mode 100644
477 488 --- /dev/null
478 489 +++ b/fred
479 490 @@ -0,0 +1,1 @@
480 491 +bar
481 492 3 barney (foo)
482 493 % refresh omitting an added file
483 494 C newfile
484 495 A newfile
485 496 popping baz
486 497 now at: bar
487 498 % create a git patch
488 499 diff --git a/alexander b/alexander
489 500 % create a git binary patch
490 501 8ba2a2f3e77b55d03051ff9c24ad65e7 bucephalus
491 502 diff --git a/bucephalus b/bucephalus
492 503 % check binary patches can be popped and pushed
493 504 popping addbucephalus
494 505 now at: addalexander
495 506 applying addbucephalus
496 507 now at: addbucephalus
497 508 8ba2a2f3e77b55d03051ff9c24ad65e7 bucephalus
498 509 % strip again
499 510 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
500 511 created new head
501 512 merging foo
502 513 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
503 514 (branch merge, don't forget to commit)
504 515 changeset: 3:99615015637b
505 516 tag: tip
506 517 parent: 2:20cbbe65cff7
507 518 parent: 1:d2871fc282d4
508 519 user: test
509 520 date: Thu Jan 01 00:00:00 1970 +0000
510 521 summary: merge
511 522
512 523 changeset: 2:20cbbe65cff7
513 524 parent: 0:53245c60e682
514 525 user: test
515 526 date: Thu Jan 01 00:00:00 1970 +0000
516 527 summary: change foo 2
517 528
518 529 changeset: 1:d2871fc282d4
519 530 user: test
520 531 date: Thu Jan 01 00:00:00 1970 +0000
521 532 summary: change foo 1
522 533
523 534 changeset: 0:53245c60e682
524 535 user: test
525 536 date: Thu Jan 01 00:00:00 1970 +0000
526 537 summary: add foo
527 538
528 539 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
529 540 saved backup bundle to
530 541 changeset: 1:20cbbe65cff7
531 542 tag: tip
532 543 user: test
533 544 date: Thu Jan 01 00:00:00 1970 +0000
534 545 summary: change foo 2
535 546
536 547 changeset: 0:53245c60e682
537 548 user: test
538 549 date: Thu Jan 01 00:00:00 1970 +0000
539 550 summary: add foo
540 551
541 552 % qclone
542 553 abort: versioned patch repository not found (see init --mq)
543 554 adding .hg/patches/patch1
544 555 main repo:
545 556 rev 1: change foo
546 557 rev 0: add foo
547 558 patch repo:
548 559 rev 0: checkpoint
549 560 updating to branch default
550 561 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
551 562 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
552 563 main repo:
553 564 rev 0: add foo
554 565 patch repo:
555 566 rev 0: checkpoint
556 567 popping patch1
557 568 patch queue now empty
558 569 main repo:
559 570 rev 0: add foo
560 571 patch repo:
561 572 rev 0: checkpoint
562 573 updating to branch default
563 574 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
564 575 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
565 576 main repo:
566 577 rev 0: add foo
567 578 patch repo:
568 579 rev 0: checkpoint
569 580 % test applying on an empty file (issue 1033)
570 581 adding a
571 582 popping changea
572 583 patch queue now empty
573 584 applying changea
574 585 now at: changea
575 586 % test qpush with --force, issue1087
576 587 adding bye.txt
577 588 adding hello.txt
578 589 popping empty
579 590 patch queue now empty
580 591 % qpush should fail, local changes
581 592 abort: local changes found, refresh first
582 593 % apply force, should not discard changes with empty patch
583 594 applying empty
584 595 patch empty is empty
585 596 now at: empty
586 597 diff -r bf5fc3f07a0a hello.txt
587 598 --- a/hello.txt
588 599 +++ b/hello.txt
589 600 @@ -1,1 +1,2 @@
590 601 hello
591 602 +world
592 603 diff -r 9ecee4f634e3 hello.txt
593 604 --- a/hello.txt
594 605 +++ b/hello.txt
595 606 @@ -1,1 +1,2 @@
596 607 hello
597 608 +world
598 609 changeset: 1:bf5fc3f07a0a
599 610 tag: empty
600 611 tag: qbase
601 612 tag: qtip
602 613 tag: tip
603 614 user: test
604 615 date: Thu Jan 01 00:00:00 1970 +0000
605 616 summary: imported patch empty
606 617
607 618
608 619 popping empty
609 620 patch queue now empty
610 621 % qpush should fail, local changes
611 622 abort: local changes found, refresh first
612 623 % apply force, should discard changes in hello, but not bye
613 624 applying empty
614 625 now at: empty
615 626 M bye.txt
616 627 diff -r ba252371dbc1 bye.txt
617 628 --- a/bye.txt
618 629 +++ b/bye.txt
619 630 @@ -1,1 +1,2 @@
620 631 bye
621 632 +universe
622 633 diff -r 9ecee4f634e3 bye.txt
623 634 --- a/bye.txt
624 635 +++ b/bye.txt
625 636 @@ -1,1 +1,2 @@
626 637 bye
627 638 +universe
628 639 diff -r 9ecee4f634e3 hello.txt
629 640 --- a/hello.txt
630 641 +++ b/hello.txt
631 642 @@ -1,1 +1,3 @@
632 643 hello
633 644 +world
634 645 +universe
635 646 % test popping revisions not in working dir ancestry
636 647 0 A empty
637 648 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
638 649 popping empty
639 650 patch queue now empty
640 651 % test popping must remove files added in subdirectories first
641 652 popping rename-dir
642 653 patch queue now empty
@@ -1,82 +1,82 b''
1 1 adding a/a
2 2 adding a/b
3 3 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
4 4 moving a/a to b/a
5 5 moving a/b to b/b
6 6 2 files updated, 0 files merged, 2 files removed, 0 files unresolved
7 7 created new head
8 8 searching for copies back to rev 1
9 9 unmatched files in local:
10 10 a/c
11 11 a/d
12 12 unmatched files in other:
13 13 b/a
14 14 b/b
15 15 all copies found (* = to merge, ! = divergent):
16 16 b/a -> a/a
17 17 b/b -> a/b
18 18 checking for directory renames
19 19 dir a/ -> b/
20 20 file a/c -> b/c
21 21 file a/d -> b/d
22 22 resolving manifests
23 23 overwrite None partial False
24 24 ancestor f9b20c0d4c51 local ce36d17b18fb+ remote 397f8b00a740
25 25 a/d: remote renamed directory to b/d -> d
26 26 a/c: remote renamed directory to b/c -> d
27 27 a/b: other deleted -> r
28 28 a/a: other deleted -> r
29 29 b/a: remote created -> g
30 30 b/b: remote created -> g
31 update: a/a 1/6 files (16.67%)
31 updating: a/a 1/6 files (16.67%)
32 32 removing a/a
33 update: a/b 2/6 files (33.33%)
33 updating: a/b 2/6 files (33.33%)
34 34 removing a/b
35 update: a/c 3/6 files (50.00%)
35 updating: a/c 3/6 files (50.00%)
36 36 moving a/c to b/c
37 update: a/d 4/6 files (66.67%)
37 updating: a/d 4/6 files (66.67%)
38 38 moving a/d to b/d
39 update: b/a 5/6 files (83.33%)
39 updating: b/a 5/6 files (83.33%)
40 40 getting b/a
41 update: b/b 6/6 files (100.00%)
41 updating: b/b 6/6 files (100.00%)
42 42 getting b/b
43 43 4 files updated, 0 files merged, 2 files removed, 0 files unresolved
44 44 (branch merge, don't forget to commit)
45 45 a/* b/a b/b b/c b/d
46 46 M b/a
47 47 M b/b
48 48 A b/c
49 49 a/c
50 50 R a/a
51 51 R a/b
52 52 R a/c
53 53 ? b/d
54 54 b/c renamed from a/c:354ae8da6e890359ef49ade27b68bbc361f3ca88
55 55 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
56 56 searching for copies back to rev 1
57 57 unmatched files in local:
58 58 b/a
59 59 b/b
60 60 b/d
61 61 unmatched files in other:
62 62 a/c
63 63 all copies found (* = to merge, ! = divergent):
64 64 b/a -> a/a
65 65 b/b -> a/b
66 66 checking for directory renames
67 67 dir a/ -> b/
68 68 file a/c -> b/c
69 69 resolving manifests
70 70 overwrite None partial False
71 71 ancestor f9b20c0d4c51 local 397f8b00a740+ remote ce36d17b18fb
72 72 None: local renamed directory to b/c -> d
73 update:None 1/1 files (100.00%)
73 updating:None 1/1 files (100.00%)
74 74 getting a/c to b/c
75 75 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
76 76 (branch merge, don't forget to commit)
77 77 a/* b/a b/b b/c b/d
78 78 A b/c
79 79 a/c
80 80 ? b/d
81 81 created new head
82 82 b/c renamed from a/c:354ae8da6e890359ef49ade27b68bbc361f3ca88
@@ -1,46 +1,46 b''
1 1 checkout
2 2 2 files updated, 0 files merged, 2 files removed, 0 files unresolved
3 3 created new head
4 4 merge
5 5 searching for copies back to rev 1
6 6 unmatched files in local:
7 7 c2
8 8 unmatched files in other:
9 9 b
10 10 b2
11 11 all copies found (* = to merge, ! = divergent):
12 12 c2 -> a2 !
13 13 b -> a *
14 14 b2 -> a2 !
15 15 checking for directory renames
16 16 a2: divergent renames -> dr
17 17 resolving manifests
18 18 overwrite None partial False
19 19 ancestor af1939970a1c local 044f8520aeeb+ remote 85c198ef2f6c
20 20 a: remote moved to b -> m
21 21 b2: remote created -> g
22 22 preserving a for resolve of b
23 23 removing a
24 update: a 1/3 files (33.33%)
24 updating: a 1/3 files (33.33%)
25 25 picked tool 'internal:merge' for b (binary False symlink False)
26 26 merging a and b to b
27 27 my b@044f8520aeeb+ other b@85c198ef2f6c ancestor a@af1939970a1c
28 28 premerge successful
29 update: a2 2/3 files (66.67%)
29 updating: a2 2/3 files (66.67%)
30 30 warning: detected divergent renames of a2 to:
31 31 c2
32 32 b2
33 update: b2 3/3 files (100.00%)
33 updating: b2 3/3 files (100.00%)
34 34 getting b2
35 35 1 files updated, 1 files merged, 0 files removed, 0 files unresolved
36 36 (branch merge, don't forget to commit)
37 37 M b
38 38 a
39 39 M b2
40 40 R a
41 41 C c2
42 42 blahblah
43 43 rev offset length base linkrev nodeid p1 p2
44 44 0 0 67 0 1 57eacc201a7f 000000000000 000000000000
45 45 1 67 72 1 3 4727ba907962 000000000000 57eacc201a7f
46 46 b renamed from a:dd03b83622e78778b403775d0d074b9ac7387a66
@@ -1,651 +1,651 b''
1 1 created new head
2 2 --------------
3 3 test L:up a R:nc a b W: - 1 get local a to b
4 4 --------------
5 5 searching for copies back to rev 1
6 6 unmatched files in other:
7 7 b
8 8 all copies found (* = to merge, ! = divergent):
9 9 b -> a *
10 10 checking for directory renames
11 11 resolving manifests
12 12 overwrite None partial False
13 13 ancestor 924404dff337 local e300d1c794ec+ remote 4ce40f5aca24
14 14 rev: versions differ -> m
15 15 a: remote copied to b -> m
16 16 preserving a for resolve of b
17 17 preserving rev for resolve of rev
18 update: a 1/2 files (50.00%)
18 updating: a 1/2 files (50.00%)
19 19 picked tool 'python ../merge' for b (binary False symlink False)
20 20 merging a and b to b
21 21 my b@e300d1c794ec+ other b@4ce40f5aca24 ancestor a@924404dff337
22 22 premerge successful
23 update: rev 2/2 files (100.00%)
23 updating: rev 2/2 files (100.00%)
24 24 picked tool 'python ../merge' for rev (binary False symlink False)
25 25 merging rev
26 26 my rev@e300d1c794ec+ other rev@4ce40f5aca24 ancestor rev@924404dff337
27 27 0 files updated, 2 files merged, 0 files removed, 0 files unresolved
28 28 (branch merge, don't forget to commit)
29 29 --------------
30 30 M b
31 31 a
32 32 C a
33 33 --------------
34 34
35 35 created new head
36 36 --------------
37 37 test L:nc a b R:up a W: - 2 get rem change to a and b
38 38 --------------
39 39 searching for copies back to rev 1
40 40 unmatched files in local:
41 41 b
42 42 all copies found (* = to merge, ! = divergent):
43 43 b -> a *
44 44 checking for directory renames
45 45 resolving manifests
46 46 overwrite None partial False
47 47 ancestor 924404dff337 local 86a2aa42fc76+ remote f4db7e329e71
48 48 a: remote is newer -> g
49 49 b: local copied/moved to a -> m
50 50 rev: versions differ -> m
51 51 preserving b for resolve of b
52 52 preserving rev for resolve of rev
53 update: a 1/3 files (33.33%)
53 updating: a 1/3 files (33.33%)
54 54 getting a
55 update: b 2/3 files (66.67%)
55 updating: b 2/3 files (66.67%)
56 56 picked tool 'python ../merge' for b (binary False symlink False)
57 57 merging b and a to b
58 58 my b@86a2aa42fc76+ other a@f4db7e329e71 ancestor a@924404dff337
59 59 premerge successful
60 update: rev 3/3 files (100.00%)
60 updating: rev 3/3 files (100.00%)
61 61 picked tool 'python ../merge' for rev (binary False symlink False)
62 62 merging rev
63 63 my rev@86a2aa42fc76+ other rev@f4db7e329e71 ancestor rev@924404dff337
64 64 1 files updated, 2 files merged, 0 files removed, 0 files unresolved
65 65 (branch merge, don't forget to commit)
66 66 --------------
67 67 M a
68 68 M b
69 69 a
70 70 --------------
71 71
72 72 created new head
73 73 --------------
74 74 test L:up a R:nm a b W: - 3 get local a change to b, remove a
75 75 --------------
76 76 searching for copies back to rev 1
77 77 unmatched files in other:
78 78 b
79 79 all copies found (* = to merge, ! = divergent):
80 80 b -> a *
81 81 checking for directory renames
82 82 resolving manifests
83 83 overwrite None partial False
84 84 ancestor 924404dff337 local e300d1c794ec+ remote bdb19105162a
85 85 rev: versions differ -> m
86 86 a: remote moved to b -> m
87 87 preserving a for resolve of b
88 88 preserving rev for resolve of rev
89 89 removing a
90 update: a 1/2 files (50.00%)
90 updating: a 1/2 files (50.00%)
91 91 picked tool 'python ../merge' for b (binary False symlink False)
92 92 merging a and b to b
93 93 my b@e300d1c794ec+ other b@bdb19105162a ancestor a@924404dff337
94 94 premerge successful
95 update: rev 2/2 files (100.00%)
95 updating: rev 2/2 files (100.00%)
96 96 picked tool 'python ../merge' for rev (binary False symlink False)
97 97 merging rev
98 98 my rev@e300d1c794ec+ other rev@bdb19105162a ancestor rev@924404dff337
99 99 0 files updated, 2 files merged, 0 files removed, 0 files unresolved
100 100 (branch merge, don't forget to commit)
101 101 --------------
102 102 M b
103 103 a
104 104 --------------
105 105
106 106 created new head
107 107 --------------
108 108 test L:nm a b R:up a W: - 4 get remote change to b
109 109 --------------
110 110 searching for copies back to rev 1
111 111 unmatched files in local:
112 112 b
113 113 all copies found (* = to merge, ! = divergent):
114 114 b -> a *
115 115 checking for directory renames
116 116 resolving manifests
117 117 overwrite None partial False
118 118 ancestor 924404dff337 local 02963e448370+ remote f4db7e329e71
119 119 b: local copied/moved to a -> m
120 120 rev: versions differ -> m
121 121 preserving b for resolve of b
122 122 preserving rev for resolve of rev
123 update: b 1/2 files (50.00%)
123 updating: b 1/2 files (50.00%)
124 124 picked tool 'python ../merge' for b (binary False symlink False)
125 125 merging b and a to b
126 126 my b@02963e448370+ other a@f4db7e329e71 ancestor a@924404dff337
127 127 premerge successful
128 update: rev 2/2 files (100.00%)
128 updating: rev 2/2 files (100.00%)
129 129 picked tool 'python ../merge' for rev (binary False symlink False)
130 130 merging rev
131 131 my rev@02963e448370+ other rev@f4db7e329e71 ancestor rev@924404dff337
132 132 0 files updated, 2 files merged, 0 files removed, 0 files unresolved
133 133 (branch merge, don't forget to commit)
134 134 --------------
135 135 M b
136 136 a
137 137 --------------
138 138
139 139 created new head
140 140 --------------
141 141 test L: R:nc a b W: - 5 get b
142 142 --------------
143 143 searching for copies back to rev 1
144 144 unmatched files in other:
145 145 b
146 146 all copies found (* = to merge, ! = divergent):
147 147 b -> a
148 148 checking for directory renames
149 149 resolving manifests
150 150 overwrite None partial False
151 151 ancestor 924404dff337 local 94b33a1b7f2d+ remote 4ce40f5aca24
152 152 rev: versions differ -> m
153 153 b: remote created -> g
154 154 preserving rev for resolve of rev
155 update: b 1/2 files (50.00%)
155 updating: b 1/2 files (50.00%)
156 156 getting b
157 update: rev 2/2 files (100.00%)
157 updating: rev 2/2 files (100.00%)
158 158 picked tool 'python ../merge' for rev (binary False symlink False)
159 159 merging rev
160 160 my rev@94b33a1b7f2d+ other rev@4ce40f5aca24 ancestor rev@924404dff337
161 161 1 files updated, 1 files merged, 0 files removed, 0 files unresolved
162 162 (branch merge, don't forget to commit)
163 163 --------------
164 164 M b
165 165 C a
166 166 --------------
167 167
168 168 created new head
169 169 --------------
170 170 test L:nc a b R: W: - 6 nothing
171 171 --------------
172 172 searching for copies back to rev 1
173 173 unmatched files in local:
174 174 b
175 175 all copies found (* = to merge, ! = divergent):
176 176 b -> a
177 177 checking for directory renames
178 178 resolving manifests
179 179 overwrite None partial False
180 180 ancestor 924404dff337 local 86a2aa42fc76+ remote 97c705ade336
181 181 rev: versions differ -> m
182 182 preserving rev for resolve of rev
183 update: rev 1/1 files (100.00%)
183 updating: rev 1/1 files (100.00%)
184 184 picked tool 'python ../merge' for rev (binary False symlink False)
185 185 merging rev
186 186 my rev@86a2aa42fc76+ other rev@97c705ade336 ancestor rev@924404dff337
187 187 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
188 188 (branch merge, don't forget to commit)
189 189 --------------
190 190 C a
191 191 C b
192 192 --------------
193 193
194 194 created new head
195 195 --------------
196 196 test L: R:nm a b W: - 7 get b
197 197 --------------
198 198 searching for copies back to rev 1
199 199 unmatched files in other:
200 200 b
201 201 all copies found (* = to merge, ! = divergent):
202 202 b -> a
203 203 checking for directory renames
204 204 resolving manifests
205 205 overwrite None partial False
206 206 ancestor 924404dff337 local 94b33a1b7f2d+ remote bdb19105162a
207 207 a: other deleted -> r
208 208 rev: versions differ -> m
209 209 b: remote created -> g
210 210 preserving rev for resolve of rev
211 update: a 1/3 files (33.33%)
211 updating: a 1/3 files (33.33%)
212 212 removing a
213 update: b 2/3 files (66.67%)
213 updating: b 2/3 files (66.67%)
214 214 getting b
215 update: rev 3/3 files (100.00%)
215 updating: rev 3/3 files (100.00%)
216 216 picked tool 'python ../merge' for rev (binary False symlink False)
217 217 merging rev
218 218 my rev@94b33a1b7f2d+ other rev@bdb19105162a ancestor rev@924404dff337
219 219 1 files updated, 1 files merged, 1 files removed, 0 files unresolved
220 220 (branch merge, don't forget to commit)
221 221 --------------
222 222 M b
223 223 --------------
224 224
225 225 created new head
226 226 --------------
227 227 test L:nm a b R: W: - 8 nothing
228 228 --------------
229 229 searching for copies back to rev 1
230 230 unmatched files in local:
231 231 b
232 232 all copies found (* = to merge, ! = divergent):
233 233 b -> a
234 234 checking for directory renames
235 235 resolving manifests
236 236 overwrite None partial False
237 237 ancestor 924404dff337 local 02963e448370+ remote 97c705ade336
238 238 rev: versions differ -> m
239 239 preserving rev for resolve of rev
240 update: rev 1/1 files (100.00%)
240 updating: rev 1/1 files (100.00%)
241 241 picked tool 'python ../merge' for rev (binary False symlink False)
242 242 merging rev
243 243 my rev@02963e448370+ other rev@97c705ade336 ancestor rev@924404dff337
244 244 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
245 245 (branch merge, don't forget to commit)
246 246 --------------
247 247 C b
248 248 --------------
249 249
250 250 created new head
251 251 --------------
252 252 test L:um a b R:um a b W: - 9 do merge with ancestor in a
253 253 --------------
254 254 searching for copies back to rev 1
255 255 resolving manifests
256 256 overwrite None partial False
257 257 ancestor 924404dff337 local 62e7bf090eba+ remote 49b6d8032493
258 258 b: versions differ -> m
259 259 rev: versions differ -> m
260 260 preserving b for resolve of b
261 261 preserving rev for resolve of rev
262 update: b 1/2 files (50.00%)
262 updating: b 1/2 files (50.00%)
263 263 picked tool 'python ../merge' for b (binary False symlink False)
264 264 merging b
265 265 my b@62e7bf090eba+ other b@49b6d8032493 ancestor a@924404dff337
266 update: rev 2/2 files (100.00%)
266 updating: rev 2/2 files (100.00%)
267 267 picked tool 'python ../merge' for rev (binary False symlink False)
268 268 merging rev
269 269 my rev@62e7bf090eba+ other rev@49b6d8032493 ancestor rev@924404dff337
270 270 0 files updated, 2 files merged, 0 files removed, 0 files unresolved
271 271 (branch merge, don't forget to commit)
272 272 --------------
273 273 M b
274 274 --------------
275 275
276 276 created new head
277 277 --------------
278 278 test L:nm a b R:nm a c W: - 11 get c, keep b
279 279 --------------
280 280 searching for copies back to rev 1
281 281 unmatched files in local:
282 282 b
283 283 unmatched files in other:
284 284 c
285 285 all copies found (* = to merge, ! = divergent):
286 286 c -> a !
287 287 b -> a !
288 288 checking for directory renames
289 289 a: divergent renames -> dr
290 290 resolving manifests
291 291 overwrite None partial False
292 292 ancestor 924404dff337 local 02963e448370+ remote fe905ef2c33e
293 293 rev: versions differ -> m
294 294 c: remote created -> g
295 295 preserving rev for resolve of rev
296 update: a 1/3 files (33.33%)
296 updating: a 1/3 files (33.33%)
297 297 warning: detected divergent renames of a to:
298 298 b
299 299 c
300 update: c 2/3 files (66.67%)
300 updating: c 2/3 files (66.67%)
301 301 getting c
302 update: rev 3/3 files (100.00%)
302 updating: rev 3/3 files (100.00%)
303 303 picked tool 'python ../merge' for rev (binary False symlink False)
304 304 merging rev
305 305 my rev@02963e448370+ other rev@fe905ef2c33e ancestor rev@924404dff337
306 306 1 files updated, 1 files merged, 0 files removed, 0 files unresolved
307 307 (branch merge, don't forget to commit)
308 308 --------------
309 309 M c
310 310 C b
311 311 --------------
312 312
313 313 created new head
314 314 --------------
315 315 test L:nc a b R:up b W: - 12 merge b no ancestor
316 316 --------------
317 317 searching for copies back to rev 1
318 318 resolving manifests
319 319 overwrite None partial False
320 320 ancestor 924404dff337 local 86a2aa42fc76+ remote af30c7647fc7
321 321 b: versions differ -> m
322 322 rev: versions differ -> m
323 323 preserving b for resolve of b
324 324 preserving rev for resolve of rev
325 update: b 1/2 files (50.00%)
325 updating: b 1/2 files (50.00%)
326 326 picked tool 'python ../merge' for b (binary False symlink False)
327 327 merging b
328 328 my b@86a2aa42fc76+ other b@af30c7647fc7 ancestor b@000000000000
329 update: rev 2/2 files (100.00%)
329 updating: rev 2/2 files (100.00%)
330 330 picked tool 'python ../merge' for rev (binary False symlink False)
331 331 merging rev
332 332 my rev@86a2aa42fc76+ other rev@af30c7647fc7 ancestor rev@924404dff337
333 333 0 files updated, 2 files merged, 0 files removed, 0 files unresolved
334 334 (branch merge, don't forget to commit)
335 335 --------------
336 336 M b
337 337 C a
338 338 --------------
339 339
340 340 created new head
341 341 --------------
342 342 test L:up b R:nm a b W: - 13 merge b no ancestor
343 343 --------------
344 344 searching for copies back to rev 1
345 345 resolving manifests
346 346 overwrite None partial False
347 347 ancestor 924404dff337 local 59318016310c+ remote bdb19105162a
348 348 a: other deleted -> r
349 349 b: versions differ -> m
350 350 rev: versions differ -> m
351 351 preserving b for resolve of b
352 352 preserving rev for resolve of rev
353 update: a 1/3 files (33.33%)
353 updating: a 1/3 files (33.33%)
354 354 removing a
355 update: b 2/3 files (66.67%)
355 updating: b 2/3 files (66.67%)
356 356 picked tool 'python ../merge' for b (binary False symlink False)
357 357 merging b
358 358 my b@59318016310c+ other b@bdb19105162a ancestor b@000000000000
359 update: rev 3/3 files (100.00%)
359 updating: rev 3/3 files (100.00%)
360 360 picked tool 'python ../merge' for rev (binary False symlink False)
361 361 merging rev
362 362 my rev@59318016310c+ other rev@bdb19105162a ancestor rev@924404dff337
363 363 0 files updated, 2 files merged, 1 files removed, 0 files unresolved
364 364 (branch merge, don't forget to commit)
365 365 --------------
366 366 M b
367 367 --------------
368 368
369 369 created new head
370 370 --------------
371 371 test L:nc a b R:up a b W: - 14 merge b no ancestor
372 372 --------------
373 373 searching for copies back to rev 1
374 374 resolving manifests
375 375 overwrite None partial False
376 376 ancestor 924404dff337 local 86a2aa42fc76+ remote 8dbce441892a
377 377 a: remote is newer -> g
378 378 b: versions differ -> m
379 379 rev: versions differ -> m
380 380 preserving b for resolve of b
381 381 preserving rev for resolve of rev
382 update: a 1/3 files (33.33%)
382 updating: a 1/3 files (33.33%)
383 383 getting a
384 update: b 2/3 files (66.67%)
384 updating: b 2/3 files (66.67%)
385 385 picked tool 'python ../merge' for b (binary False symlink False)
386 386 merging b
387 387 my b@86a2aa42fc76+ other b@8dbce441892a ancestor b@000000000000
388 update: rev 3/3 files (100.00%)
388 updating: rev 3/3 files (100.00%)
389 389 picked tool 'python ../merge' for rev (binary False symlink False)
390 390 merging rev
391 391 my rev@86a2aa42fc76+ other rev@8dbce441892a ancestor rev@924404dff337
392 392 1 files updated, 2 files merged, 0 files removed, 0 files unresolved
393 393 (branch merge, don't forget to commit)
394 394 --------------
395 395 M a
396 396 M b
397 397 --------------
398 398
399 399 created new head
400 400 --------------
401 401 test L:up b R:nm a b W: - 15 merge b no ancestor, remove a
402 402 --------------
403 403 searching for copies back to rev 1
404 404 resolving manifests
405 405 overwrite None partial False
406 406 ancestor 924404dff337 local 59318016310c+ remote bdb19105162a
407 407 a: other deleted -> r
408 408 b: versions differ -> m
409 409 rev: versions differ -> m
410 410 preserving b for resolve of b
411 411 preserving rev for resolve of rev
412 update: a 1/3 files (33.33%)
412 updating: a 1/3 files (33.33%)
413 413 removing a
414 update: b 2/3 files (66.67%)
414 updating: b 2/3 files (66.67%)
415 415 picked tool 'python ../merge' for b (binary False symlink False)
416 416 merging b
417 417 my b@59318016310c+ other b@bdb19105162a ancestor b@000000000000
418 update: rev 3/3 files (100.00%)
418 updating: rev 3/3 files (100.00%)
419 419 picked tool 'python ../merge' for rev (binary False symlink False)
420 420 merging rev
421 421 my rev@59318016310c+ other rev@bdb19105162a ancestor rev@924404dff337
422 422 0 files updated, 2 files merged, 1 files removed, 0 files unresolved
423 423 (branch merge, don't forget to commit)
424 424 --------------
425 425 M b
426 426 --------------
427 427
428 428 created new head
429 429 --------------
430 430 test L:nc a b R:up a b W: - 16 get a, merge b no ancestor
431 431 --------------
432 432 searching for copies back to rev 1
433 433 resolving manifests
434 434 overwrite None partial False
435 435 ancestor 924404dff337 local 86a2aa42fc76+ remote 8dbce441892a
436 436 a: remote is newer -> g
437 437 b: versions differ -> m
438 438 rev: versions differ -> m
439 439 preserving b for resolve of b
440 440 preserving rev for resolve of rev
441 update: a 1/3 files (33.33%)
441 updating: a 1/3 files (33.33%)
442 442 getting a
443 update: b 2/3 files (66.67%)
443 updating: b 2/3 files (66.67%)
444 444 picked tool 'python ../merge' for b (binary False symlink False)
445 445 merging b
446 446 my b@86a2aa42fc76+ other b@8dbce441892a ancestor b@000000000000
447 update: rev 3/3 files (100.00%)
447 updating: rev 3/3 files (100.00%)
448 448 picked tool 'python ../merge' for rev (binary False symlink False)
449 449 merging rev
450 450 my rev@86a2aa42fc76+ other rev@8dbce441892a ancestor rev@924404dff337
451 451 1 files updated, 2 files merged, 0 files removed, 0 files unresolved
452 452 (branch merge, don't forget to commit)
453 453 --------------
454 454 M a
455 455 M b
456 456 --------------
457 457
458 458 created new head
459 459 --------------
460 460 test L:up a b R:nc a b W: - 17 keep a, merge b no ancestor
461 461 --------------
462 462 searching for copies back to rev 1
463 463 resolving manifests
464 464 overwrite None partial False
465 465 ancestor 924404dff337 local 0b76e65c8289+ remote 4ce40f5aca24
466 466 b: versions differ -> m
467 467 rev: versions differ -> m
468 468 preserving b for resolve of b
469 469 preserving rev for resolve of rev
470 update: b 1/2 files (50.00%)
470 updating: b 1/2 files (50.00%)
471 471 picked tool 'python ../merge' for b (binary False symlink False)
472 472 merging b
473 473 my b@0b76e65c8289+ other b@4ce40f5aca24 ancestor b@000000000000
474 update: rev 2/2 files (100.00%)
474 updating: rev 2/2 files (100.00%)
475 475 picked tool 'python ../merge' for rev (binary False symlink False)
476 476 merging rev
477 477 my rev@0b76e65c8289+ other rev@4ce40f5aca24 ancestor rev@924404dff337
478 478 0 files updated, 2 files merged, 0 files removed, 0 files unresolved
479 479 (branch merge, don't forget to commit)
480 480 --------------
481 481 M b
482 482 C a
483 483 --------------
484 484
485 485 created new head
486 486 --------------
487 487 test L:nm a b R:up a b W: - 18 merge b no ancestor
488 488 --------------
489 489 searching for copies back to rev 1
490 490 resolving manifests
491 491 overwrite None partial False
492 492 ancestor 924404dff337 local 02963e448370+ remote 8dbce441892a
493 493 b: versions differ -> m
494 494 rev: versions differ -> m
495 495 remote changed a which local deleted
496 496 use (c)hanged version or leave (d)eleted? c
497 497 a: prompt recreating -> g
498 498 preserving b for resolve of b
499 499 preserving rev for resolve of rev
500 update: a 1/3 files (33.33%)
500 updating: a 1/3 files (33.33%)
501 501 getting a
502 update: b 2/3 files (66.67%)
502 updating: b 2/3 files (66.67%)
503 503 picked tool 'python ../merge' for b (binary False symlink False)
504 504 merging b
505 505 my b@02963e448370+ other b@8dbce441892a ancestor b@000000000000
506 update: rev 3/3 files (100.00%)
506 updating: rev 3/3 files (100.00%)
507 507 picked tool 'python ../merge' for rev (binary False symlink False)
508 508 merging rev
509 509 my rev@02963e448370+ other rev@8dbce441892a ancestor rev@924404dff337
510 510 1 files updated, 2 files merged, 0 files removed, 0 files unresolved
511 511 (branch merge, don't forget to commit)
512 512 --------------
513 513 M a
514 514 M b
515 515 --------------
516 516
517 517 created new head
518 518 --------------
519 519 test L:up a b R:nm a b W: - 19 merge b no ancestor, prompt remove a
520 520 --------------
521 521 searching for copies back to rev 1
522 522 resolving manifests
523 523 overwrite None partial False
524 524 ancestor 924404dff337 local 0b76e65c8289+ remote bdb19105162a
525 525 local changed a which remote deleted
526 526 use (c)hanged version or (d)elete? c
527 527 a: prompt keep -> a
528 528 b: versions differ -> m
529 529 rev: versions differ -> m
530 530 preserving b for resolve of b
531 531 preserving rev for resolve of rev
532 update: a 1/3 files (33.33%)
533 update: b 2/3 files (66.67%)
532 updating: a 1/3 files (33.33%)
533 updating: b 2/3 files (66.67%)
534 534 picked tool 'python ../merge' for b (binary False symlink False)
535 535 merging b
536 536 my b@0b76e65c8289+ other b@bdb19105162a ancestor b@000000000000
537 update: rev 3/3 files (100.00%)
537 updating: rev 3/3 files (100.00%)
538 538 picked tool 'python ../merge' for rev (binary False symlink False)
539 539 merging rev
540 540 my rev@0b76e65c8289+ other rev@bdb19105162a ancestor rev@924404dff337
541 541 0 files updated, 2 files merged, 0 files removed, 0 files unresolved
542 542 (branch merge, don't forget to commit)
543 543 --------------
544 544 M b
545 545 C a
546 546 --------------
547 547
548 548 created new head
549 549 --------------
550 550 test L:up a R:um a b W: - 20 merge a and b to b, remove a
551 551 --------------
552 552 searching for copies back to rev 1
553 553 unmatched files in other:
554 554 b
555 555 all copies found (* = to merge, ! = divergent):
556 556 b -> a *
557 557 checking for directory renames
558 558 resolving manifests
559 559 overwrite None partial False
560 560 ancestor 924404dff337 local e300d1c794ec+ remote 49b6d8032493
561 561 rev: versions differ -> m
562 562 a: remote moved to b -> m
563 563 preserving a for resolve of b
564 564 preserving rev for resolve of rev
565 565 removing a
566 update: a 1/2 files (50.00%)
566 updating: a 1/2 files (50.00%)
567 567 picked tool 'python ../merge' for b (binary False symlink False)
568 568 merging a and b to b
569 569 my b@e300d1c794ec+ other b@49b6d8032493 ancestor a@924404dff337
570 update: rev 2/2 files (100.00%)
570 updating: rev 2/2 files (100.00%)
571 571 picked tool 'python ../merge' for rev (binary False symlink False)
572 572 merging rev
573 573 my rev@e300d1c794ec+ other rev@49b6d8032493 ancestor rev@924404dff337
574 574 0 files updated, 2 files merged, 0 files removed, 0 files unresolved
575 575 (branch merge, don't forget to commit)
576 576 --------------
577 577 M b
578 578 a
579 579 --------------
580 580
581 581 created new head
582 582 --------------
583 583 test L:um a b R:up a W: - 21 merge a and b to b
584 584 --------------
585 585 searching for copies back to rev 1
586 586 unmatched files in local:
587 587 b
588 588 all copies found (* = to merge, ! = divergent):
589 589 b -> a *
590 590 checking for directory renames
591 591 resolving manifests
592 592 overwrite None partial False
593 593 ancestor 924404dff337 local 62e7bf090eba+ remote f4db7e329e71
594 594 b: local copied/moved to a -> m
595 595 rev: versions differ -> m
596 596 preserving b for resolve of b
597 597 preserving rev for resolve of rev
598 update: b 1/2 files (50.00%)
598 updating: b 1/2 files (50.00%)
599 599 picked tool 'python ../merge' for b (binary False symlink False)
600 600 merging b and a to b
601 601 my b@62e7bf090eba+ other a@f4db7e329e71 ancestor a@924404dff337
602 update: rev 2/2 files (100.00%)
602 updating: rev 2/2 files (100.00%)
603 603 picked tool 'python ../merge' for rev (binary False symlink False)
604 604 merging rev
605 605 my rev@62e7bf090eba+ other rev@f4db7e329e71 ancestor rev@924404dff337
606 606 0 files updated, 2 files merged, 0 files removed, 0 files unresolved
607 607 (branch merge, don't forget to commit)
608 608 --------------
609 609 M b
610 610 a
611 611 --------------
612 612
613 613 created new head
614 614 --------------
615 615 test L:nm a b R:up a c W: - 23 get c, keep b
616 616 --------------
617 617 searching for copies back to rev 1
618 618 unmatched files in local:
619 619 b
620 620 unmatched files in other:
621 621 c
622 622 all copies found (* = to merge, ! = divergent):
623 623 b -> a *
624 624 checking for directory renames
625 625 resolving manifests
626 626 overwrite None partial False
627 627 ancestor 924404dff337 local 02963e448370+ remote 2b958612230f
628 628 b: local copied/moved to a -> m
629 629 rev: versions differ -> m
630 630 c: remote created -> g
631 631 preserving b for resolve of b
632 632 preserving rev for resolve of rev
633 update: b 1/3 files (33.33%)
633 updating: b 1/3 files (33.33%)
634 634 picked tool 'python ../merge' for b (binary False symlink False)
635 635 merging b and a to b
636 636 my b@02963e448370+ other a@2b958612230f ancestor a@924404dff337
637 637 premerge successful
638 update: c 2/3 files (66.67%)
638 updating: c 2/3 files (66.67%)
639 639 getting c
640 update: rev 3/3 files (100.00%)
640 updating: rev 3/3 files (100.00%)
641 641 picked tool 'python ../merge' for rev (binary False symlink False)
642 642 merging rev
643 643 my rev@02963e448370+ other rev@2b958612230f ancestor rev@924404dff337
644 644 1 files updated, 2 files merged, 0 files removed, 0 files unresolved
645 645 (branch merge, don't forget to commit)
646 646 --------------
647 647 M b
648 648 a
649 649 M c
650 650 --------------
651 651
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
General Comments 0
You need to be logged in to leave comments. Login now