##// END OF EJS Templates
mq: restore _branchtags() fast path (issue3223)...
Patrick Mezard -
r16101:20ad8f05 stable
parent child Browse files
Show More
@@ -1,3393 +1,3401 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 It may be desirable for mq changesets to be kept in the secret phase (see
42 42 :hg:`help phases`), which can be enabled with the following setting::
43 43
44 44 [mq]
45 45 secret = True
46 46
47 47 You will by default be managing a patch queue named "patches". You can
48 48 create other, independent patch queues with the :hg:`qqueue` command.
49 49 '''
50 50
51 51 from mercurial.i18n import _
52 52 from mercurial.node import bin, hex, short, nullid, nullrev
53 53 from mercurial.lock import release
54 54 from mercurial import commands, cmdutil, hg, scmutil, util, revset
55 55 from mercurial import repair, extensions, url, error, phases
56 56 from mercurial import patch as patchmod
57 57 import os, re, errno, shutil
58 58
59 59 commands.norepo += " qclone"
60 60
61 61 seriesopts = [('s', 'summary', None, _('print first line of patch header'))]
62 62
63 63 cmdtable = {}
64 64 command = cmdutil.command(cmdtable)
65 65
66 66 # Patch names looks like unix-file names.
67 67 # They must be joinable with queue directory and result in the patch path.
68 68 normname = util.normpath
69 69
70 70 class statusentry(object):
71 71 def __init__(self, node, name):
72 72 self.node, self.name = node, name
73 73 def __repr__(self):
74 74 return hex(self.node) + ':' + self.name
75 75
76 76 class patchheader(object):
77 77 def __init__(self, pf, plainmode=False):
78 78 def eatdiff(lines):
79 79 while lines:
80 80 l = lines[-1]
81 81 if (l.startswith("diff -") or
82 82 l.startswith("Index:") or
83 83 l.startswith("===========")):
84 84 del lines[-1]
85 85 else:
86 86 break
87 87 def eatempty(lines):
88 88 while lines:
89 89 if not lines[-1].strip():
90 90 del lines[-1]
91 91 else:
92 92 break
93 93
94 94 message = []
95 95 comments = []
96 96 user = None
97 97 date = None
98 98 parent = None
99 99 format = None
100 100 subject = None
101 101 branch = None
102 102 nodeid = None
103 103 diffstart = 0
104 104
105 105 for line in file(pf):
106 106 line = line.rstrip()
107 107 if (line.startswith('diff --git')
108 108 or (diffstart and line.startswith('+++ '))):
109 109 diffstart = 2
110 110 break
111 111 diffstart = 0 # reset
112 112 if line.startswith("--- "):
113 113 diffstart = 1
114 114 continue
115 115 elif format == "hgpatch":
116 116 # parse values when importing the result of an hg export
117 117 if line.startswith("# User "):
118 118 user = line[7:]
119 119 elif line.startswith("# Date "):
120 120 date = line[7:]
121 121 elif line.startswith("# Parent "):
122 122 parent = line[9:].lstrip()
123 123 elif line.startswith("# Branch "):
124 124 branch = line[9:]
125 125 elif line.startswith("# Node ID "):
126 126 nodeid = line[10:]
127 127 elif not line.startswith("# ") and line:
128 128 message.append(line)
129 129 format = None
130 130 elif line == '# HG changeset patch':
131 131 message = []
132 132 format = "hgpatch"
133 133 elif (format != "tagdone" and (line.startswith("Subject: ") or
134 134 line.startswith("subject: "))):
135 135 subject = line[9:]
136 136 format = "tag"
137 137 elif (format != "tagdone" and (line.startswith("From: ") or
138 138 line.startswith("from: "))):
139 139 user = line[6:]
140 140 format = "tag"
141 141 elif (format != "tagdone" and (line.startswith("Date: ") or
142 142 line.startswith("date: "))):
143 143 date = line[6:]
144 144 format = "tag"
145 145 elif format == "tag" and line == "":
146 146 # when looking for tags (subject: from: etc) they
147 147 # end once you find a blank line in the source
148 148 format = "tagdone"
149 149 elif message or line:
150 150 message.append(line)
151 151 comments.append(line)
152 152
153 153 eatdiff(message)
154 154 eatdiff(comments)
155 155 # Remember the exact starting line of the patch diffs before consuming
156 156 # empty lines, for external use by TortoiseHg and others
157 157 self.diffstartline = len(comments)
158 158 eatempty(message)
159 159 eatempty(comments)
160 160
161 161 # make sure message isn't empty
162 162 if format and format.startswith("tag") and subject:
163 163 message.insert(0, "")
164 164 message.insert(0, subject)
165 165
166 166 self.message = message
167 167 self.comments = comments
168 168 self.user = user
169 169 self.date = date
170 170 self.parent = parent
171 171 # nodeid and branch are for external use by TortoiseHg and others
172 172 self.nodeid = nodeid
173 173 self.branch = branch
174 174 self.haspatch = diffstart > 1
175 175 self.plainmode = plainmode
176 176
177 177 def setuser(self, user):
178 178 if not self.updateheader(['From: ', '# User '], user):
179 179 try:
180 180 patchheaderat = self.comments.index('# HG changeset patch')
181 181 self.comments.insert(patchheaderat + 1, '# User ' + user)
182 182 except ValueError:
183 183 if self.plainmode or self._hasheader(['Date: ']):
184 184 self.comments = ['From: ' + user] + self.comments
185 185 else:
186 186 tmp = ['# HG changeset patch', '# User ' + user, '']
187 187 self.comments = tmp + self.comments
188 188 self.user = user
189 189
190 190 def setdate(self, date):
191 191 if not self.updateheader(['Date: ', '# Date '], date):
192 192 try:
193 193 patchheaderat = self.comments.index('# HG changeset patch')
194 194 self.comments.insert(patchheaderat + 1, '# Date ' + date)
195 195 except ValueError:
196 196 if self.plainmode or self._hasheader(['From: ']):
197 197 self.comments = ['Date: ' + date] + self.comments
198 198 else:
199 199 tmp = ['# HG changeset patch', '# Date ' + date, '']
200 200 self.comments = tmp + self.comments
201 201 self.date = date
202 202
203 203 def setparent(self, parent):
204 204 if not self.updateheader(['# Parent '], parent):
205 205 try:
206 206 patchheaderat = self.comments.index('# HG changeset patch')
207 207 self.comments.insert(patchheaderat + 1, '# Parent ' + parent)
208 208 except ValueError:
209 209 pass
210 210 self.parent = parent
211 211
212 212 def setmessage(self, message):
213 213 if self.comments:
214 214 self._delmsg()
215 215 self.message = [message]
216 216 self.comments += self.message
217 217
218 218 def updateheader(self, prefixes, new):
219 219 '''Update all references to a field in the patch header.
220 220 Return whether the field is present.'''
221 221 res = False
222 222 for prefix in prefixes:
223 223 for i in xrange(len(self.comments)):
224 224 if self.comments[i].startswith(prefix):
225 225 self.comments[i] = prefix + new
226 226 res = True
227 227 break
228 228 return res
229 229
230 230 def _hasheader(self, prefixes):
231 231 '''Check if a header starts with any of the given prefixes.'''
232 232 for prefix in prefixes:
233 233 for comment in self.comments:
234 234 if comment.startswith(prefix):
235 235 return True
236 236 return False
237 237
238 238 def __str__(self):
239 239 if not self.comments:
240 240 return ''
241 241 return '\n'.join(self.comments) + '\n\n'
242 242
243 243 def _delmsg(self):
244 244 '''Remove existing message, keeping the rest of the comments fields.
245 245 If comments contains 'subject: ', message will prepend
246 246 the field and a blank line.'''
247 247 if self.message:
248 248 subj = 'subject: ' + self.message[0].lower()
249 249 for i in xrange(len(self.comments)):
250 250 if subj == self.comments[i].lower():
251 251 del self.comments[i]
252 252 self.message = self.message[2:]
253 253 break
254 254 ci = 0
255 255 for mi in self.message:
256 256 while mi != self.comments[ci]:
257 257 ci += 1
258 258 del self.comments[ci]
259 259
260 260 def secretcommit(repo, phase, *args, **kwargs):
261 261 """helper dedicated to ensure a commit are secret
262 262
263 263 It should be used instead of repo.commit inside the mq source
264 264 """
265 265 if phase is None:
266 266 if repo.ui.configbool('mq', 'secret', False):
267 267 phase = phases.secret
268 268 if phase is not None:
269 269 backup = repo.ui.backupconfig('phases', 'new-commit')
270 # Marking the repository as committing an mq patch can be used
271 # to optimize operations like _branchtags().
272 repo._committingpatch = True
270 273 try:
271 274 if phase is not None:
272 275 repo.ui.setconfig('phases', 'new-commit', phase)
273 276 return repo.commit(*args, **kwargs)
274 277 finally:
278 repo._committingpatch = False
275 279 if phase is not None:
276 280 repo.ui.restoreconfig(backup)
277 281
278 282 class queue(object):
279 283 def __init__(self, ui, path, patchdir=None):
280 284 self.basepath = path
281 285 try:
282 286 fh = open(os.path.join(path, 'patches.queue'))
283 287 cur = fh.read().rstrip()
284 288 fh.close()
285 289 if not cur:
286 290 curpath = os.path.join(path, 'patches')
287 291 else:
288 292 curpath = os.path.join(path, 'patches-' + cur)
289 293 except IOError:
290 294 curpath = os.path.join(path, 'patches')
291 295 self.path = patchdir or curpath
292 296 self.opener = scmutil.opener(self.path)
293 297 self.ui = ui
294 298 self.applieddirty = False
295 299 self.seriesdirty = False
296 300 self.added = []
297 301 self.seriespath = "series"
298 302 self.statuspath = "status"
299 303 self.guardspath = "guards"
300 304 self.activeguards = None
301 305 self.guardsdirty = False
302 306 # Handle mq.git as a bool with extended values
303 307 try:
304 308 gitmode = ui.configbool('mq', 'git', None)
305 309 if gitmode is None:
306 310 raise error.ConfigError()
307 311 self.gitmode = gitmode and 'yes' or 'no'
308 312 except error.ConfigError:
309 313 self.gitmode = ui.config('mq', 'git', 'auto').lower()
310 314 self.plainmode = ui.configbool('mq', 'plain', False)
311 315
312 316 @util.propertycache
313 317 def applied(self):
314 318 def parselines(lines):
315 319 for l in lines:
316 320 entry = l.split(':', 1)
317 321 if len(entry) > 1:
318 322 n, name = entry
319 323 yield statusentry(bin(n), name)
320 324 elif l.strip():
321 325 self.ui.warn(_('malformated mq status line: %s\n') % entry)
322 326 # else we ignore empty lines
323 327 try:
324 328 lines = self.opener.read(self.statuspath).splitlines()
325 329 return list(parselines(lines))
326 330 except IOError, e:
327 331 if e.errno == errno.ENOENT:
328 332 return []
329 333 raise
330 334
331 335 @util.propertycache
332 336 def fullseries(self):
333 337 try:
334 338 return self.opener.read(self.seriespath).splitlines()
335 339 except IOError, e:
336 340 if e.errno == errno.ENOENT:
337 341 return []
338 342 raise
339 343
340 344 @util.propertycache
341 345 def series(self):
342 346 self.parseseries()
343 347 return self.series
344 348
345 349 @util.propertycache
346 350 def seriesguards(self):
347 351 self.parseseries()
348 352 return self.seriesguards
349 353
350 354 def invalidate(self):
351 355 for a in 'applied fullseries series seriesguards'.split():
352 356 if a in self.__dict__:
353 357 delattr(self, a)
354 358 self.applieddirty = False
355 359 self.seriesdirty = False
356 360 self.guardsdirty = False
357 361 self.activeguards = None
358 362
359 363 def diffopts(self, opts={}, patchfn=None):
360 364 diffopts = patchmod.diffopts(self.ui, opts)
361 365 if self.gitmode == 'auto':
362 366 diffopts.upgrade = True
363 367 elif self.gitmode == 'keep':
364 368 pass
365 369 elif self.gitmode in ('yes', 'no'):
366 370 diffopts.git = self.gitmode == 'yes'
367 371 else:
368 372 raise util.Abort(_('mq.git option can be auto/keep/yes/no'
369 373 ' got %s') % self.gitmode)
370 374 if patchfn:
371 375 diffopts = self.patchopts(diffopts, patchfn)
372 376 return diffopts
373 377
374 378 def patchopts(self, diffopts, *patches):
375 379 """Return a copy of input diff options with git set to true if
376 380 referenced patch is a git patch and should be preserved as such.
377 381 """
378 382 diffopts = diffopts.copy()
379 383 if not diffopts.git and self.gitmode == 'keep':
380 384 for patchfn in patches:
381 385 patchf = self.opener(patchfn, 'r')
382 386 # if the patch was a git patch, refresh it as a git patch
383 387 for line in patchf:
384 388 if line.startswith('diff --git'):
385 389 diffopts.git = True
386 390 break
387 391 patchf.close()
388 392 return diffopts
389 393
390 394 def join(self, *p):
391 395 return os.path.join(self.path, *p)
392 396
393 397 def findseries(self, patch):
394 398 def matchpatch(l):
395 399 l = l.split('#', 1)[0]
396 400 return l.strip() == patch
397 401 for index, l in enumerate(self.fullseries):
398 402 if matchpatch(l):
399 403 return index
400 404 return None
401 405
402 406 guard_re = re.compile(r'\s?#([-+][^-+# \t\r\n\f][^# \t\r\n\f]*)')
403 407
404 408 def parseseries(self):
405 409 self.series = []
406 410 self.seriesguards = []
407 411 for l in self.fullseries:
408 412 h = l.find('#')
409 413 if h == -1:
410 414 patch = l
411 415 comment = ''
412 416 elif h == 0:
413 417 continue
414 418 else:
415 419 patch = l[:h]
416 420 comment = l[h:]
417 421 patch = patch.strip()
418 422 if patch:
419 423 if patch in self.series:
420 424 raise util.Abort(_('%s appears more than once in %s') %
421 425 (patch, self.join(self.seriespath)))
422 426 self.series.append(patch)
423 427 self.seriesguards.append(self.guard_re.findall(comment))
424 428
425 429 def checkguard(self, guard):
426 430 if not guard:
427 431 return _('guard cannot be an empty string')
428 432 bad_chars = '# \t\r\n\f'
429 433 first = guard[0]
430 434 if first in '-+':
431 435 return (_('guard %r starts with invalid character: %r') %
432 436 (guard, first))
433 437 for c in bad_chars:
434 438 if c in guard:
435 439 return _('invalid character in guard %r: %r') % (guard, c)
436 440
437 441 def setactive(self, guards):
438 442 for guard in guards:
439 443 bad = self.checkguard(guard)
440 444 if bad:
441 445 raise util.Abort(bad)
442 446 guards = sorted(set(guards))
443 447 self.ui.debug('active guards: %s\n' % ' '.join(guards))
444 448 self.activeguards = guards
445 449 self.guardsdirty = True
446 450
447 451 def active(self):
448 452 if self.activeguards is None:
449 453 self.activeguards = []
450 454 try:
451 455 guards = self.opener.read(self.guardspath).split()
452 456 except IOError, err:
453 457 if err.errno != errno.ENOENT:
454 458 raise
455 459 guards = []
456 460 for i, guard in enumerate(guards):
457 461 bad = self.checkguard(guard)
458 462 if bad:
459 463 self.ui.warn('%s:%d: %s\n' %
460 464 (self.join(self.guardspath), i + 1, bad))
461 465 else:
462 466 self.activeguards.append(guard)
463 467 return self.activeguards
464 468
465 469 def setguards(self, idx, guards):
466 470 for g in guards:
467 471 if len(g) < 2:
468 472 raise util.Abort(_('guard %r too short') % g)
469 473 if g[0] not in '-+':
470 474 raise util.Abort(_('guard %r starts with invalid char') % g)
471 475 bad = self.checkguard(g[1:])
472 476 if bad:
473 477 raise util.Abort(bad)
474 478 drop = self.guard_re.sub('', self.fullseries[idx])
475 479 self.fullseries[idx] = drop + ''.join([' #' + g for g in guards])
476 480 self.parseseries()
477 481 self.seriesdirty = True
478 482
479 483 def pushable(self, idx):
480 484 if isinstance(idx, str):
481 485 idx = self.series.index(idx)
482 486 patchguards = self.seriesguards[idx]
483 487 if not patchguards:
484 488 return True, None
485 489 guards = self.active()
486 490 exactneg = [g for g in patchguards if g[0] == '-' and g[1:] in guards]
487 491 if exactneg:
488 492 return False, repr(exactneg[0])
489 493 pos = [g for g in patchguards if g[0] == '+']
490 494 exactpos = [g for g in pos if g[1:] in guards]
491 495 if pos:
492 496 if exactpos:
493 497 return True, repr(exactpos[0])
494 498 return False, ' '.join(map(repr, pos))
495 499 return True, ''
496 500
497 501 def explainpushable(self, idx, all_patches=False):
498 502 write = all_patches and self.ui.write or self.ui.warn
499 503 if all_patches or self.ui.verbose:
500 504 if isinstance(idx, str):
501 505 idx = self.series.index(idx)
502 506 pushable, why = self.pushable(idx)
503 507 if all_patches and pushable:
504 508 if why is None:
505 509 write(_('allowing %s - no guards in effect\n') %
506 510 self.series[idx])
507 511 else:
508 512 if not why:
509 513 write(_('allowing %s - no matching negative guards\n') %
510 514 self.series[idx])
511 515 else:
512 516 write(_('allowing %s - guarded by %s\n') %
513 517 (self.series[idx], why))
514 518 if not pushable:
515 519 if why:
516 520 write(_('skipping %s - guarded by %s\n') %
517 521 (self.series[idx], why))
518 522 else:
519 523 write(_('skipping %s - no matching guards\n') %
520 524 self.series[idx])
521 525
522 526 def savedirty(self):
523 527 def writelist(items, path):
524 528 fp = self.opener(path, 'w')
525 529 for i in items:
526 530 fp.write("%s\n" % i)
527 531 fp.close()
528 532 if self.applieddirty:
529 533 writelist(map(str, self.applied), self.statuspath)
530 534 self.applieddirty = False
531 535 if self.seriesdirty:
532 536 writelist(self.fullseries, self.seriespath)
533 537 self.seriesdirty = False
534 538 if self.guardsdirty:
535 539 writelist(self.activeguards, self.guardspath)
536 540 self.guardsdirty = False
537 541 if self.added:
538 542 qrepo = self.qrepo()
539 543 if qrepo:
540 544 qrepo[None].add(f for f in self.added if f not in qrepo[None])
541 545 self.added = []
542 546
543 547 def removeundo(self, repo):
544 548 undo = repo.sjoin('undo')
545 549 if not os.path.exists(undo):
546 550 return
547 551 try:
548 552 os.unlink(undo)
549 553 except OSError, inst:
550 554 self.ui.warn(_('error removing undo: %s\n') % str(inst))
551 555
552 556 def printdiff(self, repo, diffopts, node1, node2=None, files=None,
553 557 fp=None, changes=None, opts={}):
554 558 stat = opts.get('stat')
555 559 m = scmutil.match(repo[node1], files, opts)
556 560 cmdutil.diffordiffstat(self.ui, repo, diffopts, node1, node2, m,
557 561 changes, stat, fp)
558 562
559 563 def mergeone(self, repo, mergeq, head, patch, rev, diffopts):
560 564 # first try just applying the patch
561 565 (err, n) = self.apply(repo, [patch], update_status=False,
562 566 strict=True, merge=rev)
563 567
564 568 if err == 0:
565 569 return (err, n)
566 570
567 571 if n is None:
568 572 raise util.Abort(_("apply failed for patch %s") % patch)
569 573
570 574 self.ui.warn(_("patch didn't work out, merging %s\n") % patch)
571 575
572 576 # apply failed, strip away that rev and merge.
573 577 hg.clean(repo, head)
574 578 self.strip(repo, [n], update=False, backup='strip')
575 579
576 580 ctx = repo[rev]
577 581 ret = hg.merge(repo, rev)
578 582 if ret:
579 583 raise util.Abort(_("update returned %d") % ret)
580 584 n = secretcommit(repo, None, ctx.description(), ctx.user(), force=True)
581 585 if n is None:
582 586 raise util.Abort(_("repo commit failed"))
583 587 try:
584 588 ph = patchheader(mergeq.join(patch), self.plainmode)
585 589 except:
586 590 raise util.Abort(_("unable to read %s") % patch)
587 591
588 592 diffopts = self.patchopts(diffopts, patch)
589 593 patchf = self.opener(patch, "w")
590 594 comments = str(ph)
591 595 if comments:
592 596 patchf.write(comments)
593 597 self.printdiff(repo, diffopts, head, n, fp=patchf)
594 598 patchf.close()
595 599 self.removeundo(repo)
596 600 return (0, n)
597 601
598 602 def qparents(self, repo, rev=None):
599 603 if rev is None:
600 604 (p1, p2) = repo.dirstate.parents()
601 605 if p2 == nullid:
602 606 return p1
603 607 if not self.applied:
604 608 return None
605 609 return self.applied[-1].node
606 610 p1, p2 = repo.changelog.parents(rev)
607 611 if p2 != nullid and p2 in [x.node for x in self.applied]:
608 612 return p2
609 613 return p1
610 614
611 615 def mergepatch(self, repo, mergeq, series, diffopts):
612 616 if not self.applied:
613 617 # each of the patches merged in will have two parents. This
614 618 # can confuse the qrefresh, qdiff, and strip code because it
615 619 # needs to know which parent is actually in the patch queue.
616 620 # so, we insert a merge marker with only one parent. This way
617 621 # the first patch in the queue is never a merge patch
618 622 #
619 623 pname = ".hg.patches.merge.marker"
620 624 n = secretcommit(repo, None, '[mq]: merge marker', force=True)
621 625 self.removeundo(repo)
622 626 self.applied.append(statusentry(n, pname))
623 627 self.applieddirty = True
624 628
625 629 head = self.qparents(repo)
626 630
627 631 for patch in series:
628 632 patch = mergeq.lookup(patch, strict=True)
629 633 if not patch:
630 634 self.ui.warn(_("patch %s does not exist\n") % patch)
631 635 return (1, None)
632 636 pushable, reason = self.pushable(patch)
633 637 if not pushable:
634 638 self.explainpushable(patch, all_patches=True)
635 639 continue
636 640 info = mergeq.isapplied(patch)
637 641 if not info:
638 642 self.ui.warn(_("patch %s is not applied\n") % patch)
639 643 return (1, None)
640 644 rev = info[1]
641 645 err, head = self.mergeone(repo, mergeq, head, patch, rev, diffopts)
642 646 if head:
643 647 self.applied.append(statusentry(head, patch))
644 648 self.applieddirty = True
645 649 if err:
646 650 return (err, head)
647 651 self.savedirty()
648 652 return (0, head)
649 653
650 654 def patch(self, repo, patchfile):
651 655 '''Apply patchfile to the working directory.
652 656 patchfile: name of patch file'''
653 657 files = set()
654 658 try:
655 659 fuzz = patchmod.patch(self.ui, repo, patchfile, strip=1,
656 660 files=files, eolmode=None)
657 661 return (True, list(files), fuzz)
658 662 except Exception, inst:
659 663 self.ui.note(str(inst) + '\n')
660 664 if not self.ui.verbose:
661 665 self.ui.warn(_("patch failed, unable to continue (try -v)\n"))
662 666 self.ui.traceback()
663 667 return (False, list(files), False)
664 668
665 669 def apply(self, repo, series, list=False, update_status=True,
666 670 strict=False, patchdir=None, merge=None, all_files=None):
667 671 wlock = lock = tr = None
668 672 try:
669 673 wlock = repo.wlock()
670 674 lock = repo.lock()
671 675 tr = repo.transaction("qpush")
672 676 try:
673 677 ret = self._apply(repo, series, list, update_status,
674 678 strict, patchdir, merge, all_files=all_files)
675 679 tr.close()
676 680 self.savedirty()
677 681 return ret
678 682 except:
679 683 try:
680 684 tr.abort()
681 685 finally:
682 686 repo.invalidate()
683 687 repo.dirstate.invalidate()
684 688 self.invalidate()
685 689 raise
686 690 finally:
687 691 release(tr, lock, wlock)
688 692 self.removeundo(repo)
689 693
690 694 def _apply(self, repo, series, list=False, update_status=True,
691 695 strict=False, patchdir=None, merge=None, all_files=None):
692 696 '''returns (error, hash)
693 697 error = 1 for unable to read, 2 for patch failed, 3 for patch fuzz'''
694 698 # TODO unify with commands.py
695 699 if not patchdir:
696 700 patchdir = self.path
697 701 err = 0
698 702 n = None
699 703 for patchname in series:
700 704 pushable, reason = self.pushable(patchname)
701 705 if not pushable:
702 706 self.explainpushable(patchname, all_patches=True)
703 707 continue
704 708 self.ui.status(_("applying %s\n") % patchname)
705 709 pf = os.path.join(patchdir, patchname)
706 710
707 711 try:
708 712 ph = patchheader(self.join(patchname), self.plainmode)
709 713 except IOError:
710 714 self.ui.warn(_("unable to read %s\n") % patchname)
711 715 err = 1
712 716 break
713 717
714 718 message = ph.message
715 719 if not message:
716 720 # The commit message should not be translated
717 721 message = "imported patch %s\n" % patchname
718 722 else:
719 723 if list:
720 724 # The commit message should not be translated
721 725 message.append("\nimported patch %s" % patchname)
722 726 message = '\n'.join(message)
723 727
724 728 if ph.haspatch:
725 729 (patcherr, files, fuzz) = self.patch(repo, pf)
726 730 if all_files is not None:
727 731 all_files.update(files)
728 732 patcherr = not patcherr
729 733 else:
730 734 self.ui.warn(_("patch %s is empty\n") % patchname)
731 735 patcherr, files, fuzz = 0, [], 0
732 736
733 737 if merge and files:
734 738 # Mark as removed/merged and update dirstate parent info
735 739 removed = []
736 740 merged = []
737 741 for f in files:
738 742 if os.path.lexists(repo.wjoin(f)):
739 743 merged.append(f)
740 744 else:
741 745 removed.append(f)
742 746 for f in removed:
743 747 repo.dirstate.remove(f)
744 748 for f in merged:
745 749 repo.dirstate.merge(f)
746 750 p1, p2 = repo.dirstate.parents()
747 751 repo.dirstate.setparents(p1, merge)
748 752
749 753 match = scmutil.matchfiles(repo, files or [])
750 754 oldtip = repo['tip']
751 755 n = secretcommit(repo, None, message, ph.user, ph.date, match=match,
752 756 force=True)
753 757 if repo['tip'] == oldtip:
754 758 raise util.Abort(_("qpush exactly duplicates child changeset"))
755 759 if n is None:
756 760 raise util.Abort(_("repository commit failed"))
757 761
758 762 if update_status:
759 763 self.applied.append(statusentry(n, patchname))
760 764
761 765 if patcherr:
762 766 self.ui.warn(_("patch failed, rejects left in working dir\n"))
763 767 err = 2
764 768 break
765 769
766 770 if fuzz and strict:
767 771 self.ui.warn(_("fuzz found when applying patch, stopping\n"))
768 772 err = 3
769 773 break
770 774 return (err, n)
771 775
772 776 def _cleanup(self, patches, numrevs, keep=False):
773 777 if not keep:
774 778 r = self.qrepo()
775 779 if r:
776 780 r[None].forget(patches)
777 781 for p in patches:
778 782 os.unlink(self.join(p))
779 783
780 784 qfinished = []
781 785 if numrevs:
782 786 qfinished = self.applied[:numrevs]
783 787 del self.applied[:numrevs]
784 788 self.applieddirty = True
785 789
786 790 unknown = []
787 791
788 792 for (i, p) in sorted([(self.findseries(p), p) for p in patches],
789 793 reverse=True):
790 794 if i is not None:
791 795 del self.fullseries[i]
792 796 else:
793 797 unknown.append(p)
794 798
795 799 if unknown:
796 800 if numrevs:
797 801 rev = dict((entry.name, entry.node) for entry in qfinished)
798 802 for p in unknown:
799 803 msg = _('revision %s refers to unknown patches: %s\n')
800 804 self.ui.warn(msg % (short(rev[p]), p))
801 805 else:
802 806 msg = _('unknown patches: %s\n')
803 807 raise util.Abort(''.join(msg % p for p in unknown))
804 808
805 809 self.parseseries()
806 810 self.seriesdirty = True
807 811 return [entry.node for entry in qfinished]
808 812
809 813 def _revpatches(self, repo, revs):
810 814 firstrev = repo[self.applied[0].node].rev()
811 815 patches = []
812 816 for i, rev in enumerate(revs):
813 817
814 818 if rev < firstrev:
815 819 raise util.Abort(_('revision %d is not managed') % rev)
816 820
817 821 ctx = repo[rev]
818 822 base = self.applied[i].node
819 823 if ctx.node() != base:
820 824 msg = _('cannot delete revision %d above applied patches')
821 825 raise util.Abort(msg % rev)
822 826
823 827 patch = self.applied[i].name
824 828 for fmt in ('[mq]: %s', 'imported patch %s'):
825 829 if ctx.description() == fmt % patch:
826 830 msg = _('patch %s finalized without changeset message\n')
827 831 repo.ui.status(msg % patch)
828 832 break
829 833
830 834 patches.append(patch)
831 835 return patches
832 836
833 837 def finish(self, repo, revs):
834 838 # Manually trigger phase computation to ensure phasedefaults is
835 839 # executed before we remove the patches.
836 840 repo._phaserev
837 841 patches = self._revpatches(repo, sorted(revs))
838 842 qfinished = self._cleanup(patches, len(patches))
839 843 if qfinished and repo.ui.configbool('mq', 'secret', False):
840 844 # only use this logic when the secret option is added
841 845 oldqbase = repo[qfinished[0]]
842 846 if oldqbase.p1().phase() < phases.secret:
843 847 phases.advanceboundary(repo, phases.draft, qfinished)
844 848
845 849 def delete(self, repo, patches, opts):
846 850 if not patches and not opts.get('rev'):
847 851 raise util.Abort(_('qdelete requires at least one revision or '
848 852 'patch name'))
849 853
850 854 realpatches = []
851 855 for patch in patches:
852 856 patch = self.lookup(patch, strict=True)
853 857 info = self.isapplied(patch)
854 858 if info:
855 859 raise util.Abort(_("cannot delete applied patch %s") % patch)
856 860 if patch not in self.series:
857 861 raise util.Abort(_("patch %s not in series file") % patch)
858 862 if patch not in realpatches:
859 863 realpatches.append(patch)
860 864
861 865 numrevs = 0
862 866 if opts.get('rev'):
863 867 if not self.applied:
864 868 raise util.Abort(_('no patches applied'))
865 869 revs = scmutil.revrange(repo, opts.get('rev'))
866 870 if len(revs) > 1 and revs[0] > revs[1]:
867 871 revs.reverse()
868 872 revpatches = self._revpatches(repo, revs)
869 873 realpatches += revpatches
870 874 numrevs = len(revpatches)
871 875
872 876 self._cleanup(realpatches, numrevs, opts.get('keep'))
873 877
874 878 def checktoppatch(self, repo):
875 879 if self.applied:
876 880 top = self.applied[-1].node
877 881 patch = self.applied[-1].name
878 882 pp = repo.dirstate.parents()
879 883 if top not in pp:
880 884 raise util.Abort(_("working directory revision is not qtip"))
881 885 return top, patch
882 886 return None, None
883 887
884 888 def checksubstate(self, repo):
885 889 '''return list of subrepos at a different revision than substate.
886 890 Abort if any subrepos have uncommitted changes.'''
887 891 inclsubs = []
888 892 wctx = repo[None]
889 893 for s in wctx.substate:
890 894 if wctx.sub(s).dirty(True):
891 895 raise util.Abort(
892 896 _("uncommitted changes in subrepository %s") % s)
893 897 elif wctx.sub(s).dirty():
894 898 inclsubs.append(s)
895 899 return inclsubs
896 900
897 901 def localchangesfound(self, refresh=True):
898 902 if refresh:
899 903 raise util.Abort(_("local changes found, refresh first"))
900 904 else:
901 905 raise util.Abort(_("local changes found"))
902 906
903 907 def checklocalchanges(self, repo, force=False, refresh=True):
904 908 m, a, r, d = repo.status()[:4]
905 909 if (m or a or r or d) and not force:
906 910 self.localchangesfound(refresh)
907 911 return m, a, r, d
908 912
909 913 _reserved = ('series', 'status', 'guards', '.', '..')
910 914 def checkreservedname(self, name):
911 915 if name in self._reserved:
912 916 raise util.Abort(_('"%s" cannot be used as the name of a patch')
913 917 % name)
914 918 for prefix in ('.hg', '.mq'):
915 919 if name.startswith(prefix):
916 920 raise util.Abort(_('patch name cannot begin with "%s"')
917 921 % prefix)
918 922 for c in ('#', ':'):
919 923 if c in name:
920 924 raise util.Abort(_('"%s" cannot be used in the name of a patch')
921 925 % c)
922 926
923 927 def checkpatchname(self, name, force=False):
924 928 self.checkreservedname(name)
925 929 if not force and os.path.exists(self.join(name)):
926 930 if os.path.isdir(self.join(name)):
927 931 raise util.Abort(_('"%s" already exists as a directory')
928 932 % name)
929 933 else:
930 934 raise util.Abort(_('patch "%s" already exists') % name)
931 935
932 936 def new(self, repo, patchfn, *pats, **opts):
933 937 """options:
934 938 msg: a string or a no-argument function returning a string
935 939 """
936 940 msg = opts.get('msg')
937 941 user = opts.get('user')
938 942 date = opts.get('date')
939 943 if date:
940 944 date = util.parsedate(date)
941 945 diffopts = self.diffopts({'git': opts.get('git')})
942 946 if opts.get('checkname', True):
943 947 self.checkpatchname(patchfn)
944 948 inclsubs = self.checksubstate(repo)
945 949 if inclsubs:
946 950 inclsubs.append('.hgsubstate')
947 951 if opts.get('include') or opts.get('exclude') or pats:
948 952 if inclsubs:
949 953 pats = list(pats or []) + inclsubs
950 954 match = scmutil.match(repo[None], pats, opts)
951 955 # detect missing files in pats
952 956 def badfn(f, msg):
953 957 if f != '.hgsubstate': # .hgsubstate is auto-created
954 958 raise util.Abort('%s: %s' % (f, msg))
955 959 match.bad = badfn
956 960 m, a, r, d = repo.status(match=match)[:4]
957 961 else:
958 962 m, a, r, d = self.checklocalchanges(repo, force=True)
959 963 match = scmutil.matchfiles(repo, m + a + r + inclsubs)
960 964 if len(repo[None].parents()) > 1:
961 965 raise util.Abort(_('cannot manage merge changesets'))
962 966 commitfiles = m + a + r
963 967 self.checktoppatch(repo)
964 968 insert = self.fullseriesend()
965 969 wlock = repo.wlock()
966 970 try:
967 971 try:
968 972 # if patch file write fails, abort early
969 973 p = self.opener(patchfn, "w")
970 974 except IOError, e:
971 975 raise util.Abort(_('cannot write patch "%s": %s')
972 976 % (patchfn, e.strerror))
973 977 try:
974 978 if self.plainmode:
975 979 if user:
976 980 p.write("From: " + user + "\n")
977 981 if not date:
978 982 p.write("\n")
979 983 if date:
980 984 p.write("Date: %d %d\n\n" % date)
981 985 else:
982 986 p.write("# HG changeset patch\n")
983 987 p.write("# Parent "
984 988 + hex(repo[None].p1().node()) + "\n")
985 989 if user:
986 990 p.write("# User " + user + "\n")
987 991 if date:
988 992 p.write("# Date %s %s\n\n" % date)
989 993 if util.safehasattr(msg, '__call__'):
990 994 msg = msg()
991 995 commitmsg = msg and msg or ("[mq]: %s" % patchfn)
992 996 n = secretcommit(repo, None, commitmsg, user, date, match=match,
993 997 force=True)
994 998 if n is None:
995 999 raise util.Abort(_("repo commit failed"))
996 1000 try:
997 1001 self.fullseries[insert:insert] = [patchfn]
998 1002 self.applied.append(statusentry(n, patchfn))
999 1003 self.parseseries()
1000 1004 self.seriesdirty = True
1001 1005 self.applieddirty = True
1002 1006 if msg:
1003 1007 msg = msg + "\n\n"
1004 1008 p.write(msg)
1005 1009 if commitfiles:
1006 1010 parent = self.qparents(repo, n)
1007 1011 chunks = patchmod.diff(repo, node1=parent, node2=n,
1008 1012 match=match, opts=diffopts)
1009 1013 for chunk in chunks:
1010 1014 p.write(chunk)
1011 1015 p.close()
1012 1016 r = self.qrepo()
1013 1017 if r:
1014 1018 r[None].add([patchfn])
1015 1019 except:
1016 1020 repo.rollback()
1017 1021 raise
1018 1022 except Exception:
1019 1023 patchpath = self.join(patchfn)
1020 1024 try:
1021 1025 os.unlink(patchpath)
1022 1026 except:
1023 1027 self.ui.warn(_('error unlinking %s\n') % patchpath)
1024 1028 raise
1025 1029 self.removeundo(repo)
1026 1030 finally:
1027 1031 release(wlock)
1028 1032
1029 1033 def strip(self, repo, revs, update=True, backup="all", force=None):
1030 1034 wlock = lock = None
1031 1035 try:
1032 1036 wlock = repo.wlock()
1033 1037 lock = repo.lock()
1034 1038
1035 1039 if update:
1036 1040 self.checklocalchanges(repo, force=force, refresh=False)
1037 1041 urev = self.qparents(repo, revs[0])
1038 1042 hg.clean(repo, urev)
1039 1043 repo.dirstate.write()
1040 1044
1041 1045 self.removeundo(repo)
1042 1046 for rev in revs:
1043 1047 repair.strip(self.ui, repo, rev, backup)
1044 1048 # strip may have unbundled a set of backed up revisions after
1045 1049 # the actual strip
1046 1050 self.removeundo(repo)
1047 1051 finally:
1048 1052 release(lock, wlock)
1049 1053
1050 1054 def isapplied(self, patch):
1051 1055 """returns (index, rev, patch)"""
1052 1056 for i, a in enumerate(self.applied):
1053 1057 if a.name == patch:
1054 1058 return (i, a.node, a.name)
1055 1059 return None
1056 1060
1057 1061 # if the exact patch name does not exist, we try a few
1058 1062 # variations. If strict is passed, we try only #1
1059 1063 #
1060 1064 # 1) a number (as string) to indicate an offset in the series file
1061 1065 # 2) a unique substring of the patch name was given
1062 1066 # 3) patchname[-+]num to indicate an offset in the series file
1063 1067 def lookup(self, patch, strict=False):
1064 1068 def partialname(s):
1065 1069 if s in self.series:
1066 1070 return s
1067 1071 matches = [x for x in self.series if s in x]
1068 1072 if len(matches) > 1:
1069 1073 self.ui.warn(_('patch name "%s" is ambiguous:\n') % s)
1070 1074 for m in matches:
1071 1075 self.ui.warn(' %s\n' % m)
1072 1076 return None
1073 1077 if matches:
1074 1078 return matches[0]
1075 1079 if self.series and self.applied:
1076 1080 if s == 'qtip':
1077 1081 return self.series[self.seriesend(True)-1]
1078 1082 if s == 'qbase':
1079 1083 return self.series[0]
1080 1084 return None
1081 1085
1082 1086 if patch in self.series:
1083 1087 return patch
1084 1088
1085 1089 if not os.path.isfile(self.join(patch)):
1086 1090 try:
1087 1091 sno = int(patch)
1088 1092 except (ValueError, OverflowError):
1089 1093 pass
1090 1094 else:
1091 1095 if -len(self.series) <= sno < len(self.series):
1092 1096 return self.series[sno]
1093 1097
1094 1098 if not strict:
1095 1099 res = partialname(patch)
1096 1100 if res:
1097 1101 return res
1098 1102 minus = patch.rfind('-')
1099 1103 if minus >= 0:
1100 1104 res = partialname(patch[:minus])
1101 1105 if res:
1102 1106 i = self.series.index(res)
1103 1107 try:
1104 1108 off = int(patch[minus + 1:] or 1)
1105 1109 except (ValueError, OverflowError):
1106 1110 pass
1107 1111 else:
1108 1112 if i - off >= 0:
1109 1113 return self.series[i - off]
1110 1114 plus = patch.rfind('+')
1111 1115 if plus >= 0:
1112 1116 res = partialname(patch[:plus])
1113 1117 if res:
1114 1118 i = self.series.index(res)
1115 1119 try:
1116 1120 off = int(patch[plus + 1:] or 1)
1117 1121 except (ValueError, OverflowError):
1118 1122 pass
1119 1123 else:
1120 1124 if i + off < len(self.series):
1121 1125 return self.series[i + off]
1122 1126 raise util.Abort(_("patch %s not in series") % patch)
1123 1127
1124 1128 def push(self, repo, patch=None, force=False, list=False,
1125 1129 mergeq=None, all=False, move=False, exact=False):
1126 1130 diffopts = self.diffopts()
1127 1131 wlock = repo.wlock()
1128 1132 try:
1129 1133 heads = []
1130 1134 for b, ls in repo.branchmap().iteritems():
1131 1135 heads += ls
1132 1136 if not heads:
1133 1137 heads = [nullid]
1134 1138 if repo.dirstate.p1() not in heads and not exact:
1135 1139 self.ui.status(_("(working directory not at a head)\n"))
1136 1140
1137 1141 if not self.series:
1138 1142 self.ui.warn(_('no patches in series\n'))
1139 1143 return 0
1140 1144
1141 1145 # Suppose our series file is: A B C and the current 'top'
1142 1146 # patch is B. qpush C should be performed (moving forward)
1143 1147 # qpush B is a NOP (no change) qpush A is an error (can't
1144 1148 # go backwards with qpush)
1145 1149 if patch:
1146 1150 patch = self.lookup(patch)
1147 1151 info = self.isapplied(patch)
1148 1152 if info and info[0] >= len(self.applied) - 1:
1149 1153 self.ui.warn(
1150 1154 _('qpush: %s is already at the top\n') % patch)
1151 1155 return 0
1152 1156
1153 1157 pushable, reason = self.pushable(patch)
1154 1158 if pushable:
1155 1159 if self.series.index(patch) < self.seriesend():
1156 1160 raise util.Abort(
1157 1161 _("cannot push to a previous patch: %s") % patch)
1158 1162 else:
1159 1163 if reason:
1160 1164 reason = _('guarded by %s') % reason
1161 1165 else:
1162 1166 reason = _('no matching guards')
1163 1167 self.ui.warn(_("cannot push '%s' - %s\n") % (patch, reason))
1164 1168 return 1
1165 1169 elif all:
1166 1170 patch = self.series[-1]
1167 1171 if self.isapplied(patch):
1168 1172 self.ui.warn(_('all patches are currently applied\n'))
1169 1173 return 0
1170 1174
1171 1175 # Following the above example, starting at 'top' of B:
1172 1176 # qpush should be performed (pushes C), but a subsequent
1173 1177 # qpush without an argument is an error (nothing to
1174 1178 # apply). This allows a loop of "...while hg qpush..." to
1175 1179 # work as it detects an error when done
1176 1180 start = self.seriesend()
1177 1181 if start == len(self.series):
1178 1182 self.ui.warn(_('patch series already fully applied\n'))
1179 1183 return 1
1180 1184 if not force:
1181 1185 self.checklocalchanges(repo, refresh=self.applied)
1182 1186
1183 1187 if exact:
1184 1188 if move:
1185 1189 raise util.Abort(_("cannot use --exact and --move together"))
1186 1190 if self.applied:
1187 1191 raise util.Abort(_("cannot push --exact with applied patches"))
1188 1192 root = self.series[start]
1189 1193 target = patchheader(self.join(root), self.plainmode).parent
1190 1194 if not target:
1191 1195 raise util.Abort(_("%s does not have a parent recorded" % root))
1192 1196 if not repo[target] == repo['.']:
1193 1197 hg.update(repo, target)
1194 1198
1195 1199 if move:
1196 1200 if not patch:
1197 1201 raise util.Abort(_("please specify the patch to move"))
1198 1202 for i, rpn in enumerate(self.fullseries[start:]):
1199 1203 # strip markers for patch guards
1200 1204 if self.guard_re.split(rpn, 1)[0] == patch:
1201 1205 break
1202 1206 index = start + i
1203 1207 assert index < len(self.fullseries)
1204 1208 fullpatch = self.fullseries[index]
1205 1209 del self.fullseries[index]
1206 1210 self.fullseries.insert(start, fullpatch)
1207 1211 self.parseseries()
1208 1212 self.seriesdirty = True
1209 1213
1210 1214 self.applieddirty = True
1211 1215 if start > 0:
1212 1216 self.checktoppatch(repo)
1213 1217 if not patch:
1214 1218 patch = self.series[start]
1215 1219 end = start + 1
1216 1220 else:
1217 1221 end = self.series.index(patch, start) + 1
1218 1222
1219 1223 s = self.series[start:end]
1220 1224 all_files = set()
1221 1225 try:
1222 1226 if mergeq:
1223 1227 ret = self.mergepatch(repo, mergeq, s, diffopts)
1224 1228 else:
1225 1229 ret = self.apply(repo, s, list, all_files=all_files)
1226 1230 except:
1227 1231 self.ui.warn(_('cleaning up working directory...'))
1228 1232 node = repo.dirstate.p1()
1229 1233 hg.revert(repo, node, None)
1230 1234 # only remove unknown files that we know we touched or
1231 1235 # created while patching
1232 1236 for f in all_files:
1233 1237 if f not in repo.dirstate:
1234 1238 try:
1235 1239 util.unlinkpath(repo.wjoin(f))
1236 1240 except OSError, inst:
1237 1241 if inst.errno != errno.ENOENT:
1238 1242 raise
1239 1243 self.ui.warn(_('done\n'))
1240 1244 raise
1241 1245
1242 1246 if not self.applied:
1243 1247 return ret[0]
1244 1248 top = self.applied[-1].name
1245 1249 if ret[0] and ret[0] > 1:
1246 1250 msg = _("errors during apply, please fix and refresh %s\n")
1247 1251 self.ui.write(msg % top)
1248 1252 else:
1249 1253 self.ui.write(_("now at: %s\n") % top)
1250 1254 return ret[0]
1251 1255
1252 1256 finally:
1253 1257 wlock.release()
1254 1258
1255 1259 def pop(self, repo, patch=None, force=False, update=True, all=False):
1256 1260 wlock = repo.wlock()
1257 1261 try:
1258 1262 if patch:
1259 1263 # index, rev, patch
1260 1264 info = self.isapplied(patch)
1261 1265 if not info:
1262 1266 patch = self.lookup(patch)
1263 1267 info = self.isapplied(patch)
1264 1268 if not info:
1265 1269 raise util.Abort(_("patch %s is not applied") % patch)
1266 1270
1267 1271 if not self.applied:
1268 1272 # Allow qpop -a to work repeatedly,
1269 1273 # but not qpop without an argument
1270 1274 self.ui.warn(_("no patches applied\n"))
1271 1275 return not all
1272 1276
1273 1277 if all:
1274 1278 start = 0
1275 1279 elif patch:
1276 1280 start = info[0] + 1
1277 1281 else:
1278 1282 start = len(self.applied) - 1
1279 1283
1280 1284 if start >= len(self.applied):
1281 1285 self.ui.warn(_("qpop: %s is already at the top\n") % patch)
1282 1286 return
1283 1287
1284 1288 if not update:
1285 1289 parents = repo.dirstate.parents()
1286 1290 rr = [x.node for x in self.applied]
1287 1291 for p in parents:
1288 1292 if p in rr:
1289 1293 self.ui.warn(_("qpop: forcing dirstate update\n"))
1290 1294 update = True
1291 1295 else:
1292 1296 parents = [p.node() for p in repo[None].parents()]
1293 1297 needupdate = False
1294 1298 for entry in self.applied[start:]:
1295 1299 if entry.node in parents:
1296 1300 needupdate = True
1297 1301 break
1298 1302 update = needupdate
1299 1303
1300 1304 if not force and update:
1301 1305 self.checklocalchanges(repo)
1302 1306
1303 1307 self.applieddirty = True
1304 1308 end = len(self.applied)
1305 1309 rev = self.applied[start].node
1306 1310 if update:
1307 1311 top = self.checktoppatch(repo)[0]
1308 1312
1309 1313 try:
1310 1314 heads = repo.changelog.heads(rev)
1311 1315 except error.LookupError:
1312 1316 node = short(rev)
1313 1317 raise util.Abort(_('trying to pop unknown node %s') % node)
1314 1318
1315 1319 if heads != [self.applied[-1].node]:
1316 1320 raise util.Abort(_("popping would remove a revision not "
1317 1321 "managed by this patch queue"))
1318 1322 if not repo[self.applied[-1].node].mutable():
1319 1323 raise util.Abort(
1320 1324 _("popping would remove an immutable revision"),
1321 1325 hint=_('see "hg help phases" for details'))
1322 1326
1323 1327 # we know there are no local changes, so we can make a simplified
1324 1328 # form of hg.update.
1325 1329 if update:
1326 1330 qp = self.qparents(repo, rev)
1327 1331 ctx = repo[qp]
1328 1332 m, a, r, d = repo.status(qp, top)[:4]
1329 1333 if d:
1330 1334 raise util.Abort(_("deletions found between repo revs"))
1331 1335 for f in a:
1332 1336 try:
1333 1337 util.unlinkpath(repo.wjoin(f))
1334 1338 except OSError, e:
1335 1339 if e.errno != errno.ENOENT:
1336 1340 raise
1337 1341 repo.dirstate.drop(f)
1338 1342 for f in m + r:
1339 1343 fctx = ctx[f]
1340 1344 repo.wwrite(f, fctx.data(), fctx.flags())
1341 1345 repo.dirstate.normal(f)
1342 1346 repo.dirstate.setparents(qp, nullid)
1343 1347 for patch in reversed(self.applied[start:end]):
1344 1348 self.ui.status(_("popping %s\n") % patch.name)
1345 1349 del self.applied[start:end]
1346 1350 self.strip(repo, [rev], update=False, backup='strip')
1347 1351 if self.applied:
1348 1352 self.ui.write(_("now at: %s\n") % self.applied[-1].name)
1349 1353 else:
1350 1354 self.ui.write(_("patch queue now empty\n"))
1351 1355 finally:
1352 1356 wlock.release()
1353 1357
1354 1358 def diff(self, repo, pats, opts):
1355 1359 top, patch = self.checktoppatch(repo)
1356 1360 if not top:
1357 1361 self.ui.write(_("no patches applied\n"))
1358 1362 return
1359 1363 qp = self.qparents(repo, top)
1360 1364 if opts.get('reverse'):
1361 1365 node1, node2 = None, qp
1362 1366 else:
1363 1367 node1, node2 = qp, None
1364 1368 diffopts = self.diffopts(opts, patch)
1365 1369 self.printdiff(repo, diffopts, node1, node2, files=pats, opts=opts)
1366 1370
1367 1371 def refresh(self, repo, pats=None, **opts):
1368 1372 if not self.applied:
1369 1373 self.ui.write(_("no patches applied\n"))
1370 1374 return 1
1371 1375 msg = opts.get('msg', '').rstrip()
1372 1376 newuser = opts.get('user')
1373 1377 newdate = opts.get('date')
1374 1378 if newdate:
1375 1379 newdate = '%d %d' % util.parsedate(newdate)
1376 1380 wlock = repo.wlock()
1377 1381
1378 1382 try:
1379 1383 self.checktoppatch(repo)
1380 1384 (top, patchfn) = (self.applied[-1].node, self.applied[-1].name)
1381 1385 if repo.changelog.heads(top) != [top]:
1382 1386 raise util.Abort(_("cannot refresh a revision with children"))
1383 1387 if not repo[top].mutable():
1384 1388 raise util.Abort(_("cannot refresh immutable revision"),
1385 1389 hint=_('see "hg help phases" for details'))
1386 1390
1387 1391 inclsubs = self.checksubstate(repo)
1388 1392
1389 1393 cparents = repo.changelog.parents(top)
1390 1394 patchparent = self.qparents(repo, top)
1391 1395 ph = patchheader(self.join(patchfn), self.plainmode)
1392 1396 diffopts = self.diffopts({'git': opts.get('git')}, patchfn)
1393 1397 if msg:
1394 1398 ph.setmessage(msg)
1395 1399 if newuser:
1396 1400 ph.setuser(newuser)
1397 1401 if newdate:
1398 1402 ph.setdate(newdate)
1399 1403 ph.setparent(hex(patchparent))
1400 1404
1401 1405 # only commit new patch when write is complete
1402 1406 patchf = self.opener(patchfn, 'w', atomictemp=True)
1403 1407
1404 1408 comments = str(ph)
1405 1409 if comments:
1406 1410 patchf.write(comments)
1407 1411
1408 1412 # update the dirstate in place, strip off the qtip commit
1409 1413 # and then commit.
1410 1414 #
1411 1415 # this should really read:
1412 1416 # mm, dd, aa = repo.status(top, patchparent)[:3]
1413 1417 # but we do it backwards to take advantage of manifest/chlog
1414 1418 # caching against the next repo.status call
1415 1419 mm, aa, dd = repo.status(patchparent, top)[:3]
1416 1420 changes = repo.changelog.read(top)
1417 1421 man = repo.manifest.read(changes[0])
1418 1422 aaa = aa[:]
1419 1423 matchfn = scmutil.match(repo[None], pats, opts)
1420 1424 # in short mode, we only diff the files included in the
1421 1425 # patch already plus specified files
1422 1426 if opts.get('short'):
1423 1427 # if amending a patch, we start with existing
1424 1428 # files plus specified files - unfiltered
1425 1429 match = scmutil.matchfiles(repo, mm + aa + dd + matchfn.files())
1426 1430 # filter with inc/exl options
1427 1431 matchfn = scmutil.match(repo[None], opts=opts)
1428 1432 else:
1429 1433 match = scmutil.matchall(repo)
1430 1434 m, a, r, d = repo.status(match=match)[:4]
1431 1435 mm = set(mm)
1432 1436 aa = set(aa)
1433 1437 dd = set(dd)
1434 1438
1435 1439 # we might end up with files that were added between
1436 1440 # qtip and the dirstate parent, but then changed in the
1437 1441 # local dirstate. in this case, we want them to only
1438 1442 # show up in the added section
1439 1443 for x in m:
1440 1444 if x not in aa:
1441 1445 mm.add(x)
1442 1446 # we might end up with files added by the local dirstate that
1443 1447 # were deleted by the patch. In this case, they should only
1444 1448 # show up in the changed section.
1445 1449 for x in a:
1446 1450 if x in dd:
1447 1451 dd.remove(x)
1448 1452 mm.add(x)
1449 1453 else:
1450 1454 aa.add(x)
1451 1455 # make sure any files deleted in the local dirstate
1452 1456 # are not in the add or change column of the patch
1453 1457 forget = []
1454 1458 for x in d + r:
1455 1459 if x in aa:
1456 1460 aa.remove(x)
1457 1461 forget.append(x)
1458 1462 continue
1459 1463 else:
1460 1464 mm.discard(x)
1461 1465 dd.add(x)
1462 1466
1463 1467 m = list(mm)
1464 1468 r = list(dd)
1465 1469 a = list(aa)
1466 1470 c = [filter(matchfn, l) for l in (m, a, r)]
1467 1471 match = scmutil.matchfiles(repo, set(c[0] + c[1] + c[2] + inclsubs))
1468 1472 chunks = patchmod.diff(repo, patchparent, match=match,
1469 1473 changes=c, opts=diffopts)
1470 1474 for chunk in chunks:
1471 1475 patchf.write(chunk)
1472 1476
1473 1477 try:
1474 1478 if diffopts.git or diffopts.upgrade:
1475 1479 copies = {}
1476 1480 for dst in a:
1477 1481 src = repo.dirstate.copied(dst)
1478 1482 # during qfold, the source file for copies may
1479 1483 # be removed. Treat this as a simple add.
1480 1484 if src is not None and src in repo.dirstate:
1481 1485 copies.setdefault(src, []).append(dst)
1482 1486 repo.dirstate.add(dst)
1483 1487 # remember the copies between patchparent and qtip
1484 1488 for dst in aaa:
1485 1489 f = repo.file(dst)
1486 1490 src = f.renamed(man[dst])
1487 1491 if src:
1488 1492 copies.setdefault(src[0], []).extend(
1489 1493 copies.get(dst, []))
1490 1494 if dst in a:
1491 1495 copies[src[0]].append(dst)
1492 1496 # we can't copy a file created by the patch itself
1493 1497 if dst in copies:
1494 1498 del copies[dst]
1495 1499 for src, dsts in copies.iteritems():
1496 1500 for dst in dsts:
1497 1501 repo.dirstate.copy(src, dst)
1498 1502 else:
1499 1503 for dst in a:
1500 1504 repo.dirstate.add(dst)
1501 1505 # Drop useless copy information
1502 1506 for f in list(repo.dirstate.copies()):
1503 1507 repo.dirstate.copy(None, f)
1504 1508 for f in r:
1505 1509 repo.dirstate.remove(f)
1506 1510 # if the patch excludes a modified file, mark that
1507 1511 # file with mtime=0 so status can see it.
1508 1512 mm = []
1509 1513 for i in xrange(len(m)-1, -1, -1):
1510 1514 if not matchfn(m[i]):
1511 1515 mm.append(m[i])
1512 1516 del m[i]
1513 1517 for f in m:
1514 1518 repo.dirstate.normal(f)
1515 1519 for f in mm:
1516 1520 repo.dirstate.normallookup(f)
1517 1521 for f in forget:
1518 1522 repo.dirstate.drop(f)
1519 1523
1520 1524 if not msg:
1521 1525 if not ph.message:
1522 1526 message = "[mq]: %s\n" % patchfn
1523 1527 else:
1524 1528 message = "\n".join(ph.message)
1525 1529 else:
1526 1530 message = msg
1527 1531
1528 1532 user = ph.user or changes[1]
1529 1533
1530 1534 oldphase = repo[top].phase()
1531 1535
1532 1536 # assumes strip can roll itself back if interrupted
1533 1537 repo.dirstate.setparents(*cparents)
1534 1538 self.applied.pop()
1535 1539 self.applieddirty = True
1536 1540 self.strip(repo, [top], update=False,
1537 1541 backup='strip')
1538 1542 except:
1539 1543 repo.dirstate.invalidate()
1540 1544 raise
1541 1545
1542 1546 try:
1543 1547 # might be nice to attempt to roll back strip after this
1544 1548
1545 1549 # Ensure we create a new changeset in the same phase than
1546 1550 # the old one.
1547 1551 n = secretcommit(repo, oldphase, message, user, ph.date,
1548 1552 match=match, force=True)
1549 1553 # only write patch after a successful commit
1550 1554 patchf.close()
1551 1555 self.applied.append(statusentry(n, patchfn))
1552 1556 except:
1553 1557 ctx = repo[cparents[0]]
1554 1558 repo.dirstate.rebuild(ctx.node(), ctx.manifest())
1555 1559 self.savedirty()
1556 1560 self.ui.warn(_('refresh interrupted while patch was popped! '
1557 1561 '(revert --all, qpush to recover)\n'))
1558 1562 raise
1559 1563 finally:
1560 1564 wlock.release()
1561 1565 self.removeundo(repo)
1562 1566
1563 1567 def init(self, repo, create=False):
1564 1568 if not create and os.path.isdir(self.path):
1565 1569 raise util.Abort(_("patch queue directory already exists"))
1566 1570 try:
1567 1571 os.mkdir(self.path)
1568 1572 except OSError, inst:
1569 1573 if inst.errno != errno.EEXIST or not create:
1570 1574 raise
1571 1575 if create:
1572 1576 return self.qrepo(create=True)
1573 1577
1574 1578 def unapplied(self, repo, patch=None):
1575 1579 if patch and patch not in self.series:
1576 1580 raise util.Abort(_("patch %s is not in series file") % patch)
1577 1581 if not patch:
1578 1582 start = self.seriesend()
1579 1583 else:
1580 1584 start = self.series.index(patch) + 1
1581 1585 unapplied = []
1582 1586 for i in xrange(start, len(self.series)):
1583 1587 pushable, reason = self.pushable(i)
1584 1588 if pushable:
1585 1589 unapplied.append((i, self.series[i]))
1586 1590 self.explainpushable(i)
1587 1591 return unapplied
1588 1592
1589 1593 def qseries(self, repo, missing=None, start=0, length=None, status=None,
1590 1594 summary=False):
1591 1595 def displayname(pfx, patchname, state):
1592 1596 if pfx:
1593 1597 self.ui.write(pfx)
1594 1598 if summary:
1595 1599 ph = patchheader(self.join(patchname), self.plainmode)
1596 1600 msg = ph.message and ph.message[0] or ''
1597 1601 if self.ui.formatted():
1598 1602 width = self.ui.termwidth() - len(pfx) - len(patchname) - 2
1599 1603 if width > 0:
1600 1604 msg = util.ellipsis(msg, width)
1601 1605 else:
1602 1606 msg = ''
1603 1607 self.ui.write(patchname, label='qseries.' + state)
1604 1608 self.ui.write(': ')
1605 1609 self.ui.write(msg, label='qseries.message.' + state)
1606 1610 else:
1607 1611 self.ui.write(patchname, label='qseries.' + state)
1608 1612 self.ui.write('\n')
1609 1613
1610 1614 applied = set([p.name for p in self.applied])
1611 1615 if length is None:
1612 1616 length = len(self.series) - start
1613 1617 if not missing:
1614 1618 if self.ui.verbose:
1615 1619 idxwidth = len(str(start + length - 1))
1616 1620 for i in xrange(start, start + length):
1617 1621 patch = self.series[i]
1618 1622 if patch in applied:
1619 1623 char, state = 'A', 'applied'
1620 1624 elif self.pushable(i)[0]:
1621 1625 char, state = 'U', 'unapplied'
1622 1626 else:
1623 1627 char, state = 'G', 'guarded'
1624 1628 pfx = ''
1625 1629 if self.ui.verbose:
1626 1630 pfx = '%*d %s ' % (idxwidth, i, char)
1627 1631 elif status and status != char:
1628 1632 continue
1629 1633 displayname(pfx, patch, state)
1630 1634 else:
1631 1635 msng_list = []
1632 1636 for root, dirs, files in os.walk(self.path):
1633 1637 d = root[len(self.path) + 1:]
1634 1638 for f in files:
1635 1639 fl = os.path.join(d, f)
1636 1640 if (fl not in self.series and
1637 1641 fl not in (self.statuspath, self.seriespath,
1638 1642 self.guardspath)
1639 1643 and not fl.startswith('.')):
1640 1644 msng_list.append(fl)
1641 1645 for x in sorted(msng_list):
1642 1646 pfx = self.ui.verbose and ('D ') or ''
1643 1647 displayname(pfx, x, 'missing')
1644 1648
1645 1649 def issaveline(self, l):
1646 1650 if l.name == '.hg.patches.save.line':
1647 1651 return True
1648 1652
1649 1653 def qrepo(self, create=False):
1650 1654 ui = self.ui.copy()
1651 1655 ui.setconfig('paths', 'default', '', overlay=False)
1652 1656 ui.setconfig('paths', 'default-push', '', overlay=False)
1653 1657 if create or os.path.isdir(self.join(".hg")):
1654 1658 return hg.repository(ui, path=self.path, create=create)
1655 1659
1656 1660 def restore(self, repo, rev, delete=None, qupdate=None):
1657 1661 desc = repo[rev].description().strip()
1658 1662 lines = desc.splitlines()
1659 1663 i = 0
1660 1664 datastart = None
1661 1665 series = []
1662 1666 applied = []
1663 1667 qpp = None
1664 1668 for i, line in enumerate(lines):
1665 1669 if line == 'Patch Data:':
1666 1670 datastart = i + 1
1667 1671 elif line.startswith('Dirstate:'):
1668 1672 l = line.rstrip()
1669 1673 l = l[10:].split(' ')
1670 1674 qpp = [bin(x) for x in l]
1671 1675 elif datastart is not None:
1672 1676 l = line.rstrip()
1673 1677 n, name = l.split(':', 1)
1674 1678 if n:
1675 1679 applied.append(statusentry(bin(n), name))
1676 1680 else:
1677 1681 series.append(l)
1678 1682 if datastart is None:
1679 1683 self.ui.warn(_("No saved patch data found\n"))
1680 1684 return 1
1681 1685 self.ui.warn(_("restoring status: %s\n") % lines[0])
1682 1686 self.fullseries = series
1683 1687 self.applied = applied
1684 1688 self.parseseries()
1685 1689 self.seriesdirty = True
1686 1690 self.applieddirty = True
1687 1691 heads = repo.changelog.heads()
1688 1692 if delete:
1689 1693 if rev not in heads:
1690 1694 self.ui.warn(_("save entry has children, leaving it alone\n"))
1691 1695 else:
1692 1696 self.ui.warn(_("removing save entry %s\n") % short(rev))
1693 1697 pp = repo.dirstate.parents()
1694 1698 if rev in pp:
1695 1699 update = True
1696 1700 else:
1697 1701 update = False
1698 1702 self.strip(repo, [rev], update=update, backup='strip')
1699 1703 if qpp:
1700 1704 self.ui.warn(_("saved queue repository parents: %s %s\n") %
1701 1705 (short(qpp[0]), short(qpp[1])))
1702 1706 if qupdate:
1703 1707 self.ui.status(_("updating queue directory\n"))
1704 1708 r = self.qrepo()
1705 1709 if not r:
1706 1710 self.ui.warn(_("Unable to load queue repository\n"))
1707 1711 return 1
1708 1712 hg.clean(r, qpp[0])
1709 1713
1710 1714 def save(self, repo, msg=None):
1711 1715 if not self.applied:
1712 1716 self.ui.warn(_("save: no patches applied, exiting\n"))
1713 1717 return 1
1714 1718 if self.issaveline(self.applied[-1]):
1715 1719 self.ui.warn(_("status is already saved\n"))
1716 1720 return 1
1717 1721
1718 1722 if not msg:
1719 1723 msg = _("hg patches saved state")
1720 1724 else:
1721 1725 msg = "hg patches: " + msg.rstrip('\r\n')
1722 1726 r = self.qrepo()
1723 1727 if r:
1724 1728 pp = r.dirstate.parents()
1725 1729 msg += "\nDirstate: %s %s" % (hex(pp[0]), hex(pp[1]))
1726 1730 msg += "\n\nPatch Data:\n"
1727 1731 msg += ''.join('%s\n' % x for x in self.applied)
1728 1732 msg += ''.join(':%s\n' % x for x in self.fullseries)
1729 1733 n = repo.commit(msg, force=True)
1730 1734 if not n:
1731 1735 self.ui.warn(_("repo commit failed\n"))
1732 1736 return 1
1733 1737 self.applied.append(statusentry(n, '.hg.patches.save.line'))
1734 1738 self.applieddirty = True
1735 1739 self.removeundo(repo)
1736 1740
1737 1741 def fullseriesend(self):
1738 1742 if self.applied:
1739 1743 p = self.applied[-1].name
1740 1744 end = self.findseries(p)
1741 1745 if end is None:
1742 1746 return len(self.fullseries)
1743 1747 return end + 1
1744 1748 return 0
1745 1749
1746 1750 def seriesend(self, all_patches=False):
1747 1751 """If all_patches is False, return the index of the next pushable patch
1748 1752 in the series, or the series length. If all_patches is True, return the
1749 1753 index of the first patch past the last applied one.
1750 1754 """
1751 1755 end = 0
1752 1756 def next(start):
1753 1757 if all_patches or start >= len(self.series):
1754 1758 return start
1755 1759 for i in xrange(start, len(self.series)):
1756 1760 p, reason = self.pushable(i)
1757 1761 if p:
1758 1762 return i
1759 1763 self.explainpushable(i)
1760 1764 return len(self.series)
1761 1765 if self.applied:
1762 1766 p = self.applied[-1].name
1763 1767 try:
1764 1768 end = self.series.index(p)
1765 1769 except ValueError:
1766 1770 return 0
1767 1771 return next(end + 1)
1768 1772 return next(end)
1769 1773
1770 1774 def appliedname(self, index):
1771 1775 pname = self.applied[index].name
1772 1776 if not self.ui.verbose:
1773 1777 p = pname
1774 1778 else:
1775 1779 p = str(self.series.index(pname)) + " " + pname
1776 1780 return p
1777 1781
1778 1782 def qimport(self, repo, files, patchname=None, rev=None, existing=None,
1779 1783 force=None, git=False):
1780 1784 def checkseries(patchname):
1781 1785 if patchname in self.series:
1782 1786 raise util.Abort(_('patch %s is already in the series file')
1783 1787 % patchname)
1784 1788
1785 1789 if rev:
1786 1790 if files:
1787 1791 raise util.Abort(_('option "-r" not valid when importing '
1788 1792 'files'))
1789 1793 rev = scmutil.revrange(repo, rev)
1790 1794 rev.sort(reverse=True)
1791 1795 if (len(files) > 1 or len(rev) > 1) and patchname:
1792 1796 raise util.Abort(_('option "-n" not valid when importing multiple '
1793 1797 'patches'))
1794 1798 if rev:
1795 1799 # If mq patches are applied, we can only import revisions
1796 1800 # that form a linear path to qbase.
1797 1801 # Otherwise, they should form a linear path to a head.
1798 1802 heads = repo.changelog.heads(repo.changelog.node(rev[-1]))
1799 1803 if len(heads) > 1:
1800 1804 raise util.Abort(_('revision %d is the root of more than one '
1801 1805 'branch') % rev[-1])
1802 1806 if self.applied:
1803 1807 base = repo.changelog.node(rev[0])
1804 1808 if base in [n.node for n in self.applied]:
1805 1809 raise util.Abort(_('revision %d is already managed')
1806 1810 % rev[0])
1807 1811 if heads != [self.applied[-1].node]:
1808 1812 raise util.Abort(_('revision %d is not the parent of '
1809 1813 'the queue') % rev[0])
1810 1814 base = repo.changelog.rev(self.applied[0].node)
1811 1815 lastparent = repo.changelog.parentrevs(base)[0]
1812 1816 else:
1813 1817 if heads != [repo.changelog.node(rev[0])]:
1814 1818 raise util.Abort(_('revision %d has unmanaged children')
1815 1819 % rev[0])
1816 1820 lastparent = None
1817 1821
1818 1822 diffopts = self.diffopts({'git': git})
1819 1823 for r in rev:
1820 1824 if not repo[r].mutable():
1821 1825 raise util.Abort(_('revision %d is not mutable') % r,
1822 1826 hint=_('see "hg help phases" for details'))
1823 1827 p1, p2 = repo.changelog.parentrevs(r)
1824 1828 n = repo.changelog.node(r)
1825 1829 if p2 != nullrev:
1826 1830 raise util.Abort(_('cannot import merge revision %d') % r)
1827 1831 if lastparent and lastparent != r:
1828 1832 raise util.Abort(_('revision %d is not the parent of %d')
1829 1833 % (r, lastparent))
1830 1834 lastparent = p1
1831 1835
1832 1836 if not patchname:
1833 1837 patchname = normname('%d.diff' % r)
1834 1838 checkseries(patchname)
1835 1839 self.checkpatchname(patchname, force)
1836 1840 self.fullseries.insert(0, patchname)
1837 1841
1838 1842 patchf = self.opener(patchname, "w")
1839 1843 cmdutil.export(repo, [n], fp=patchf, opts=diffopts)
1840 1844 patchf.close()
1841 1845
1842 1846 se = statusentry(n, patchname)
1843 1847 self.applied.insert(0, se)
1844 1848
1845 1849 self.added.append(patchname)
1846 1850 patchname = None
1847 1851 if rev and repo.ui.configbool('mq', 'secret', False):
1848 1852 # if we added anything with --rev, we must move the secret root
1849 1853 phases.retractboundary(repo, phases.secret, [n])
1850 1854 self.parseseries()
1851 1855 self.applieddirty = True
1852 1856 self.seriesdirty = True
1853 1857
1854 1858 for i, filename in enumerate(files):
1855 1859 if existing:
1856 1860 if filename == '-':
1857 1861 raise util.Abort(_('-e is incompatible with import from -'))
1858 1862 filename = normname(filename)
1859 1863 self.checkreservedname(filename)
1860 1864 originpath = self.join(filename)
1861 1865 if not os.path.isfile(originpath):
1862 1866 raise util.Abort(_("patch %s does not exist") % filename)
1863 1867
1864 1868 if patchname:
1865 1869 self.checkpatchname(patchname, force)
1866 1870
1867 1871 self.ui.write(_('renaming %s to %s\n')
1868 1872 % (filename, patchname))
1869 1873 util.rename(originpath, self.join(patchname))
1870 1874 else:
1871 1875 patchname = filename
1872 1876
1873 1877 else:
1874 1878 if filename == '-' and not patchname:
1875 1879 raise util.Abort(_('need --name to import a patch from -'))
1876 1880 elif not patchname:
1877 1881 patchname = normname(os.path.basename(filename.rstrip('/')))
1878 1882 self.checkpatchname(patchname, force)
1879 1883 try:
1880 1884 if filename == '-':
1881 1885 text = self.ui.fin.read()
1882 1886 else:
1883 1887 fp = url.open(self.ui, filename)
1884 1888 text = fp.read()
1885 1889 fp.close()
1886 1890 except (OSError, IOError):
1887 1891 raise util.Abort(_("unable to read file %s") % filename)
1888 1892 patchf = self.opener(patchname, "w")
1889 1893 patchf.write(text)
1890 1894 patchf.close()
1891 1895 if not force:
1892 1896 checkseries(patchname)
1893 1897 if patchname not in self.series:
1894 1898 index = self.fullseriesend() + i
1895 1899 self.fullseries[index:index] = [patchname]
1896 1900 self.parseseries()
1897 1901 self.seriesdirty = True
1898 1902 self.ui.warn(_("adding %s to series file\n") % patchname)
1899 1903 self.added.append(patchname)
1900 1904 patchname = None
1901 1905
1902 1906 self.removeundo(repo)
1903 1907
1904 1908 @command("qdelete|qremove|qrm",
1905 1909 [('k', 'keep', None, _('keep patch file')),
1906 1910 ('r', 'rev', [],
1907 1911 _('stop managing a revision (DEPRECATED)'), _('REV'))],
1908 1912 _('hg qdelete [-k] [PATCH]...'))
1909 1913 def delete(ui, repo, *patches, **opts):
1910 1914 """remove patches from queue
1911 1915
1912 1916 The patches must not be applied, and at least one patch is required. Exact
1913 1917 patch identifiers must be given. With -k/--keep, the patch files are
1914 1918 preserved in the patch directory.
1915 1919
1916 1920 To stop managing a patch and move it into permanent history,
1917 1921 use the :hg:`qfinish` command."""
1918 1922 q = repo.mq
1919 1923 q.delete(repo, patches, opts)
1920 1924 q.savedirty()
1921 1925 return 0
1922 1926
1923 1927 @command("qapplied",
1924 1928 [('1', 'last', None, _('show only the last patch'))
1925 1929 ] + seriesopts,
1926 1930 _('hg qapplied [-1] [-s] [PATCH]'))
1927 1931 def applied(ui, repo, patch=None, **opts):
1928 1932 """print the patches already applied
1929 1933
1930 1934 Returns 0 on success."""
1931 1935
1932 1936 q = repo.mq
1933 1937
1934 1938 if patch:
1935 1939 if patch not in q.series:
1936 1940 raise util.Abort(_("patch %s is not in series file") % patch)
1937 1941 end = q.series.index(patch) + 1
1938 1942 else:
1939 1943 end = q.seriesend(True)
1940 1944
1941 1945 if opts.get('last') and not end:
1942 1946 ui.write(_("no patches applied\n"))
1943 1947 return 1
1944 1948 elif opts.get('last') and end == 1:
1945 1949 ui.write(_("only one patch applied\n"))
1946 1950 return 1
1947 1951 elif opts.get('last'):
1948 1952 start = end - 2
1949 1953 end = 1
1950 1954 else:
1951 1955 start = 0
1952 1956
1953 1957 q.qseries(repo, length=end, start=start, status='A',
1954 1958 summary=opts.get('summary'))
1955 1959
1956 1960
1957 1961 @command("qunapplied",
1958 1962 [('1', 'first', None, _('show only the first patch'))] + seriesopts,
1959 1963 _('hg qunapplied [-1] [-s] [PATCH]'))
1960 1964 def unapplied(ui, repo, patch=None, **opts):
1961 1965 """print the patches not yet applied
1962 1966
1963 1967 Returns 0 on success."""
1964 1968
1965 1969 q = repo.mq
1966 1970 if patch:
1967 1971 if patch not in q.series:
1968 1972 raise util.Abort(_("patch %s is not in series file") % patch)
1969 1973 start = q.series.index(patch) + 1
1970 1974 else:
1971 1975 start = q.seriesend(True)
1972 1976
1973 1977 if start == len(q.series) and opts.get('first'):
1974 1978 ui.write(_("all patches applied\n"))
1975 1979 return 1
1976 1980
1977 1981 length = opts.get('first') and 1 or None
1978 1982 q.qseries(repo, start=start, length=length, status='U',
1979 1983 summary=opts.get('summary'))
1980 1984
1981 1985 @command("qimport",
1982 1986 [('e', 'existing', None, _('import file in patch directory')),
1983 1987 ('n', 'name', '',
1984 1988 _('name of patch file'), _('NAME')),
1985 1989 ('f', 'force', None, _('overwrite existing files')),
1986 1990 ('r', 'rev', [],
1987 1991 _('place existing revisions under mq control'), _('REV')),
1988 1992 ('g', 'git', None, _('use git extended diff format')),
1989 1993 ('P', 'push', None, _('qpush after importing'))],
1990 1994 _('hg qimport [-e] [-n NAME] [-f] [-g] [-P] [-r REV]... FILE...'))
1991 1995 def qimport(ui, repo, *filename, **opts):
1992 1996 """import a patch
1993 1997
1994 1998 The patch is inserted into the series after the last applied
1995 1999 patch. If no patches have been applied, qimport prepends the patch
1996 2000 to the series.
1997 2001
1998 2002 The patch will have the same name as its source file unless you
1999 2003 give it a new one with -n/--name.
2000 2004
2001 2005 You can register an existing patch inside the patch directory with
2002 2006 the -e/--existing flag.
2003 2007
2004 2008 With -f/--force, an existing patch of the same name will be
2005 2009 overwritten.
2006 2010
2007 2011 An existing changeset may be placed under mq control with -r/--rev
2008 2012 (e.g. qimport --rev tip -n patch will place tip under mq control).
2009 2013 With -g/--git, patches imported with --rev will use the git diff
2010 2014 format. See the diffs help topic for information on why this is
2011 2015 important for preserving rename/copy information and permission
2012 2016 changes. Use :hg:`qfinish` to remove changesets from mq control.
2013 2017
2014 2018 To import a patch from standard input, pass - as the patch file.
2015 2019 When importing from standard input, a patch name must be specified
2016 2020 using the --name flag.
2017 2021
2018 2022 To import an existing patch while renaming it::
2019 2023
2020 2024 hg qimport -e existing-patch -n new-name
2021 2025
2022 2026 Returns 0 if import succeeded.
2023 2027 """
2024 2028 lock = repo.lock() # cause this may move phase
2025 2029 try:
2026 2030 q = repo.mq
2027 2031 try:
2028 2032 q.qimport(repo, filename, patchname=opts.get('name'),
2029 2033 existing=opts.get('existing'), force=opts.get('force'),
2030 2034 rev=opts.get('rev'), git=opts.get('git'))
2031 2035 finally:
2032 2036 q.savedirty()
2033 2037
2034 2038
2035 2039 if opts.get('push') and not opts.get('rev'):
2036 2040 return q.push(repo, None)
2037 2041 finally:
2038 2042 lock.release()
2039 2043 return 0
2040 2044
2041 2045 def qinit(ui, repo, create):
2042 2046 """initialize a new queue repository
2043 2047
2044 2048 This command also creates a series file for ordering patches, and
2045 2049 an mq-specific .hgignore file in the queue repository, to exclude
2046 2050 the status and guards files (these contain mostly transient state).
2047 2051
2048 2052 Returns 0 if initialization succeeded."""
2049 2053 q = repo.mq
2050 2054 r = q.init(repo, create)
2051 2055 q.savedirty()
2052 2056 if r:
2053 2057 if not os.path.exists(r.wjoin('.hgignore')):
2054 2058 fp = r.wopener('.hgignore', 'w')
2055 2059 fp.write('^\\.hg\n')
2056 2060 fp.write('^\\.mq\n')
2057 2061 fp.write('syntax: glob\n')
2058 2062 fp.write('status\n')
2059 2063 fp.write('guards\n')
2060 2064 fp.close()
2061 2065 if not os.path.exists(r.wjoin('series')):
2062 2066 r.wopener('series', 'w').close()
2063 2067 r[None].add(['.hgignore', 'series'])
2064 2068 commands.add(ui, r)
2065 2069 return 0
2066 2070
2067 2071 @command("^qinit",
2068 2072 [('c', 'create-repo', None, _('create queue repository'))],
2069 2073 _('hg qinit [-c]'))
2070 2074 def init(ui, repo, **opts):
2071 2075 """init a new queue repository (DEPRECATED)
2072 2076
2073 2077 The queue repository is unversioned by default. If
2074 2078 -c/--create-repo is specified, qinit will create a separate nested
2075 2079 repository for patches (qinit -c may also be run later to convert
2076 2080 an unversioned patch repository into a versioned one). You can use
2077 2081 qcommit to commit changes to this queue repository.
2078 2082
2079 2083 This command is deprecated. Without -c, it's implied by other relevant
2080 2084 commands. With -c, use :hg:`init --mq` instead."""
2081 2085 return qinit(ui, repo, create=opts.get('create_repo'))
2082 2086
2083 2087 @command("qclone",
2084 2088 [('', 'pull', None, _('use pull protocol to copy metadata')),
2085 2089 ('U', 'noupdate', None, _('do not update the new working directories')),
2086 2090 ('', 'uncompressed', None,
2087 2091 _('use uncompressed transfer (fast over LAN)')),
2088 2092 ('p', 'patches', '',
2089 2093 _('location of source patch repository'), _('REPO')),
2090 2094 ] + commands.remoteopts,
2091 2095 _('hg qclone [OPTION]... SOURCE [DEST]'))
2092 2096 def clone(ui, source, dest=None, **opts):
2093 2097 '''clone main and patch repository at same time
2094 2098
2095 2099 If source is local, destination will have no patches applied. If
2096 2100 source is remote, this command can not check if patches are
2097 2101 applied in source, so cannot guarantee that patches are not
2098 2102 applied in destination. If you clone remote repository, be sure
2099 2103 before that it has no patches applied.
2100 2104
2101 2105 Source patch repository is looked for in <src>/.hg/patches by
2102 2106 default. Use -p <url> to change.
2103 2107
2104 2108 The patch directory must be a nested Mercurial repository, as
2105 2109 would be created by :hg:`init --mq`.
2106 2110
2107 2111 Return 0 on success.
2108 2112 '''
2109 2113 def patchdir(repo):
2110 2114 """compute a patch repo url from a repo object"""
2111 2115 url = repo.url()
2112 2116 if url.endswith('/'):
2113 2117 url = url[:-1]
2114 2118 return url + '/.hg/patches'
2115 2119
2116 2120 # main repo (destination and sources)
2117 2121 if dest is None:
2118 2122 dest = hg.defaultdest(source)
2119 2123 sr = hg.repository(hg.remoteui(ui, opts), ui.expandpath(source))
2120 2124
2121 2125 # patches repo (source only)
2122 2126 if opts.get('patches'):
2123 2127 patchespath = ui.expandpath(opts.get('patches'))
2124 2128 else:
2125 2129 patchespath = patchdir(sr)
2126 2130 try:
2127 2131 hg.repository(ui, patchespath)
2128 2132 except error.RepoError:
2129 2133 raise util.Abort(_('versioned patch repository not found'
2130 2134 ' (see init --mq)'))
2131 2135 qbase, destrev = None, None
2132 2136 if sr.local():
2133 2137 if sr.mq.applied and sr[qbase].phase() != phases.secret:
2134 2138 qbase = sr.mq.applied[0].node
2135 2139 if not hg.islocal(dest):
2136 2140 heads = set(sr.heads())
2137 2141 destrev = list(heads.difference(sr.heads(qbase)))
2138 2142 destrev.append(sr.changelog.parents(qbase)[0])
2139 2143 elif sr.capable('lookup'):
2140 2144 try:
2141 2145 qbase = sr.lookup('qbase')
2142 2146 except error.RepoError:
2143 2147 pass
2144 2148
2145 2149 ui.note(_('cloning main repository\n'))
2146 2150 sr, dr = hg.clone(ui, opts, sr.url(), dest,
2147 2151 pull=opts.get('pull'),
2148 2152 rev=destrev,
2149 2153 update=False,
2150 2154 stream=opts.get('uncompressed'))
2151 2155
2152 2156 ui.note(_('cloning patch repository\n'))
2153 2157 hg.clone(ui, opts, opts.get('patches') or patchdir(sr), patchdir(dr),
2154 2158 pull=opts.get('pull'), update=not opts.get('noupdate'),
2155 2159 stream=opts.get('uncompressed'))
2156 2160
2157 2161 if dr.local():
2158 2162 if qbase:
2159 2163 ui.note(_('stripping applied patches from destination '
2160 2164 'repository\n'))
2161 2165 dr.mq.strip(dr, [qbase], update=False, backup=None)
2162 2166 if not opts.get('noupdate'):
2163 2167 ui.note(_('updating destination repository\n'))
2164 2168 hg.update(dr, dr.changelog.tip())
2165 2169
2166 2170 @command("qcommit|qci",
2167 2171 commands.table["^commit|ci"][1],
2168 2172 _('hg qcommit [OPTION]... [FILE]...'))
2169 2173 def commit(ui, repo, *pats, **opts):
2170 2174 """commit changes in the queue repository (DEPRECATED)
2171 2175
2172 2176 This command is deprecated; use :hg:`commit --mq` instead."""
2173 2177 q = repo.mq
2174 2178 r = q.qrepo()
2175 2179 if not r:
2176 2180 raise util.Abort('no queue repository')
2177 2181 commands.commit(r.ui, r, *pats, **opts)
2178 2182
2179 2183 @command("qseries",
2180 2184 [('m', 'missing', None, _('print patches not in series')),
2181 2185 ] + seriesopts,
2182 2186 _('hg qseries [-ms]'))
2183 2187 def series(ui, repo, **opts):
2184 2188 """print the entire series file
2185 2189
2186 2190 Returns 0 on success."""
2187 2191 repo.mq.qseries(repo, missing=opts.get('missing'), summary=opts.get('summary'))
2188 2192 return 0
2189 2193
2190 2194 @command("qtop", seriesopts, _('hg qtop [-s]'))
2191 2195 def top(ui, repo, **opts):
2192 2196 """print the name of the current patch
2193 2197
2194 2198 Returns 0 on success."""
2195 2199 q = repo.mq
2196 2200 t = q.applied and q.seriesend(True) or 0
2197 2201 if t:
2198 2202 q.qseries(repo, start=t - 1, length=1, status='A',
2199 2203 summary=opts.get('summary'))
2200 2204 else:
2201 2205 ui.write(_("no patches applied\n"))
2202 2206 return 1
2203 2207
2204 2208 @command("qnext", seriesopts, _('hg qnext [-s]'))
2205 2209 def next(ui, repo, **opts):
2206 2210 """print the name of the next pushable patch
2207 2211
2208 2212 Returns 0 on success."""
2209 2213 q = repo.mq
2210 2214 end = q.seriesend()
2211 2215 if end == len(q.series):
2212 2216 ui.write(_("all patches applied\n"))
2213 2217 return 1
2214 2218 q.qseries(repo, start=end, length=1, summary=opts.get('summary'))
2215 2219
2216 2220 @command("qprev", seriesopts, _('hg qprev [-s]'))
2217 2221 def prev(ui, repo, **opts):
2218 2222 """print the name of the previous applied patch
2219 2223
2220 2224 Returns 0 on success."""
2221 2225 q = repo.mq
2222 2226 l = len(q.applied)
2223 2227 if l == 1:
2224 2228 ui.write(_("only one patch applied\n"))
2225 2229 return 1
2226 2230 if not l:
2227 2231 ui.write(_("no patches applied\n"))
2228 2232 return 1
2229 2233 idx = q.series.index(q.applied[-2].name)
2230 2234 q.qseries(repo, start=idx, length=1, status='A',
2231 2235 summary=opts.get('summary'))
2232 2236
2233 2237 def setupheaderopts(ui, opts):
2234 2238 if not opts.get('user') and opts.get('currentuser'):
2235 2239 opts['user'] = ui.username()
2236 2240 if not opts.get('date') and opts.get('currentdate'):
2237 2241 opts['date'] = "%d %d" % util.makedate()
2238 2242
2239 2243 @command("^qnew",
2240 2244 [('e', 'edit', None, _('edit commit message')),
2241 2245 ('f', 'force', None, _('import uncommitted changes (DEPRECATED)')),
2242 2246 ('g', 'git', None, _('use git extended diff format')),
2243 2247 ('U', 'currentuser', None, _('add "From: <current user>" to patch')),
2244 2248 ('u', 'user', '',
2245 2249 _('add "From: <USER>" to patch'), _('USER')),
2246 2250 ('D', 'currentdate', None, _('add "Date: <current date>" to patch')),
2247 2251 ('d', 'date', '',
2248 2252 _('add "Date: <DATE>" to patch'), _('DATE'))
2249 2253 ] + commands.walkopts + commands.commitopts,
2250 2254 _('hg qnew [-e] [-m TEXT] [-l FILE] PATCH [FILE]...'))
2251 2255 def new(ui, repo, patch, *args, **opts):
2252 2256 """create a new patch
2253 2257
2254 2258 qnew creates a new patch on top of the currently-applied patch (if
2255 2259 any). The patch will be initialized with any outstanding changes
2256 2260 in the working directory. You may also use -I/--include,
2257 2261 -X/--exclude, and/or a list of files after the patch name to add
2258 2262 only changes to matching files to the new patch, leaving the rest
2259 2263 as uncommitted modifications.
2260 2264
2261 2265 -u/--user and -d/--date can be used to set the (given) user and
2262 2266 date, respectively. -U/--currentuser and -D/--currentdate set user
2263 2267 to current user and date to current date.
2264 2268
2265 2269 -e/--edit, -m/--message or -l/--logfile set the patch header as
2266 2270 well as the commit message. If none is specified, the header is
2267 2271 empty and the commit message is '[mq]: PATCH'.
2268 2272
2269 2273 Use the -g/--git option to keep the patch in the git extended diff
2270 2274 format. Read the diffs help topic for more information on why this
2271 2275 is important for preserving permission changes and copy/rename
2272 2276 information.
2273 2277
2274 2278 Returns 0 on successful creation of a new patch.
2275 2279 """
2276 2280 msg = cmdutil.logmessage(ui, opts)
2277 2281 def getmsg():
2278 2282 return ui.edit(msg, opts.get('user') or ui.username())
2279 2283 q = repo.mq
2280 2284 opts['msg'] = msg
2281 2285 if opts.get('edit'):
2282 2286 opts['msg'] = getmsg
2283 2287 else:
2284 2288 opts['msg'] = msg
2285 2289 setupheaderopts(ui, opts)
2286 2290 q.new(repo, patch, *args, **opts)
2287 2291 q.savedirty()
2288 2292 return 0
2289 2293
2290 2294 @command("^qrefresh",
2291 2295 [('e', 'edit', None, _('edit commit message')),
2292 2296 ('g', 'git', None, _('use git extended diff format')),
2293 2297 ('s', 'short', None,
2294 2298 _('refresh only files already in the patch and specified files')),
2295 2299 ('U', 'currentuser', None,
2296 2300 _('add/update author field in patch with current user')),
2297 2301 ('u', 'user', '',
2298 2302 _('add/update author field in patch with given user'), _('USER')),
2299 2303 ('D', 'currentdate', None,
2300 2304 _('add/update date field in patch with current date')),
2301 2305 ('d', 'date', '',
2302 2306 _('add/update date field in patch with given date'), _('DATE'))
2303 2307 ] + commands.walkopts + commands.commitopts,
2304 2308 _('hg qrefresh [-I] [-X] [-e] [-m TEXT] [-l FILE] [-s] [FILE]...'))
2305 2309 def refresh(ui, repo, *pats, **opts):
2306 2310 """update the current patch
2307 2311
2308 2312 If any file patterns are provided, the refreshed patch will
2309 2313 contain only the modifications that match those patterns; the
2310 2314 remaining modifications will remain in the working directory.
2311 2315
2312 2316 If -s/--short is specified, files currently included in the patch
2313 2317 will be refreshed just like matched files and remain in the patch.
2314 2318
2315 2319 If -e/--edit is specified, Mercurial will start your configured editor for
2316 2320 you to enter a message. In case qrefresh fails, you will find a backup of
2317 2321 your message in ``.hg/last-message.txt``.
2318 2322
2319 2323 hg add/remove/copy/rename work as usual, though you might want to
2320 2324 use git-style patches (-g/--git or [diff] git=1) to track copies
2321 2325 and renames. See the diffs help topic for more information on the
2322 2326 git diff format.
2323 2327
2324 2328 Returns 0 on success.
2325 2329 """
2326 2330 q = repo.mq
2327 2331 message = cmdutil.logmessage(ui, opts)
2328 2332 if opts.get('edit'):
2329 2333 if not q.applied:
2330 2334 ui.write(_("no patches applied\n"))
2331 2335 return 1
2332 2336 if message:
2333 2337 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
2334 2338 patch = q.applied[-1].name
2335 2339 ph = patchheader(q.join(patch), q.plainmode)
2336 2340 message = ui.edit('\n'.join(ph.message), ph.user or ui.username())
2337 2341 # We don't want to lose the patch message if qrefresh fails (issue2062)
2338 2342 repo.savecommitmessage(message)
2339 2343 setupheaderopts(ui, opts)
2340 2344 wlock = repo.wlock()
2341 2345 try:
2342 2346 ret = q.refresh(repo, pats, msg=message, **opts)
2343 2347 q.savedirty()
2344 2348 return ret
2345 2349 finally:
2346 2350 wlock.release()
2347 2351
2348 2352 @command("^qdiff",
2349 2353 commands.diffopts + commands.diffopts2 + commands.walkopts,
2350 2354 _('hg qdiff [OPTION]... [FILE]...'))
2351 2355 def diff(ui, repo, *pats, **opts):
2352 2356 """diff of the current patch and subsequent modifications
2353 2357
2354 2358 Shows a diff which includes the current patch as well as any
2355 2359 changes which have been made in the working directory since the
2356 2360 last refresh (thus showing what the current patch would become
2357 2361 after a qrefresh).
2358 2362
2359 2363 Use :hg:`diff` if you only want to see the changes made since the
2360 2364 last qrefresh, or :hg:`export qtip` if you want to see changes
2361 2365 made by the current patch without including changes made since the
2362 2366 qrefresh.
2363 2367
2364 2368 Returns 0 on success.
2365 2369 """
2366 2370 repo.mq.diff(repo, pats, opts)
2367 2371 return 0
2368 2372
2369 2373 @command('qfold',
2370 2374 [('e', 'edit', None, _('edit patch header')),
2371 2375 ('k', 'keep', None, _('keep folded patch files')),
2372 2376 ] + commands.commitopts,
2373 2377 _('hg qfold [-e] [-k] [-m TEXT] [-l FILE] PATCH...'))
2374 2378 def fold(ui, repo, *files, **opts):
2375 2379 """fold the named patches into the current patch
2376 2380
2377 2381 Patches must not yet be applied. Each patch will be successively
2378 2382 applied to the current patch in the order given. If all the
2379 2383 patches apply successfully, the current patch will be refreshed
2380 2384 with the new cumulative patch, and the folded patches will be
2381 2385 deleted. With -k/--keep, the folded patch files will not be
2382 2386 removed afterwards.
2383 2387
2384 2388 The header for each folded patch will be concatenated with the
2385 2389 current patch header, separated by a line of ``* * *``.
2386 2390
2387 2391 Returns 0 on success."""
2388 2392 q = repo.mq
2389 2393 if not files:
2390 2394 raise util.Abort(_('qfold requires at least one patch name'))
2391 2395 if not q.checktoppatch(repo)[0]:
2392 2396 raise util.Abort(_('no patches applied'))
2393 2397 q.checklocalchanges(repo)
2394 2398
2395 2399 message = cmdutil.logmessage(ui, opts)
2396 2400 if opts.get('edit'):
2397 2401 if message:
2398 2402 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
2399 2403
2400 2404 parent = q.lookup('qtip')
2401 2405 patches = []
2402 2406 messages = []
2403 2407 for f in files:
2404 2408 p = q.lookup(f)
2405 2409 if p in patches or p == parent:
2406 2410 ui.warn(_('Skipping already folded patch %s\n') % p)
2407 2411 if q.isapplied(p):
2408 2412 raise util.Abort(_('qfold cannot fold already applied patch %s') % p)
2409 2413 patches.append(p)
2410 2414
2411 2415 for p in patches:
2412 2416 if not message:
2413 2417 ph = patchheader(q.join(p), q.plainmode)
2414 2418 if ph.message:
2415 2419 messages.append(ph.message)
2416 2420 pf = q.join(p)
2417 2421 (patchsuccess, files, fuzz) = q.patch(repo, pf)
2418 2422 if not patchsuccess:
2419 2423 raise util.Abort(_('error folding patch %s') % p)
2420 2424
2421 2425 if not message:
2422 2426 ph = patchheader(q.join(parent), q.plainmode)
2423 2427 message, user = ph.message, ph.user
2424 2428 for msg in messages:
2425 2429 message.append('* * *')
2426 2430 message.extend(msg)
2427 2431 message = '\n'.join(message)
2428 2432
2429 2433 if opts.get('edit'):
2430 2434 message = ui.edit(message, user or ui.username())
2431 2435
2432 2436 diffopts = q.patchopts(q.diffopts(), *patches)
2433 2437 wlock = repo.wlock()
2434 2438 try:
2435 2439 q.refresh(repo, msg=message, git=diffopts.git)
2436 2440 q.delete(repo, patches, opts)
2437 2441 q.savedirty()
2438 2442 finally:
2439 2443 wlock.release()
2440 2444
2441 2445 @command("qgoto",
2442 2446 [('f', 'force', None, _('overwrite any local changes'))],
2443 2447 _('hg qgoto [OPTION]... PATCH'))
2444 2448 def goto(ui, repo, patch, **opts):
2445 2449 '''push or pop patches until named patch is at top of stack
2446 2450
2447 2451 Returns 0 on success.'''
2448 2452 q = repo.mq
2449 2453 patch = q.lookup(patch)
2450 2454 if q.isapplied(patch):
2451 2455 ret = q.pop(repo, patch, force=opts.get('force'))
2452 2456 else:
2453 2457 ret = q.push(repo, patch, force=opts.get('force'))
2454 2458 q.savedirty()
2455 2459 return ret
2456 2460
2457 2461 @command("qguard",
2458 2462 [('l', 'list', None, _('list all patches and guards')),
2459 2463 ('n', 'none', None, _('drop all guards'))],
2460 2464 _('hg qguard [-l] [-n] [PATCH] [-- [+GUARD]... [-GUARD]...]'))
2461 2465 def guard(ui, repo, *args, **opts):
2462 2466 '''set or print guards for a patch
2463 2467
2464 2468 Guards control whether a patch can be pushed. A patch with no
2465 2469 guards is always pushed. A patch with a positive guard ("+foo") is
2466 2470 pushed only if the :hg:`qselect` command has activated it. A patch with
2467 2471 a negative guard ("-foo") is never pushed if the :hg:`qselect` command
2468 2472 has activated it.
2469 2473
2470 2474 With no arguments, print the currently active guards.
2471 2475 With arguments, set guards for the named patch.
2472 2476
2473 2477 .. note::
2474 2478 Specifying negative guards now requires '--'.
2475 2479
2476 2480 To set guards on another patch::
2477 2481
2478 2482 hg qguard other.patch -- +2.6.17 -stable
2479 2483
2480 2484 Returns 0 on success.
2481 2485 '''
2482 2486 def status(idx):
2483 2487 guards = q.seriesguards[idx] or ['unguarded']
2484 2488 if q.series[idx] in applied:
2485 2489 state = 'applied'
2486 2490 elif q.pushable(idx)[0]:
2487 2491 state = 'unapplied'
2488 2492 else:
2489 2493 state = 'guarded'
2490 2494 label = 'qguard.patch qguard.%s qseries.%s' % (state, state)
2491 2495 ui.write('%s: ' % ui.label(q.series[idx], label))
2492 2496
2493 2497 for i, guard in enumerate(guards):
2494 2498 if guard.startswith('+'):
2495 2499 ui.write(guard, label='qguard.positive')
2496 2500 elif guard.startswith('-'):
2497 2501 ui.write(guard, label='qguard.negative')
2498 2502 else:
2499 2503 ui.write(guard, label='qguard.unguarded')
2500 2504 if i != len(guards) - 1:
2501 2505 ui.write(' ')
2502 2506 ui.write('\n')
2503 2507 q = repo.mq
2504 2508 applied = set(p.name for p in q.applied)
2505 2509 patch = None
2506 2510 args = list(args)
2507 2511 if opts.get('list'):
2508 2512 if args or opts.get('none'):
2509 2513 raise util.Abort(_('cannot mix -l/--list with options or arguments'))
2510 2514 for i in xrange(len(q.series)):
2511 2515 status(i)
2512 2516 return
2513 2517 if not args or args[0][0:1] in '-+':
2514 2518 if not q.applied:
2515 2519 raise util.Abort(_('no patches applied'))
2516 2520 patch = q.applied[-1].name
2517 2521 if patch is None and args[0][0:1] not in '-+':
2518 2522 patch = args.pop(0)
2519 2523 if patch is None:
2520 2524 raise util.Abort(_('no patch to work with'))
2521 2525 if args or opts.get('none'):
2522 2526 idx = q.findseries(patch)
2523 2527 if idx is None:
2524 2528 raise util.Abort(_('no patch named %s') % patch)
2525 2529 q.setguards(idx, args)
2526 2530 q.savedirty()
2527 2531 else:
2528 2532 status(q.series.index(q.lookup(patch)))
2529 2533
2530 2534 @command("qheader", [], _('hg qheader [PATCH]'))
2531 2535 def header(ui, repo, patch=None):
2532 2536 """print the header of the topmost or specified patch
2533 2537
2534 2538 Returns 0 on success."""
2535 2539 q = repo.mq
2536 2540
2537 2541 if patch:
2538 2542 patch = q.lookup(patch)
2539 2543 else:
2540 2544 if not q.applied:
2541 2545 ui.write(_('no patches applied\n'))
2542 2546 return 1
2543 2547 patch = q.lookup('qtip')
2544 2548 ph = patchheader(q.join(patch), q.plainmode)
2545 2549
2546 2550 ui.write('\n'.join(ph.message) + '\n')
2547 2551
2548 2552 def lastsavename(path):
2549 2553 (directory, base) = os.path.split(path)
2550 2554 names = os.listdir(directory)
2551 2555 namere = re.compile("%s.([0-9]+)" % base)
2552 2556 maxindex = None
2553 2557 maxname = None
2554 2558 for f in names:
2555 2559 m = namere.match(f)
2556 2560 if m:
2557 2561 index = int(m.group(1))
2558 2562 if maxindex is None or index > maxindex:
2559 2563 maxindex = index
2560 2564 maxname = f
2561 2565 if maxname:
2562 2566 return (os.path.join(directory, maxname), maxindex)
2563 2567 return (None, None)
2564 2568
2565 2569 def savename(path):
2566 2570 (last, index) = lastsavename(path)
2567 2571 if last is None:
2568 2572 index = 0
2569 2573 newpath = path + ".%d" % (index + 1)
2570 2574 return newpath
2571 2575
2572 2576 @command("^qpush",
2573 2577 [('f', 'force', None, _('apply on top of local changes')),
2574 2578 ('e', 'exact', None, _('apply the target patch to its recorded parent')),
2575 2579 ('l', 'list', None, _('list patch name in commit text')),
2576 2580 ('a', 'all', None, _('apply all patches')),
2577 2581 ('m', 'merge', None, _('merge from another queue (DEPRECATED)')),
2578 2582 ('n', 'name', '',
2579 2583 _('merge queue name (DEPRECATED)'), _('NAME')),
2580 2584 ('', 'move', None, _('reorder patch series and apply only the patch'))],
2581 2585 _('hg qpush [-f] [-l] [-a] [--move] [PATCH | INDEX]'))
2582 2586 def push(ui, repo, patch=None, **opts):
2583 2587 """push the next patch onto the stack
2584 2588
2585 2589 When -f/--force is applied, all local changes in patched files
2586 2590 will be lost.
2587 2591
2588 2592 Return 0 on success.
2589 2593 """
2590 2594 q = repo.mq
2591 2595 mergeq = None
2592 2596
2593 2597 if opts.get('merge'):
2594 2598 if opts.get('name'):
2595 2599 newpath = repo.join(opts.get('name'))
2596 2600 else:
2597 2601 newpath, i = lastsavename(q.path)
2598 2602 if not newpath:
2599 2603 ui.warn(_("no saved queues found, please use -n\n"))
2600 2604 return 1
2601 2605 mergeq = queue(ui, repo.path, newpath)
2602 2606 ui.warn(_("merging with queue at: %s\n") % mergeq.path)
2603 2607 ret = q.push(repo, patch, force=opts.get('force'), list=opts.get('list'),
2604 2608 mergeq=mergeq, all=opts.get('all'), move=opts.get('move'),
2605 2609 exact=opts.get('exact'))
2606 2610 return ret
2607 2611
2608 2612 @command("^qpop",
2609 2613 [('a', 'all', None, _('pop all patches')),
2610 2614 ('n', 'name', '',
2611 2615 _('queue name to pop (DEPRECATED)'), _('NAME')),
2612 2616 ('f', 'force', None, _('forget any local changes to patched files'))],
2613 2617 _('hg qpop [-a] [-f] [PATCH | INDEX]'))
2614 2618 def pop(ui, repo, patch=None, **opts):
2615 2619 """pop the current patch off the stack
2616 2620
2617 2621 By default, pops off the top of the patch stack. If given a patch
2618 2622 name, keeps popping off patches until the named patch is at the
2619 2623 top of the stack.
2620 2624
2621 2625 Return 0 on success.
2622 2626 """
2623 2627 localupdate = True
2624 2628 if opts.get('name'):
2625 2629 q = queue(ui, repo.path, repo.join(opts.get('name')))
2626 2630 ui.warn(_('using patch queue: %s\n') % q.path)
2627 2631 localupdate = False
2628 2632 else:
2629 2633 q = repo.mq
2630 2634 ret = q.pop(repo, patch, force=opts.get('force'), update=localupdate,
2631 2635 all=opts.get('all'))
2632 2636 q.savedirty()
2633 2637 return ret
2634 2638
2635 2639 @command("qrename|qmv", [], _('hg qrename PATCH1 [PATCH2]'))
2636 2640 def rename(ui, repo, patch, name=None, **opts):
2637 2641 """rename a patch
2638 2642
2639 2643 With one argument, renames the current patch to PATCH1.
2640 2644 With two arguments, renames PATCH1 to PATCH2.
2641 2645
2642 2646 Returns 0 on success."""
2643 2647 q = repo.mq
2644 2648 if not name:
2645 2649 name = patch
2646 2650 patch = None
2647 2651
2648 2652 if patch:
2649 2653 patch = q.lookup(patch)
2650 2654 else:
2651 2655 if not q.applied:
2652 2656 ui.write(_('no patches applied\n'))
2653 2657 return
2654 2658 patch = q.lookup('qtip')
2655 2659 absdest = q.join(name)
2656 2660 if os.path.isdir(absdest):
2657 2661 name = normname(os.path.join(name, os.path.basename(patch)))
2658 2662 absdest = q.join(name)
2659 2663 q.checkpatchname(name)
2660 2664
2661 2665 ui.note(_('renaming %s to %s\n') % (patch, name))
2662 2666 i = q.findseries(patch)
2663 2667 guards = q.guard_re.findall(q.fullseries[i])
2664 2668 q.fullseries[i] = name + ''.join([' #' + g for g in guards])
2665 2669 q.parseseries()
2666 2670 q.seriesdirty = True
2667 2671
2668 2672 info = q.isapplied(patch)
2669 2673 if info:
2670 2674 q.applied[info[0]] = statusentry(info[1], name)
2671 2675 q.applieddirty = True
2672 2676
2673 2677 destdir = os.path.dirname(absdest)
2674 2678 if not os.path.isdir(destdir):
2675 2679 os.makedirs(destdir)
2676 2680 util.rename(q.join(patch), absdest)
2677 2681 r = q.qrepo()
2678 2682 if r and patch in r.dirstate:
2679 2683 wctx = r[None]
2680 2684 wlock = r.wlock()
2681 2685 try:
2682 2686 if r.dirstate[patch] == 'a':
2683 2687 r.dirstate.drop(patch)
2684 2688 r.dirstate.add(name)
2685 2689 else:
2686 2690 wctx.copy(patch, name)
2687 2691 wctx.forget([patch])
2688 2692 finally:
2689 2693 wlock.release()
2690 2694
2691 2695 q.savedirty()
2692 2696
2693 2697 @command("qrestore",
2694 2698 [('d', 'delete', None, _('delete save entry')),
2695 2699 ('u', 'update', None, _('update queue working directory'))],
2696 2700 _('hg qrestore [-d] [-u] REV'))
2697 2701 def restore(ui, repo, rev, **opts):
2698 2702 """restore the queue state saved by a revision (DEPRECATED)
2699 2703
2700 2704 This command is deprecated, use :hg:`rebase` instead."""
2701 2705 rev = repo.lookup(rev)
2702 2706 q = repo.mq
2703 2707 q.restore(repo, rev, delete=opts.get('delete'),
2704 2708 qupdate=opts.get('update'))
2705 2709 q.savedirty()
2706 2710 return 0
2707 2711
2708 2712 @command("qsave",
2709 2713 [('c', 'copy', None, _('copy patch directory')),
2710 2714 ('n', 'name', '',
2711 2715 _('copy directory name'), _('NAME')),
2712 2716 ('e', 'empty', None, _('clear queue status file')),
2713 2717 ('f', 'force', None, _('force copy'))] + commands.commitopts,
2714 2718 _('hg qsave [-m TEXT] [-l FILE] [-c] [-n NAME] [-e] [-f]'))
2715 2719 def save(ui, repo, **opts):
2716 2720 """save current queue state (DEPRECATED)
2717 2721
2718 2722 This command is deprecated, use :hg:`rebase` instead."""
2719 2723 q = repo.mq
2720 2724 message = cmdutil.logmessage(ui, opts)
2721 2725 ret = q.save(repo, msg=message)
2722 2726 if ret:
2723 2727 return ret
2724 2728 q.savedirty() # save to .hg/patches before copying
2725 2729 if opts.get('copy'):
2726 2730 path = q.path
2727 2731 if opts.get('name'):
2728 2732 newpath = os.path.join(q.basepath, opts.get('name'))
2729 2733 if os.path.exists(newpath):
2730 2734 if not os.path.isdir(newpath):
2731 2735 raise util.Abort(_('destination %s exists and is not '
2732 2736 'a directory') % newpath)
2733 2737 if not opts.get('force'):
2734 2738 raise util.Abort(_('destination %s exists, '
2735 2739 'use -f to force') % newpath)
2736 2740 else:
2737 2741 newpath = savename(path)
2738 2742 ui.warn(_("copy %s to %s\n") % (path, newpath))
2739 2743 util.copyfiles(path, newpath)
2740 2744 if opts.get('empty'):
2741 2745 del q.applied[:]
2742 2746 q.applieddirty = True
2743 2747 q.savedirty()
2744 2748 return 0
2745 2749
2746 2750 @command("strip",
2747 2751 [
2748 2752 ('r', 'rev', [], _('strip specified revision (optional, '
2749 2753 'can specify revisions without this '
2750 2754 'option)'), _('REV')),
2751 2755 ('f', 'force', None, _('force removal of changesets, discard '
2752 2756 'uncommitted changes (no backup)')),
2753 2757 ('b', 'backup', None, _('bundle only changesets with local revision'
2754 2758 ' number greater than REV which are not'
2755 2759 ' descendants of REV (DEPRECATED)')),
2756 2760 ('n', 'no-backup', None, _('no backups')),
2757 2761 ('', 'nobackup', None, _('no backups (DEPRECATED)')),
2758 2762 ('k', 'keep', None, _("do not modify working copy during strip"))],
2759 2763 _('hg strip [-k] [-f] [-n] REV...'))
2760 2764 def strip(ui, repo, *revs, **opts):
2761 2765 """strip changesets and all their descendants from the repository
2762 2766
2763 2767 The strip command removes the specified changesets and all their
2764 2768 descendants. If the working directory has uncommitted changes, the
2765 2769 operation is aborted unless the --force flag is supplied, in which
2766 2770 case changes will be discarded.
2767 2771
2768 2772 If a parent of the working directory is stripped, then the working
2769 2773 directory will automatically be updated to the most recent
2770 2774 available ancestor of the stripped parent after the operation
2771 2775 completes.
2772 2776
2773 2777 Any stripped changesets are stored in ``.hg/strip-backup`` as a
2774 2778 bundle (see :hg:`help bundle` and :hg:`help unbundle`). They can
2775 2779 be restored by running :hg:`unbundle .hg/strip-backup/BUNDLE`,
2776 2780 where BUNDLE is the bundle file created by the strip. Note that
2777 2781 the local revision numbers will in general be different after the
2778 2782 restore.
2779 2783
2780 2784 Use the --no-backup option to discard the backup bundle once the
2781 2785 operation completes.
2782 2786
2783 2787 Return 0 on success.
2784 2788 """
2785 2789 backup = 'all'
2786 2790 if opts.get('backup'):
2787 2791 backup = 'strip'
2788 2792 elif opts.get('no_backup') or opts.get('nobackup'):
2789 2793 backup = 'none'
2790 2794
2791 2795 cl = repo.changelog
2792 2796 revs = list(revs) + opts.get('rev')
2793 2797 revs = set(scmutil.revrange(repo, revs))
2794 2798 if not revs:
2795 2799 raise util.Abort(_('empty revision set'))
2796 2800
2797 2801 descendants = set(cl.descendants(*revs))
2798 2802 strippedrevs = revs.union(descendants)
2799 2803 roots = revs.difference(descendants)
2800 2804
2801 2805 update = False
2802 2806 # if one of the wdir parent is stripped we'll need
2803 2807 # to update away to an earlier revision
2804 2808 for p in repo.dirstate.parents():
2805 2809 if p != nullid and cl.rev(p) in strippedrevs:
2806 2810 update = True
2807 2811 break
2808 2812
2809 2813 rootnodes = set(cl.node(r) for r in roots)
2810 2814
2811 2815 q = repo.mq
2812 2816 if q.applied:
2813 2817 # refresh queue state if we're about to strip
2814 2818 # applied patches
2815 2819 if cl.rev(repo.lookup('qtip')) in strippedrevs:
2816 2820 q.applieddirty = True
2817 2821 start = 0
2818 2822 end = len(q.applied)
2819 2823 for i, statusentry in enumerate(q.applied):
2820 2824 if statusentry.node in rootnodes:
2821 2825 # if one of the stripped roots is an applied
2822 2826 # patch, only part of the queue is stripped
2823 2827 start = i
2824 2828 break
2825 2829 del q.applied[start:end]
2826 2830 q.savedirty()
2827 2831
2828 2832 revs = list(rootnodes)
2829 2833 if update and opts.get('keep'):
2830 2834 wlock = repo.wlock()
2831 2835 try:
2832 2836 urev = repo.mq.qparents(repo, revs[0])
2833 2837 repo.dirstate.rebuild(urev, repo[urev].manifest())
2834 2838 repo.dirstate.write()
2835 2839 update = False
2836 2840 finally:
2837 2841 wlock.release()
2838 2842
2839 2843 repo.mq.strip(repo, revs, backup=backup, update=update,
2840 2844 force=opts.get('force'))
2841 2845 return 0
2842 2846
2843 2847 @command("qselect",
2844 2848 [('n', 'none', None, _('disable all guards')),
2845 2849 ('s', 'series', None, _('list all guards in series file')),
2846 2850 ('', 'pop', None, _('pop to before first guarded applied patch')),
2847 2851 ('', 'reapply', None, _('pop, then reapply patches'))],
2848 2852 _('hg qselect [OPTION]... [GUARD]...'))
2849 2853 def select(ui, repo, *args, **opts):
2850 2854 '''set or print guarded patches to push
2851 2855
2852 2856 Use the :hg:`qguard` command to set or print guards on patch, then use
2853 2857 qselect to tell mq which guards to use. A patch will be pushed if
2854 2858 it has no guards or any positive guards match the currently
2855 2859 selected guard, but will not be pushed if any negative guards
2856 2860 match the current guard. For example::
2857 2861
2858 2862 qguard foo.patch -- -stable (negative guard)
2859 2863 qguard bar.patch +stable (positive guard)
2860 2864 qselect stable
2861 2865
2862 2866 This activates the "stable" guard. mq will skip foo.patch (because
2863 2867 it has a negative match) but push bar.patch (because it has a
2864 2868 positive match).
2865 2869
2866 2870 With no arguments, prints the currently active guards.
2867 2871 With one argument, sets the active guard.
2868 2872
2869 2873 Use -n/--none to deactivate guards (no other arguments needed).
2870 2874 When no guards are active, patches with positive guards are
2871 2875 skipped and patches with negative guards are pushed.
2872 2876
2873 2877 qselect can change the guards on applied patches. It does not pop
2874 2878 guarded patches by default. Use --pop to pop back to the last
2875 2879 applied patch that is not guarded. Use --reapply (which implies
2876 2880 --pop) to push back to the current patch afterwards, but skip
2877 2881 guarded patches.
2878 2882
2879 2883 Use -s/--series to print a list of all guards in the series file
2880 2884 (no other arguments needed). Use -v for more information.
2881 2885
2882 2886 Returns 0 on success.'''
2883 2887
2884 2888 q = repo.mq
2885 2889 guards = q.active()
2886 2890 if args or opts.get('none'):
2887 2891 old_unapplied = q.unapplied(repo)
2888 2892 old_guarded = [i for i in xrange(len(q.applied)) if
2889 2893 not q.pushable(i)[0]]
2890 2894 q.setactive(args)
2891 2895 q.savedirty()
2892 2896 if not args:
2893 2897 ui.status(_('guards deactivated\n'))
2894 2898 if not opts.get('pop') and not opts.get('reapply'):
2895 2899 unapplied = q.unapplied(repo)
2896 2900 guarded = [i for i in xrange(len(q.applied))
2897 2901 if not q.pushable(i)[0]]
2898 2902 if len(unapplied) != len(old_unapplied):
2899 2903 ui.status(_('number of unguarded, unapplied patches has '
2900 2904 'changed from %d to %d\n') %
2901 2905 (len(old_unapplied), len(unapplied)))
2902 2906 if len(guarded) != len(old_guarded):
2903 2907 ui.status(_('number of guarded, applied patches has changed '
2904 2908 'from %d to %d\n') %
2905 2909 (len(old_guarded), len(guarded)))
2906 2910 elif opts.get('series'):
2907 2911 guards = {}
2908 2912 noguards = 0
2909 2913 for gs in q.seriesguards:
2910 2914 if not gs:
2911 2915 noguards += 1
2912 2916 for g in gs:
2913 2917 guards.setdefault(g, 0)
2914 2918 guards[g] += 1
2915 2919 if ui.verbose:
2916 2920 guards['NONE'] = noguards
2917 2921 guards = guards.items()
2918 2922 guards.sort(key=lambda x: x[0][1:])
2919 2923 if guards:
2920 2924 ui.note(_('guards in series file:\n'))
2921 2925 for guard, count in guards:
2922 2926 ui.note('%2d ' % count)
2923 2927 ui.write(guard, '\n')
2924 2928 else:
2925 2929 ui.note(_('no guards in series file\n'))
2926 2930 else:
2927 2931 if guards:
2928 2932 ui.note(_('active guards:\n'))
2929 2933 for g in guards:
2930 2934 ui.write(g, '\n')
2931 2935 else:
2932 2936 ui.write(_('no active guards\n'))
2933 2937 reapply = opts.get('reapply') and q.applied and q.appliedname(-1)
2934 2938 popped = False
2935 2939 if opts.get('pop') or opts.get('reapply'):
2936 2940 for i in xrange(len(q.applied)):
2937 2941 pushable, reason = q.pushable(i)
2938 2942 if not pushable:
2939 2943 ui.status(_('popping guarded patches\n'))
2940 2944 popped = True
2941 2945 if i == 0:
2942 2946 q.pop(repo, all=True)
2943 2947 else:
2944 2948 q.pop(repo, str(i - 1))
2945 2949 break
2946 2950 if popped:
2947 2951 try:
2948 2952 if reapply:
2949 2953 ui.status(_('reapplying unguarded patches\n'))
2950 2954 q.push(repo, reapply)
2951 2955 finally:
2952 2956 q.savedirty()
2953 2957
2954 2958 @command("qfinish",
2955 2959 [('a', 'applied', None, _('finish all applied changesets'))],
2956 2960 _('hg qfinish [-a] [REV]...'))
2957 2961 def finish(ui, repo, *revrange, **opts):
2958 2962 """move applied patches into repository history
2959 2963
2960 2964 Finishes the specified revisions (corresponding to applied
2961 2965 patches) by moving them out of mq control into regular repository
2962 2966 history.
2963 2967
2964 2968 Accepts a revision range or the -a/--applied option. If --applied
2965 2969 is specified, all applied mq revisions are removed from mq
2966 2970 control. Otherwise, the given revisions must be at the base of the
2967 2971 stack of applied patches.
2968 2972
2969 2973 This can be especially useful if your changes have been applied to
2970 2974 an upstream repository, or if you are about to push your changes
2971 2975 to upstream.
2972 2976
2973 2977 Returns 0 on success.
2974 2978 """
2975 2979 if not opts.get('applied') and not revrange:
2976 2980 raise util.Abort(_('no revisions specified'))
2977 2981 elif opts.get('applied'):
2978 2982 revrange = ('qbase::qtip',) + revrange
2979 2983
2980 2984 q = repo.mq
2981 2985 if not q.applied:
2982 2986 ui.status(_('no patches applied\n'))
2983 2987 return 0
2984 2988
2985 2989 revs = scmutil.revrange(repo, revrange)
2986 2990 if repo['.'].rev() in revs and repo[None].files():
2987 2991 ui.warn(_('warning: uncommitted changes in the working directory\n'))
2988 2992 # queue.finish may changes phases but leave the responsability to lock the
2989 2993 # repo to the caller to avoid deadlock with wlock. This command code is
2990 2994 # responsability for this locking.
2991 2995 lock = repo.lock()
2992 2996 try:
2993 2997 q.finish(repo, revs)
2994 2998 q.savedirty()
2995 2999 finally:
2996 3000 lock.release()
2997 3001 return 0
2998 3002
2999 3003 @command("qqueue",
3000 3004 [('l', 'list', False, _('list all available queues')),
3001 3005 ('', 'active', False, _('print name of active queue')),
3002 3006 ('c', 'create', False, _('create new queue')),
3003 3007 ('', 'rename', False, _('rename active queue')),
3004 3008 ('', 'delete', False, _('delete reference to queue')),
3005 3009 ('', 'purge', False, _('delete queue, and remove patch dir')),
3006 3010 ],
3007 3011 _('[OPTION] [QUEUE]'))
3008 3012 def qqueue(ui, repo, name=None, **opts):
3009 3013 '''manage multiple patch queues
3010 3014
3011 3015 Supports switching between different patch queues, as well as creating
3012 3016 new patch queues and deleting existing ones.
3013 3017
3014 3018 Omitting a queue name or specifying -l/--list will show you the registered
3015 3019 queues - by default the "normal" patches queue is registered. The currently
3016 3020 active queue will be marked with "(active)". Specifying --active will print
3017 3021 only the name of the active queue.
3018 3022
3019 3023 To create a new queue, use -c/--create. The queue is automatically made
3020 3024 active, except in the case where there are applied patches from the
3021 3025 currently active queue in the repository. Then the queue will only be
3022 3026 created and switching will fail.
3023 3027
3024 3028 To delete an existing queue, use --delete. You cannot delete the currently
3025 3029 active queue.
3026 3030
3027 3031 Returns 0 on success.
3028 3032 '''
3029 3033 q = repo.mq
3030 3034 _defaultqueue = 'patches'
3031 3035 _allqueues = 'patches.queues'
3032 3036 _activequeue = 'patches.queue'
3033 3037
3034 3038 def _getcurrent():
3035 3039 cur = os.path.basename(q.path)
3036 3040 if cur.startswith('patches-'):
3037 3041 cur = cur[8:]
3038 3042 return cur
3039 3043
3040 3044 def _noqueues():
3041 3045 try:
3042 3046 fh = repo.opener(_allqueues, 'r')
3043 3047 fh.close()
3044 3048 except IOError:
3045 3049 return True
3046 3050
3047 3051 return False
3048 3052
3049 3053 def _getqueues():
3050 3054 current = _getcurrent()
3051 3055
3052 3056 try:
3053 3057 fh = repo.opener(_allqueues, 'r')
3054 3058 queues = [queue.strip() for queue in fh if queue.strip()]
3055 3059 fh.close()
3056 3060 if current not in queues:
3057 3061 queues.append(current)
3058 3062 except IOError:
3059 3063 queues = [_defaultqueue]
3060 3064
3061 3065 return sorted(queues)
3062 3066
3063 3067 def _setactive(name):
3064 3068 if q.applied:
3065 3069 raise util.Abort(_('patches applied - cannot set new queue active'))
3066 3070 _setactivenocheck(name)
3067 3071
3068 3072 def _setactivenocheck(name):
3069 3073 fh = repo.opener(_activequeue, 'w')
3070 3074 if name != 'patches':
3071 3075 fh.write(name)
3072 3076 fh.close()
3073 3077
3074 3078 def _addqueue(name):
3075 3079 fh = repo.opener(_allqueues, 'a')
3076 3080 fh.write('%s\n' % (name,))
3077 3081 fh.close()
3078 3082
3079 3083 def _queuedir(name):
3080 3084 if name == 'patches':
3081 3085 return repo.join('patches')
3082 3086 else:
3083 3087 return repo.join('patches-' + name)
3084 3088
3085 3089 def _validname(name):
3086 3090 for n in name:
3087 3091 if n in ':\\/.':
3088 3092 return False
3089 3093 return True
3090 3094
3091 3095 def _delete(name):
3092 3096 if name not in existing:
3093 3097 raise util.Abort(_('cannot delete queue that does not exist'))
3094 3098
3095 3099 current = _getcurrent()
3096 3100
3097 3101 if name == current:
3098 3102 raise util.Abort(_('cannot delete currently active queue'))
3099 3103
3100 3104 fh = repo.opener('patches.queues.new', 'w')
3101 3105 for queue in existing:
3102 3106 if queue == name:
3103 3107 continue
3104 3108 fh.write('%s\n' % (queue,))
3105 3109 fh.close()
3106 3110 util.rename(repo.join('patches.queues.new'), repo.join(_allqueues))
3107 3111
3108 3112 if not name or opts.get('list') or opts.get('active'):
3109 3113 current = _getcurrent()
3110 3114 if opts.get('active'):
3111 3115 ui.write('%s\n' % (current,))
3112 3116 return
3113 3117 for queue in _getqueues():
3114 3118 ui.write('%s' % (queue,))
3115 3119 if queue == current and not ui.quiet:
3116 3120 ui.write(_(' (active)\n'))
3117 3121 else:
3118 3122 ui.write('\n')
3119 3123 return
3120 3124
3121 3125 if not _validname(name):
3122 3126 raise util.Abort(
3123 3127 _('invalid queue name, may not contain the characters ":\\/."'))
3124 3128
3125 3129 existing = _getqueues()
3126 3130
3127 3131 if opts.get('create'):
3128 3132 if name in existing:
3129 3133 raise util.Abort(_('queue "%s" already exists') % name)
3130 3134 if _noqueues():
3131 3135 _addqueue(_defaultqueue)
3132 3136 _addqueue(name)
3133 3137 _setactive(name)
3134 3138 elif opts.get('rename'):
3135 3139 current = _getcurrent()
3136 3140 if name == current:
3137 3141 raise util.Abort(_('can\'t rename "%s" to its current name') % name)
3138 3142 if name in existing:
3139 3143 raise util.Abort(_('queue "%s" already exists') % name)
3140 3144
3141 3145 olddir = _queuedir(current)
3142 3146 newdir = _queuedir(name)
3143 3147
3144 3148 if os.path.exists(newdir):
3145 3149 raise util.Abort(_('non-queue directory "%s" already exists') %
3146 3150 newdir)
3147 3151
3148 3152 fh = repo.opener('patches.queues.new', 'w')
3149 3153 for queue in existing:
3150 3154 if queue == current:
3151 3155 fh.write('%s\n' % (name,))
3152 3156 if os.path.exists(olddir):
3153 3157 util.rename(olddir, newdir)
3154 3158 else:
3155 3159 fh.write('%s\n' % (queue,))
3156 3160 fh.close()
3157 3161 util.rename(repo.join('patches.queues.new'), repo.join(_allqueues))
3158 3162 _setactivenocheck(name)
3159 3163 elif opts.get('delete'):
3160 3164 _delete(name)
3161 3165 elif opts.get('purge'):
3162 3166 if name in existing:
3163 3167 _delete(name)
3164 3168 qdir = _queuedir(name)
3165 3169 if os.path.exists(qdir):
3166 3170 shutil.rmtree(qdir)
3167 3171 else:
3168 3172 if name not in existing:
3169 3173 raise util.Abort(_('use --create to create a new queue'))
3170 3174 _setactive(name)
3171 3175
3172 3176 def mqphasedefaults(repo, roots):
3173 3177 """callback used to set mq changeset as secret when no phase data exists"""
3174 3178 if repo.mq.applied:
3175 3179 if repo.ui.configbool('mq', 'secret', False):
3176 3180 mqphase = phases.secret
3177 3181 else:
3178 3182 mqphase = phases.draft
3179 3183 qbase = repo[repo.mq.applied[0].node]
3180 3184 roots[mqphase].add(qbase.node())
3181 3185 return roots
3182 3186
3183 3187 def reposetup(ui, repo):
3184 3188 class mqrepo(repo.__class__):
3185 3189 @util.propertycache
3186 3190 def mq(self):
3187 3191 return queue(self.ui, self.path)
3188 3192
3189 3193 def abortifwdirpatched(self, errmsg, force=False):
3190 3194 if self.mq.applied and not force:
3191 3195 parents = self.dirstate.parents()
3192 3196 patches = [s.node for s in self.mq.applied]
3193 3197 if parents[0] in patches or parents[1] in patches:
3194 3198 raise util.Abort(errmsg)
3195 3199
3196 3200 def commit(self, text="", user=None, date=None, match=None,
3197 3201 force=False, editor=False, extra={}):
3198 3202 self.abortifwdirpatched(
3199 3203 _('cannot commit over an applied mq patch'),
3200 3204 force)
3201 3205
3202 3206 return super(mqrepo, self).commit(text, user, date, match, force,
3203 3207 editor, extra)
3204 3208
3205 3209 def checkpush(self, force, revs):
3206 3210 if self.mq.applied and not force:
3207 3211 outapplied = [e.node for e in self.mq.applied]
3208 3212 if revs:
3209 3213 # Assume applied patches have no non-patch descendants and
3210 3214 # are not on remote already. Filtering any changeset not
3211 3215 # pushed.
3212 3216 heads = set(revs)
3213 3217 for node in reversed(outapplied):
3214 3218 if node in heads:
3215 3219 break
3216 3220 else:
3217 3221 outapplied.pop()
3218 3222 # looking for pushed and shared changeset
3219 3223 for node in outapplied:
3220 3224 if repo[node].phase() < phases.secret:
3221 3225 raise util.Abort(_('source has mq patches applied'))
3222 3226 # no non-secret patches pushed
3223 3227 super(mqrepo, self).checkpush(force, revs)
3224 3228
3225 3229 def _findtags(self):
3226 3230 '''augment tags from base class with patch tags'''
3227 3231 result = super(mqrepo, self)._findtags()
3228 3232
3229 3233 q = self.mq
3230 3234 if not q.applied:
3231 3235 return result
3232 3236
3233 3237 mqtags = [(patch.node, patch.name) for patch in q.applied]
3234 3238
3235 3239 try:
3236 3240 self.changelog.rev(mqtags[-1][0])
3237 3241 except error.LookupError:
3238 3242 self.ui.warn(_('mq status file refers to unknown node %s\n')
3239 3243 % short(mqtags[-1][0]))
3240 3244 return result
3241 3245
3242 3246 mqtags.append((mqtags[-1][0], 'qtip'))
3243 3247 mqtags.append((mqtags[0][0], 'qbase'))
3244 3248 mqtags.append((self.changelog.parents(mqtags[0][0])[0], 'qparent'))
3245 3249 tags = result[0]
3246 3250 for patch in mqtags:
3247 3251 if patch[1] in tags:
3248 3252 self.ui.warn(_('Tag %s overrides mq patch of the same name\n')
3249 3253 % patch[1])
3250 3254 else:
3251 3255 tags[patch[1]] = patch[0]
3252 3256
3253 3257 return result
3254 3258
3255 3259 def _branchtags(self, partial, lrev):
3256 3260 q = self.mq
3261 cl = self.changelog
3262 qbase = None
3257 3263 if not q.applied:
3258 return super(mqrepo, self)._branchtags(partial, lrev)
3259
3260 cl = self.changelog
3264 if getattr(self, '_committingpatch', False):
3265 # Committing a new patch, must be tip
3266 qbase = len(cl) - 1
3267 else:
3261 3268 qbasenode = q.applied[0].node
3262 3269 try:
3263 3270 qbase = cl.rev(qbasenode)
3264 3271 except error.LookupError:
3265 3272 self.ui.warn(_('mq status file refers to unknown node %s\n')
3266 3273 % short(qbasenode))
3274 if qbase is None:
3267 3275 return super(mqrepo, self)._branchtags(partial, lrev)
3268 3276
3269 3277 start = lrev + 1
3270 3278 if start < qbase:
3271 3279 # update the cache (excluding the patches) and save it
3272 3280 ctxgen = (self[r] for r in xrange(lrev + 1, qbase))
3273 3281 self._updatebranchcache(partial, ctxgen)
3274 3282 self._writebranchcache(partial, cl.node(qbase - 1), qbase - 1)
3275 3283 start = qbase
3276 3284 # if start = qbase, the cache is as updated as it should be.
3277 3285 # if start > qbase, the cache includes (part of) the patches.
3278 3286 # we might as well use it, but we won't save it.
3279 3287
3280 3288 # update the cache up to the tip
3281 3289 ctxgen = (self[r] for r in xrange(start, len(cl)))
3282 3290 self._updatebranchcache(partial, ctxgen)
3283 3291
3284 3292 return partial
3285 3293
3286 3294 if repo.local():
3287 3295 repo.__class__ = mqrepo
3288 3296
3289 3297 repo._phasedefaults.append(mqphasedefaults)
3290 3298
3291 3299 def mqimport(orig, ui, repo, *args, **kwargs):
3292 3300 if (hasattr(repo, 'abortifwdirpatched')
3293 3301 and not kwargs.get('no_commit', False)):
3294 3302 repo.abortifwdirpatched(_('cannot import over an applied patch'),
3295 3303 kwargs.get('force'))
3296 3304 return orig(ui, repo, *args, **kwargs)
3297 3305
3298 3306 def mqinit(orig, ui, *args, **kwargs):
3299 3307 mq = kwargs.pop('mq', None)
3300 3308
3301 3309 if not mq:
3302 3310 return orig(ui, *args, **kwargs)
3303 3311
3304 3312 if args:
3305 3313 repopath = args[0]
3306 3314 if not hg.islocal(repopath):
3307 3315 raise util.Abort(_('only a local queue repository '
3308 3316 'may be initialized'))
3309 3317 else:
3310 3318 repopath = cmdutil.findrepo(os.getcwd())
3311 3319 if not repopath:
3312 3320 raise util.Abort(_('there is no Mercurial repository here '
3313 3321 '(.hg not found)'))
3314 3322 repo = hg.repository(ui, repopath)
3315 3323 return qinit(ui, repo, True)
3316 3324
3317 3325 def mqcommand(orig, ui, repo, *args, **kwargs):
3318 3326 """Add --mq option to operate on patch repository instead of main"""
3319 3327
3320 3328 # some commands do not like getting unknown options
3321 3329 mq = kwargs.pop('mq', None)
3322 3330
3323 3331 if not mq:
3324 3332 return orig(ui, repo, *args, **kwargs)
3325 3333
3326 3334 q = repo.mq
3327 3335 r = q.qrepo()
3328 3336 if not r:
3329 3337 raise util.Abort(_('no queue repository'))
3330 3338 return orig(r.ui, r, *args, **kwargs)
3331 3339
3332 3340 def summary(orig, ui, repo, *args, **kwargs):
3333 3341 r = orig(ui, repo, *args, **kwargs)
3334 3342 q = repo.mq
3335 3343 m = []
3336 3344 a, u = len(q.applied), len(q.unapplied(repo))
3337 3345 if a:
3338 3346 m.append(ui.label(_("%d applied"), 'qseries.applied') % a)
3339 3347 if u:
3340 3348 m.append(ui.label(_("%d unapplied"), 'qseries.unapplied') % u)
3341 3349 if m:
3342 3350 ui.write("mq: %s\n" % ', '.join(m))
3343 3351 else:
3344 3352 ui.note(_("mq: (empty queue)\n"))
3345 3353 return r
3346 3354
3347 3355 def revsetmq(repo, subset, x):
3348 3356 """``mq()``
3349 3357 Changesets managed by MQ.
3350 3358 """
3351 3359 revset.getargs(x, 0, 0, _("mq takes no arguments"))
3352 3360 applied = set([repo[r.node].rev() for r in repo.mq.applied])
3353 3361 return [r for r in subset if r in applied]
3354 3362
3355 3363 def extsetup(ui):
3356 3364 revset.symbols['mq'] = revsetmq
3357 3365
3358 3366 # tell hggettext to extract docstrings from these functions:
3359 3367 i18nfunctions = [revsetmq]
3360 3368
3361 3369 def uisetup(ui):
3362 3370 mqopt = [('', 'mq', None, _("operate on patch repository"))]
3363 3371
3364 3372 extensions.wrapcommand(commands.table, 'import', mqimport)
3365 3373 extensions.wrapcommand(commands.table, 'summary', summary)
3366 3374
3367 3375 entry = extensions.wrapcommand(commands.table, 'init', mqinit)
3368 3376 entry[1].extend(mqopt)
3369 3377
3370 3378 nowrap = set(commands.norepo.split(" "))
3371 3379
3372 3380 def dotable(cmdtable):
3373 3381 for cmd in cmdtable.keys():
3374 3382 cmd = cmdutil.parsealiases(cmd)[0]
3375 3383 if cmd in nowrap:
3376 3384 continue
3377 3385 entry = extensions.wrapcommand(cmdtable, cmd, mqcommand)
3378 3386 entry[1].extend(mqopt)
3379 3387
3380 3388 dotable(commands.table)
3381 3389
3382 3390 for extname, extmodule in extensions.extensions():
3383 3391 if extmodule.__file__ != __file__:
3384 3392 dotable(getattr(extmodule, 'cmdtable', {}))
3385 3393
3386 3394
3387 3395 colortable = {'qguard.negative': 'red',
3388 3396 'qguard.positive': 'yellow',
3389 3397 'qguard.unguarded': 'green',
3390 3398 'qseries.applied': 'blue bold underline',
3391 3399 'qseries.guarded': 'black bold',
3392 3400 'qseries.missing': 'red bold',
3393 3401 'qseries.unapplied': 'black bold'}
@@ -1,1108 +1,1107 b''
1 1 $ "$TESTDIR/hghave" symlink unix-permissions serve || exit 80
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 Run kwdemo before [keyword] files are set up
15 15 as it would succeed without uisetup otherwise
16 16
17 17 $ hg --quiet kwdemo
18 18 [extensions]
19 19 keyword =
20 20 [keyword]
21 21 demo.txt =
22 22 [keywordset]
23 23 svn = False
24 24 [keywordmaps]
25 25 Author = {author|user}
26 26 Date = {date|utcdate}
27 27 Header = {root}/{file},v {node|short} {date|utcdate} {author|user}
28 28 Id = {file|basename},v {node|short} {date|utcdate} {author|user}
29 29 RCSFile = {file|basename},v
30 30 RCSfile = {file|basename},v
31 31 Revision = {node|short}
32 32 Source = {root}/{file},v
33 33 $Author: test $
34 34 $Date: ????/??/?? ??:??:?? $ (glob)
35 35 $Header: */demo.txt,v ???????????? ????/??/?? ??:??:?? test $ (glob)
36 36 $Id: demo.txt,v ???????????? ????/??/?? ??:??:?? test $ (glob)
37 37 $RCSFile: demo.txt,v $
38 38 $RCSfile: demo.txt,v $
39 39 $Revision: ???????????? $ (glob)
40 40 $Source: */demo.txt,v $ (glob)
41 41
42 42 $ hg --quiet kwdemo "Branch = {branches}"
43 43 [extensions]
44 44 keyword =
45 45 [keyword]
46 46 demo.txt =
47 47 [keywordset]
48 48 svn = False
49 49 [keywordmaps]
50 50 Branch = {branches}
51 51 $Branch: demobranch $
52 52
53 53 $ cat <<EOF >> $HGRCPATH
54 54 > [keyword]
55 55 > ** =
56 56 > b = ignore
57 57 > i = ignore
58 58 > [hooks]
59 59 > EOF
60 60 $ cp $HGRCPATH $HGRCPATH.nohooks
61 61 > cat <<EOF >> $HGRCPATH
62 62 > commit=
63 63 > commit.test=cp a hooktest
64 64 > EOF
65 65
66 66 $ hg init Test-bndl
67 67 $ cd Test-bndl
68 68
69 69 kwshrink should exit silently in empty/invalid repo
70 70
71 71 $ hg kwshrink
72 72
73 73 Symlinks cannot be created on Windows.
74 74 A bundle to test this was made with:
75 75 hg init t
76 76 cd t
77 77 echo a > a
78 78 ln -s a sym
79 79 hg add sym
80 80 hg ci -m addsym -u mercurial
81 81 hg bundle --base null ../test-keyword.hg
82 82
83 83 $ hg pull -u "$TESTDIR"/bundles/test-keyword.hg
84 84 pulling from *test-keyword.hg (glob)
85 85 requesting all changes
86 86 adding changesets
87 87 adding manifests
88 88 adding file changes
89 89 added 1 changesets with 1 changes to 1 files
90 90 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
91 91
92 92 $ echo 'expand $Id$' > a
93 93 $ echo 'do not process $Id:' >> a
94 94 $ echo 'xxx $' >> a
95 95 $ echo 'ignore $Id$' > b
96 96
97 97 Output files as they were created
98 98
99 99 $ cat a b
100 100 expand $Id$
101 101 do not process $Id:
102 102 xxx $
103 103 ignore $Id$
104 104
105 105 no kwfiles
106 106
107 107 $ hg kwfiles
108 108
109 109 untracked candidates
110 110
111 111 $ hg -v kwfiles --unknown
112 112 k a
113 113
114 114 Add files and check status
115 115
116 116 $ hg addremove
117 117 adding a
118 118 adding b
119 119 $ hg status
120 120 A a
121 121 A b
122 122
123 123
124 124 Default keyword expansion including commit hook
125 125 Interrupted commit should not change state or run commit hook
126 126
127 127 $ hg --debug commit
128 128 abort: empty commit message
129 129 [255]
130 130 $ hg status
131 131 A a
132 132 A b
133 133
134 134 Commit with several checks
135 135
136 136 $ hg --debug commit -mabsym -u 'User Name <user@example.com>'
137 137 a
138 138 b
139 139 overwriting a expanding keywords
140 140 running hook commit.test: cp a hooktest
141 141 committed changeset 1:ef63ca68695bc9495032c6fda1350c71e6d256e9
142 142 $ hg status
143 143 ? hooktest
144 144 $ hg debugrebuildstate
145 145 $ hg --quiet identify
146 146 ef63ca68695b
147 147
148 148 cat files in working directory with keywords expanded
149 149
150 150 $ cat a b
151 151 expand $Id: a,v ef63ca68695b 1970/01/01 00:00:00 user $
152 152 do not process $Id:
153 153 xxx $
154 154 ignore $Id$
155 155
156 156 hg cat files and symlink, no expansion
157 157
158 158 $ hg cat sym a b && echo
159 159 expand $Id: a,v ef63ca68695b 1970/01/01 00:00:00 user $
160 160 do not process $Id:
161 161 xxx $
162 162 ignore $Id$
163 163 a
164 164
165 165 $ diff a hooktest
166 166
167 167 $ cp $HGRCPATH.nohooks $HGRCPATH
168 168 $ rm hooktest
169 169
170 170 hg status of kw-ignored binary file starting with '\1\n'
171 171
172 172 >>> open("i", "wb").write("\1\nfoo")
173 173 $ hg -q commit -Am metasep i
174 174 $ hg status
175 175 >>> open("i", "wb").write("\1\nbar")
176 176 $ hg status
177 177 M i
178 178 $ hg -q commit -m "modify metasep" i
179 179 $ hg status --rev 2:3
180 180 M i
181 181 $ touch empty
182 182 $ hg -q commit -A -m "another file"
183 183 $ hg status -A --rev 3:4 i
184 184 C i
185 185
186 186 $ hg -q strip -n 2
187 187
188 188 Test hook execution
189 189
190 190 bundle
191 191
192 192 $ hg bundle --base null ../kw.hg
193 193 2 changesets found
194 194 $ cd ..
195 195 $ hg init Test
196 196 $ cd Test
197 197
198 198 Notify on pull to check whether keywords stay as is in email
199 199 ie. if patch.diff wrapper acts as it should
200 200
201 201 $ cat <<EOF >> $HGRCPATH
202 202 > [hooks]
203 203 > incoming.notify = python:hgext.notify.hook
204 204 > [notify]
205 205 > sources = pull
206 206 > diffstat = False
207 207 > maxsubject = 15
208 208 > [reposubs]
209 209 > * = Test
210 210 > EOF
211 211
212 212 Pull from bundle and trigger notify
213 213
214 214 $ hg pull -u ../kw.hg
215 215 pulling from ../kw.hg
216 216 requesting all changes
217 217 adding changesets
218 218 adding manifests
219 219 adding file changes
220 220 added 2 changesets with 3 changes to 3 files
221 221 Content-Type: text/plain; charset="us-ascii"
222 222 MIME-Version: 1.0
223 223 Content-Transfer-Encoding: 7bit
224 224 Date: * (glob)
225 225 Subject: changeset in...
226 226 From: mercurial
227 227 X-Hg-Notification: changeset a2392c293916
228 228 Message-Id: <hg.a2392c293916*> (glob)
229 229 To: Test
230 230
231 231 changeset a2392c293916 in $TESTTMP/Test (glob)
232 232 details: $TESTTMP/Test?cmd=changeset;node=a2392c293916
233 233 description:
234 234 addsym
235 235
236 236 diffs (6 lines):
237 237
238 238 diff -r 000000000000 -r a2392c293916 sym
239 239 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
240 240 +++ b/sym Sat Feb 09 20:25:47 2008 +0100
241 241 @@ -0,0 +1,1 @@
242 242 +a
243 243 \ No newline at end of file
244 244 Content-Type: text/plain; charset="us-ascii"
245 245 MIME-Version: 1.0
246 246 Content-Transfer-Encoding: 7bit
247 247 Date:* (glob)
248 248 Subject: changeset in...
249 249 From: User Name <user@example.com>
250 250 X-Hg-Notification: changeset ef63ca68695b
251 251 Message-Id: <hg.ef63ca68695b*> (glob)
252 252 To: Test
253 253
254 254 changeset ef63ca68695b in $TESTTMP/Test (glob)
255 255 details: $TESTTMP/Test?cmd=changeset;node=ef63ca68695b
256 256 description:
257 257 absym
258 258
259 259 diffs (12 lines):
260 260
261 261 diff -r a2392c293916 -r ef63ca68695b a
262 262 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
263 263 +++ b/a Thu Jan 01 00:00:00 1970 +0000
264 264 @@ -0,0 +1,3 @@
265 265 +expand $Id$
266 266 +do not process $Id:
267 267 +xxx $
268 268 diff -r a2392c293916 -r ef63ca68695b b
269 269 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
270 270 +++ b/b Thu Jan 01 00:00:00 1970 +0000
271 271 @@ -0,0 +1,1 @@
272 272 +ignore $Id$
273 273 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
274 274
275 275 $ cp $HGRCPATH.nohooks $HGRCPATH
276 276
277 277 Touch files and check with status
278 278
279 279 $ touch a b
280 280 $ hg status
281 281
282 282 Update and expand
283 283
284 284 $ rm sym a b
285 285 $ hg update -C
286 286 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
287 287 $ cat a b
288 288 expand $Id: a,v ef63ca68695b 1970/01/01 00:00:00 user $
289 289 do not process $Id:
290 290 xxx $
291 291 ignore $Id$
292 292
293 293 Check whether expansion is filewise and file mode is preserved
294 294
295 295 $ echo '$Id$' > c
296 296 $ echo 'tests for different changenodes' >> c
297 297 $ chmod 600 c
298 298 $ ls -l c | cut -b 1-10
299 299 -rw-------
300 300
301 301 commit file c
302 302
303 303 $ hg commit -A -mcndiff -d '1 0' -u 'User Name <user@example.com>'
304 304 adding c
305 305 $ ls -l c | cut -b 1-10
306 306 -rw-------
307 307
308 308 force expansion
309 309
310 310 $ hg -v kwexpand
311 311 overwriting a expanding keywords
312 312 overwriting c expanding keywords
313 313
314 314 compare changenodes in a and c
315 315
316 316 $ cat a c
317 317 expand $Id: a,v ef63ca68695b 1970/01/01 00:00:00 user $
318 318 do not process $Id:
319 319 xxx $
320 320 $Id: c,v 40a904bbbe4c 1970/01/01 00:00:01 user $
321 321 tests for different changenodes
322 322
323 323 record
324 324
325 325 $ echo '$Id$' > r
326 326 $ hg add r
327 327
328 328 record chunk
329 329
330 330 >>> lines = open('a').readlines()
331 331 >>> lines.insert(1, 'foo\n')
332 332 >>> lines.append('bar\n')
333 333 >>> open('a', 'w').writelines(lines)
334 334 $ hg record -d '1 10' -m rectest a<<EOF
335 335 > y
336 336 > y
337 337 > n
338 338 > EOF
339 339 diff --git a/a b/a
340 340 2 hunks, 2 lines changed
341 341 examine changes to 'a'? [Ynsfdaq?]
342 342 @@ -1,3 +1,4 @@
343 343 expand $Id$
344 344 +foo
345 345 do not process $Id:
346 346 xxx $
347 347 record change 1/2 to 'a'? [Ynsfdaq?]
348 348 @@ -2,2 +3,3 @@
349 349 do not process $Id:
350 350 xxx $
351 351 +bar
352 352 record change 2/2 to 'a'? [Ynsfdaq?]
353 353
354 354 $ hg identify
355 355 d17e03c92c97+ tip
356 356 $ hg status
357 357 M a
358 358 A r
359 359
360 360 Cat modified file a
361 361
362 362 $ cat a
363 363 expand $Id: a,v d17e03c92c97 1970/01/01 00:00:01 test $
364 364 foo
365 365 do not process $Id:
366 366 xxx $
367 367 bar
368 368
369 369 Diff remaining chunk
370 370
371 371 $ hg diff a
372 372 diff -r d17e03c92c97 a
373 373 --- a/a Wed Dec 31 23:59:51 1969 -0000
374 374 +++ b/a * (glob)
375 375 @@ -2,3 +2,4 @@
376 376 foo
377 377 do not process $Id:
378 378 xxx $
379 379 +bar
380 380
381 381 $ hg rollback
382 382 repository tip rolled back to revision 2 (undo commit)
383 383 working directory now based on revision 2
384 384
385 385 Record all chunks in file a
386 386
387 387 $ echo foo > msg
388 388
389 389 - do not use "hg record -m" here!
390 390
391 391 $ hg record -l msg -d '1 11' a<<EOF
392 392 > y
393 393 > y
394 394 > y
395 395 > EOF
396 396 diff --git a/a b/a
397 397 2 hunks, 2 lines changed
398 398 examine changes to 'a'? [Ynsfdaq?]
399 399 @@ -1,3 +1,4 @@
400 400 expand $Id$
401 401 +foo
402 402 do not process $Id:
403 403 xxx $
404 404 record change 1/2 to 'a'? [Ynsfdaq?]
405 405 @@ -2,2 +3,3 @@
406 406 do not process $Id:
407 407 xxx $
408 408 +bar
409 409 record change 2/2 to 'a'? [Ynsfdaq?]
410 410
411 411 File a should be clean
412 412
413 413 $ hg status -A a
414 414 C a
415 415
416 416 rollback and revert expansion
417 417
418 418 $ cat a
419 419 expand $Id: a,v 59f969a3b52c 1970/01/01 00:00:01 test $
420 420 foo
421 421 do not process $Id:
422 422 xxx $
423 423 bar
424 424 $ hg --verbose rollback
425 425 repository tip rolled back to revision 2 (undo commit)
426 426 working directory now based on revision 2
427 427 overwriting a expanding keywords
428 428 $ hg status a
429 429 M a
430 430 $ cat a
431 431 expand $Id: a,v ef63ca68695b 1970/01/01 00:00:00 user $
432 432 foo
433 433 do not process $Id:
434 434 xxx $
435 435 bar
436 436 $ echo '$Id$' > y
437 437 $ echo '$Id$' > z
438 438 $ hg add y
439 439 $ hg commit -Am "rollback only" z
440 440 $ cat z
441 441 $Id: z,v 45a5d3adce53 1970/01/01 00:00:00 test $
442 442 $ hg --verbose rollback
443 443 repository tip rolled back to revision 2 (undo commit)
444 444 working directory now based on revision 2
445 445 overwriting z shrinking keywords
446 446
447 447 Only z should be overwritten
448 448
449 449 $ hg status a y z
450 450 M a
451 451 A y
452 452 A z
453 453 $ cat z
454 454 $Id$
455 455 $ hg forget y z
456 456 $ rm y z
457 457
458 458 record added file alone
459 459
460 460 $ hg -v record -l msg -d '1 12' r<<EOF
461 461 > y
462 462 > EOF
463 463 diff --git a/r b/r
464 464 new file mode 100644
465 465 examine changes to 'r'? [Ynsfdaq?]
466 466 r
467 467 committed changeset 3:899491280810
468 468 overwriting r expanding keywords
469 469 - status call required for dirstate.normallookup() check
470 470 $ hg status r
471 471 $ hg --verbose rollback
472 472 repository tip rolled back to revision 2 (undo commit)
473 473 working directory now based on revision 2
474 474 overwriting r shrinking keywords
475 475 $ hg forget r
476 476 $ rm msg r
477 477 $ hg update -C
478 478 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
479 479
480 480 record added keyword ignored file
481 481
482 482 $ echo '$Id$' > i
483 483 $ hg add i
484 484 $ hg --verbose record -d '1 13' -m recignored<<EOF
485 485 > y
486 486 > EOF
487 487 diff --git a/i b/i
488 488 new file mode 100644
489 489 examine changes to 'i'? [Ynsfdaq?]
490 490 i
491 491 committed changeset 3:5f40fe93bbdc
492 492 $ cat i
493 493 $Id$
494 494 $ hg -q rollback
495 495 $ hg forget i
496 496 $ rm i
497 497
498 498 Test patch queue repo
499 499
500 500 $ hg init --mq
501 501 $ hg qimport -r tip -n mqtest.diff
502 502 $ hg commit --mq -m mqtest
503 503
504 504 Keywords should not be expanded in patch
505 505
506 506 $ cat .hg/patches/mqtest.diff
507 507 # HG changeset patch
508 508 # User User Name <user@example.com>
509 509 # Date 1 0
510 510 # Node ID 40a904bbbe4cd4ab0a1f28411e35db26341a40ad
511 511 # Parent ef63ca68695bc9495032c6fda1350c71e6d256e9
512 512 cndiff
513 513
514 514 diff -r ef63ca68695b -r 40a904bbbe4c c
515 515 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
516 516 +++ b/c Thu Jan 01 00:00:01 1970 +0000
517 517 @@ -0,0 +1,2 @@
518 518 +$Id$
519 519 +tests for different changenodes
520 520
521 521 $ hg qpop
522 522 popping mqtest.diff
523 523 patch queue now empty
524 524
525 525 qgoto, implying qpush, should expand
526 526
527 527 $ hg qgoto mqtest.diff
528 528 applying mqtest.diff
529 529 now at: mqtest.diff
530 530 $ cat c
531 531 $Id: c,v 40a904bbbe4c 1970/01/01 00:00:01 user $
532 532 tests for different changenodes
533 533 $ hg cat c
534 534 $Id: c,v 40a904bbbe4c 1970/01/01 00:00:01 user $
535 535 tests for different changenodes
536 536
537 537 Keywords should not be expanded in filelog
538 538
539 539 $ hg --config 'extensions.keyword=!' cat c
540 540 $Id$
541 541 tests for different changenodes
542 542
543 543 qpop and move on
544 544
545 545 $ hg qpop
546 546 popping mqtest.diff
547 547 patch queue now empty
548 548
549 549 Copy and show added kwfiles
550 550
551 551 $ hg cp a c
552 552 $ hg kwfiles
553 553 a
554 554 c
555 555
556 556 Commit and show expansion in original and copy
557 557
558 558 $ hg --debug commit -ma2c -d '1 0' -u 'User Name <user@example.com>'
559 invalidating branch cache (tip differs)
560 559 c
561 560 c: copy a:0045e12f6c5791aac80ca6cbfd97709a88307292
562 561 overwriting c expanding keywords
563 562 committed changeset 2:25736cf2f5cbe41f6be4e6784ef6ecf9f3bbcc7d
564 563 $ cat a c
565 564 expand $Id: a,v ef63ca68695b 1970/01/01 00:00:00 user $
566 565 do not process $Id:
567 566 xxx $
568 567 expand $Id: c,v 25736cf2f5cb 1970/01/01 00:00:01 user $
569 568 do not process $Id:
570 569 xxx $
571 570
572 571 Touch copied c and check its status
573 572
574 573 $ touch c
575 574 $ hg status
576 575
577 576 Copy kwfile to keyword ignored file unexpanding keywords
578 577
579 578 $ hg --verbose copy a i
580 579 copying a to i
581 580 overwriting i shrinking keywords
582 581 $ head -n 1 i
583 582 expand $Id$
584 583 $ hg forget i
585 584 $ rm i
586 585
587 586 Copy ignored file to ignored file: no overwriting
588 587
589 588 $ hg --verbose copy b i
590 589 copying b to i
591 590 $ hg forget i
592 591 $ rm i
593 592
594 593 cp symlink file; hg cp -A symlink file (part1)
595 594 - copied symlink points to kwfile: overwrite
596 595
597 596 $ cp sym i
598 597 $ ls -l i
599 598 -rw-r--r--* (glob)
600 599 $ head -1 i
601 600 expand $Id: a,v ef63ca68695b 1970/01/01 00:00:00 user $
602 601 $ hg copy --after --verbose sym i
603 602 copying sym to i
604 603 overwriting i shrinking keywords
605 604 $ head -1 i
606 605 expand $Id$
607 606 $ hg forget i
608 607 $ rm i
609 608
610 609 Test different options of hg kwfiles
611 610
612 611 $ hg kwfiles
613 612 a
614 613 c
615 614 $ hg -v kwfiles --ignore
616 615 I b
617 616 I sym
618 617 $ hg kwfiles --all
619 618 K a
620 619 K c
621 620 I b
622 621 I sym
623 622
624 623 Diff specific revision
625 624
626 625 $ hg diff --rev 1
627 626 diff -r ef63ca68695b c
628 627 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
629 628 +++ b/c * (glob)
630 629 @@ -0,0 +1,3 @@
631 630 +expand $Id$
632 631 +do not process $Id:
633 632 +xxx $
634 633
635 634 Status after rollback:
636 635
637 636 $ hg rollback
638 637 repository tip rolled back to revision 1 (undo commit)
639 638 working directory now based on revision 1
640 639 $ hg status
641 640 A c
642 641 $ hg update --clean
643 642 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
644 643
645 644 cp symlink file; hg cp -A symlink file (part2)
646 645 - copied symlink points to kw ignored file: do not overwrite
647 646
648 647 $ cat a > i
649 648 $ ln -s i symignored
650 649 $ hg commit -Am 'fake expansion in ignored and symlink' i symignored
651 650 $ cp symignored x
652 651 $ hg copy --after --verbose symignored x
653 652 copying symignored to x
654 653 $ head -n 1 x
655 654 expand $Id: a,v ef63ca68695b 1970/01/01 00:00:00 user $
656 655 $ hg forget x
657 656 $ rm x
658 657
659 658 $ hg rollback
660 659 repository tip rolled back to revision 1 (undo commit)
661 660 working directory now based on revision 1
662 661 $ hg update --clean
663 662 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
664 663 $ rm i symignored
665 664
666 665 Custom keywordmaps as argument to kwdemo
667 666
668 667 $ hg --quiet kwdemo "Xinfo = {author}: {desc}"
669 668 [extensions]
670 669 keyword =
671 670 [keyword]
672 671 ** =
673 672 b = ignore
674 673 demo.txt =
675 674 i = ignore
676 675 [keywordset]
677 676 svn = False
678 677 [keywordmaps]
679 678 Xinfo = {author}: {desc}
680 679 $Xinfo: test: hg keyword configuration and expansion example $
681 680
682 681 Configure custom keywordmaps
683 682
684 683 $ cat <<EOF >>$HGRCPATH
685 684 > [keywordmaps]
686 685 > Id = {file} {node|short} {date|rfc822date} {author|user}
687 686 > Xinfo = {author}: {desc}
688 687 > EOF
689 688
690 689 Cat and hg cat files before custom expansion
691 690
692 691 $ cat a b
693 692 expand $Id: a,v ef63ca68695b 1970/01/01 00:00:00 user $
694 693 do not process $Id:
695 694 xxx $
696 695 ignore $Id$
697 696 $ hg cat sym a b && echo
698 697 expand $Id: a ef63ca68695b Thu, 01 Jan 1970 00:00:00 +0000 user $
699 698 do not process $Id:
700 699 xxx $
701 700 ignore $Id$
702 701 a
703 702
704 703 Write custom keyword and prepare multiline commit message
705 704
706 705 $ echo '$Xinfo$' >> a
707 706 $ cat <<EOF >> log
708 707 > firstline
709 708 > secondline
710 709 > EOF
711 710
712 711 Interrupted commit should not change state
713 712
714 713 $ hg commit
715 714 abort: empty commit message
716 715 [255]
717 716 $ hg status
718 717 M a
719 718 ? c
720 719 ? log
721 720
722 721 Commit with multiline message and custom expansion
723 722
724 723 $ hg --debug commit -l log -d '2 0' -u 'User Name <user@example.com>'
725 724 a
726 725 overwriting a expanding keywords
727 726 committed changeset 2:bb948857c743469b22bbf51f7ec8112279ca5d83
728 727 $ rm log
729 728
730 729 Stat, verify and show custom expansion (firstline)
731 730
732 731 $ hg status
733 732 ? c
734 733 $ hg verify
735 734 checking changesets
736 735 checking manifests
737 736 crosschecking files in changesets and manifests
738 737 checking files
739 738 3 files, 3 changesets, 4 total revisions
740 739 $ cat a b
741 740 expand $Id: a bb948857c743 Thu, 01 Jan 1970 00:00:02 +0000 user $
742 741 do not process $Id:
743 742 xxx $
744 743 $Xinfo: User Name <user@example.com>: firstline $
745 744 ignore $Id$
746 745 $ hg cat sym a b && echo
747 746 expand $Id: a bb948857c743 Thu, 01 Jan 1970 00:00:02 +0000 user $
748 747 do not process $Id:
749 748 xxx $
750 749 $Xinfo: User Name <user@example.com>: firstline $
751 750 ignore $Id$
752 751 a
753 752
754 753 annotate
755 754
756 755 $ hg annotate a
757 756 1: expand $Id$
758 757 1: do not process $Id:
759 758 1: xxx $
760 759 2: $Xinfo$
761 760
762 761 remove with status checks
763 762
764 763 $ hg debugrebuildstate
765 764 $ hg remove a
766 765 $ hg --debug commit -m rma
767 766 committed changeset 3:d14c712653769de926994cf7fbb06c8fbd68f012
768 767 $ hg status
769 768 ? c
770 769
771 770 Rollback, revert, and check expansion
772 771
773 772 $ hg rollback
774 773 repository tip rolled back to revision 2 (undo commit)
775 774 working directory now based on revision 2
776 775 $ hg status
777 776 R a
778 777 ? c
779 778 $ hg revert --no-backup --rev tip a
780 779 $ cat a
781 780 expand $Id: a bb948857c743 Thu, 01 Jan 1970 00:00:02 +0000 user $
782 781 do not process $Id:
783 782 xxx $
784 783 $Xinfo: User Name <user@example.com>: firstline $
785 784
786 785 Clone to test global and local configurations
787 786
788 787 $ cd ..
789 788
790 789 Expansion in destinaton with global configuration
791 790
792 791 $ hg --quiet clone Test globalconf
793 792 $ cat globalconf/a
794 793 expand $Id: a bb948857c743 Thu, 01 Jan 1970 00:00:02 +0000 user $
795 794 do not process $Id:
796 795 xxx $
797 796 $Xinfo: User Name <user@example.com>: firstline $
798 797
799 798 No expansion in destination with local configuration in origin only
800 799
801 800 $ hg --quiet --config 'keyword.**=ignore' clone Test localconf
802 801 $ cat localconf/a
803 802 expand $Id$
804 803 do not process $Id:
805 804 xxx $
806 805 $Xinfo$
807 806
808 807 Clone to test incoming
809 808
810 809 $ hg clone -r1 Test Test-a
811 810 adding changesets
812 811 adding manifests
813 812 adding file changes
814 813 added 2 changesets with 3 changes to 3 files
815 814 updating to branch default
816 815 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
817 816 $ cd Test-a
818 817 $ cat <<EOF >> .hg/hgrc
819 818 > [paths]
820 819 > default = ../Test
821 820 > EOF
822 821 $ hg incoming
823 822 comparing with $TESTTMP/Test (glob)
824 823 searching for changes
825 824 changeset: 2:bb948857c743
826 825 tag: tip
827 826 user: User Name <user@example.com>
828 827 date: Thu Jan 01 00:00:02 1970 +0000
829 828 summary: firstline
830 829
831 830 Imported patch should not be rejected
832 831
833 832 >>> import re
834 833 >>> text = re.sub(r'(Id.*)', r'\1 rejecttest', open('a').read())
835 834 >>> open('a', 'wb').write(text)
836 835 $ hg --debug commit -m'rejects?' -d '3 0' -u 'User Name <user@example.com>'
837 836 a
838 837 overwriting a expanding keywords
839 838 committed changeset 2:85e279d709ffc28c9fdd1b868570985fc3d87082
840 839 $ hg export -o ../rejecttest.diff tip
841 840 $ cd ../Test
842 841 $ hg import ../rejecttest.diff
843 842 applying ../rejecttest.diff
844 843 $ cat a b
845 844 expand $Id: a 4e0994474d25 Thu, 01 Jan 1970 00:00:03 +0000 user $ rejecttest
846 845 do not process $Id: rejecttest
847 846 xxx $
848 847 $Xinfo: User Name <user@example.com>: rejects? $
849 848 ignore $Id$
850 849
851 850 $ hg rollback
852 851 repository tip rolled back to revision 2 (undo import)
853 852 working directory now based on revision 2
854 853 $ hg update --clean
855 854 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
856 855
857 856 kwexpand/kwshrink on selected files
858 857
859 858 $ mkdir x
860 859 $ hg copy a x/a
861 860 $ hg --verbose kwshrink a
862 861 overwriting a shrinking keywords
863 862 - sleep required for dirstate.normal() check
864 863 $ sleep 1
865 864 $ hg status a
866 865 $ hg --verbose kwexpand a
867 866 overwriting a expanding keywords
868 867 $ hg status a
869 868
870 869 kwexpand x/a should abort
871 870
872 871 $ hg --verbose kwexpand x/a
873 872 abort: outstanding uncommitted changes
874 873 [255]
875 874 $ cd x
876 875 $ hg --debug commit -m xa -d '3 0' -u 'User Name <user@example.com>'
877 876 x/a
878 877 x/a: copy a:779c764182ce5d43e2b1eb66ce06d7b47bfe342e
879 878 overwriting x/a expanding keywords
880 879 committed changeset 3:b4560182a3f9a358179fd2d835c15e9da379c1e4
881 880 $ cat a
882 881 expand $Id: x/a b4560182a3f9 Thu, 01 Jan 1970 00:00:03 +0000 user $
883 882 do not process $Id:
884 883 xxx $
885 884 $Xinfo: User Name <user@example.com>: xa $
886 885
887 886 kwshrink a inside directory x
888 887
889 888 $ hg --verbose kwshrink a
890 889 overwriting x/a shrinking keywords
891 890 $ cat a
892 891 expand $Id$
893 892 do not process $Id:
894 893 xxx $
895 894 $Xinfo$
896 895 $ cd ..
897 896
898 897 kwexpand nonexistent
899 898
900 899 $ hg kwexpand nonexistent
901 900 nonexistent:* (glob)
902 901
903 902
904 903 hg serve
905 904 - expand with hgweb file
906 905 - no expansion with hgweb annotate/changeset/filediff
907 906 - check errors
908 907
909 908 $ hg serve -p $HGPORT -d --pid-file=hg.pid -A access.log -E errors.log
910 909 $ cat hg.pid >> $DAEMON_PIDS
911 910 $ $TESTDIR/get-with-headers.py localhost:$HGPORT '/file/tip/a/?style=raw'
912 911 200 Script output follows
913 912
914 913 expand $Id: a bb948857c743 Thu, 01 Jan 1970 00:00:02 +0000 user $
915 914 do not process $Id:
916 915 xxx $
917 916 $Xinfo: User Name <user@example.com>: firstline $
918 917 $ $TESTDIR/get-with-headers.py localhost:$HGPORT '/annotate/tip/a/?style=raw'
919 918 200 Script output follows
920 919
921 920
922 921 user@1: expand $Id$
923 922 user@1: do not process $Id:
924 923 user@1: xxx $
925 924 user@2: $Xinfo$
926 925
927 926
928 927
929 928
930 929 $ $TESTDIR/get-with-headers.py localhost:$HGPORT '/rev/tip/?style=raw'
931 930 200 Script output follows
932 931
933 932
934 933 # HG changeset patch
935 934 # User User Name <user@example.com>
936 935 # Date 3 0
937 936 # Node ID b4560182a3f9a358179fd2d835c15e9da379c1e4
938 937 # Parent bb948857c743469b22bbf51f7ec8112279ca5d83
939 938 xa
940 939
941 940 diff -r bb948857c743 -r b4560182a3f9 x/a
942 941 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
943 942 +++ b/x/a Thu Jan 01 00:00:03 1970 +0000
944 943 @@ -0,0 +1,4 @@
945 944 +expand $Id$
946 945 +do not process $Id:
947 946 +xxx $
948 947 +$Xinfo$
949 948
950 949 $ $TESTDIR/get-with-headers.py localhost:$HGPORT '/diff/bb948857c743/a?style=raw'
951 950 200 Script output follows
952 951
953 952
954 953 diff -r ef63ca68695b -r bb948857c743 a
955 954 --- a/a Thu Jan 01 00:00:00 1970 +0000
956 955 +++ b/a Thu Jan 01 00:00:02 1970 +0000
957 956 @@ -1,3 +1,4 @@
958 957 expand $Id$
959 958 do not process $Id:
960 959 xxx $
961 960 +$Xinfo$
962 961
963 962
964 963
965 964
966 965 $ cat errors.log
967 966
968 967 Prepare merge and resolve tests
969 968
970 969 $ echo '$Id$' > m
971 970 $ hg add m
972 971 $ hg commit -m 4kw
973 972 $ echo foo >> m
974 973 $ hg commit -m 5foo
975 974
976 975 simplemerge
977 976
978 977 $ hg update 4
979 978 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
980 979 $ echo foo >> m
981 980 $ hg commit -m 6foo
982 981 created new head
983 982 $ hg merge
984 983 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
985 984 (branch merge, don't forget to commit)
986 985 $ hg commit -m simplemerge
987 986 $ cat m
988 987 $Id: m 27d48ee14f67 Thu, 01 Jan 1970 00:00:00 +0000 test $
989 988 foo
990 989
991 990 conflict: keyword should stay outside conflict zone
992 991
993 992 $ hg update 4
994 993 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
995 994 $ echo bar >> m
996 995 $ hg commit -m 8bar
997 996 created new head
998 997 $ hg merge
999 998 merging m
1000 999 warning: conflicts during merge.
1001 1000 merging m incomplete! (edit conflicts, then use 'hg resolve --mark')
1002 1001 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
1003 1002 use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
1004 1003 [1]
1005 1004 $ cat m
1006 1005 $Id$
1007 1006 <<<<<<< local
1008 1007 bar
1009 1008 =======
1010 1009 foo
1011 1010 >>>>>>> other
1012 1011
1013 1012 resolve to local
1014 1013
1015 1014 $ HGMERGE=internal:local hg resolve -a
1016 1015 $ hg commit -m localresolve
1017 1016 $ cat m
1018 1017 $Id: m 800511b3a22d Thu, 01 Jan 1970 00:00:00 +0000 test $
1019 1018 bar
1020 1019
1021 1020 Test restricted mode with transplant -b
1022 1021
1023 1022 $ hg update 6
1024 1023 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1025 1024 $ hg branch foo
1026 1025 marked working directory as branch foo
1027 1026 (branches are permanent and global, did you want a bookmark?)
1028 1027 $ mv a a.bak
1029 1028 $ echo foobranch > a
1030 1029 $ cat a.bak >> a
1031 1030 $ rm a.bak
1032 1031 $ hg commit -m 9foobranch
1033 1032 $ hg update default
1034 1033 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
1035 1034 $ hg -y transplant -b foo tip
1036 1035 applying 4aa30d025d50
1037 1036 4aa30d025d50 transplanted to e00abbf63521
1038 1037
1039 1038 Expansion in changeset but not in file
1040 1039
1041 1040 $ hg tip -p
1042 1041 changeset: 11:e00abbf63521
1043 1042 tag: tip
1044 1043 parent: 9:800511b3a22d
1045 1044 user: test
1046 1045 date: Thu Jan 01 00:00:00 1970 +0000
1047 1046 summary: 9foobranch
1048 1047
1049 1048 diff -r 800511b3a22d -r e00abbf63521 a
1050 1049 --- a/a Thu Jan 01 00:00:00 1970 +0000
1051 1050 +++ b/a Thu Jan 01 00:00:00 1970 +0000
1052 1051 @@ -1,3 +1,4 @@
1053 1052 +foobranch
1054 1053 expand $Id$
1055 1054 do not process $Id:
1056 1055 xxx $
1057 1056
1058 1057 $ head -n 2 a
1059 1058 foobranch
1060 1059 expand $Id: a e00abbf63521 Thu, 01 Jan 1970 00:00:00 +0000 test $
1061 1060
1062 1061 Turn off expansion
1063 1062
1064 1063 $ hg -q rollback
1065 1064 $ hg -q update -C
1066 1065
1067 1066 kwshrink with unknown file u
1068 1067
1069 1068 $ cp a u
1070 1069 $ hg --verbose kwshrink
1071 1070 overwriting a shrinking keywords
1072 1071 overwriting m shrinking keywords
1073 1072 overwriting x/a shrinking keywords
1074 1073
1075 1074 Keywords shrunk in working directory, but not yet disabled
1076 1075 - cat shows unexpanded keywords
1077 1076 - hg cat shows expanded keywords
1078 1077
1079 1078 $ cat a b
1080 1079 expand $Id$
1081 1080 do not process $Id:
1082 1081 xxx $
1083 1082 $Xinfo$
1084 1083 ignore $Id$
1085 1084 $ hg cat sym a b && echo
1086 1085 expand $Id: a bb948857c743 Thu, 01 Jan 1970 00:00:02 +0000 user $
1087 1086 do not process $Id:
1088 1087 xxx $
1089 1088 $Xinfo: User Name <user@example.com>: firstline $
1090 1089 ignore $Id$
1091 1090 a
1092 1091
1093 1092 Now disable keyword expansion
1094 1093
1095 1094 $ rm "$HGRCPATH"
1096 1095 $ cat a b
1097 1096 expand $Id$
1098 1097 do not process $Id:
1099 1098 xxx $
1100 1099 $Xinfo$
1101 1100 ignore $Id$
1102 1101 $ hg cat sym a b && echo
1103 1102 expand $Id$
1104 1103 do not process $Id:
1105 1104 xxx $
1106 1105 $Xinfo$
1107 1106 ignore $Id$
1108 1107 a
@@ -1,126 +1,124 b''
1 1 $ branches=.hg/cache/branchheads
2 2 $ echo '[extensions]' >> $HGRCPATH
3 3 $ echo 'mq =' >> $HGRCPATH
4 4
5 5 $ show_branch_cache()
6 6 > {
7 7 > # force cache (re)generation
8 8 > hg log -r does-not-exist 2> /dev/null
9 9 > hg log -r tip --template 'tip: {rev}\n'
10 10 > if [ -f $branches ]; then
11 11 > sort $branches
12 12 > else
13 13 > echo No branch cache
14 14 > fi
15 15 > if [ "$1" = 1 ]; then
16 16 > for b in foo bar; do
17 17 > hg log -r $b --template "branch $b: "'{rev}\n'
18 18 > done
19 19 > fi
20 20 > }
21 21
22 22 $ hg init a
23 23 $ cd a
24 24 $ hg qinit -c
25 25
26 26
27 27 mq patch on an empty repo
28 28
29 29 $ hg qnew -d '0 0' p1
30 30 $ show_branch_cache
31 31 tip: 0
32 d986d5caac23a7d44a46efc0ddaf5eb9665844cf 0
33 d986d5caac23a7d44a46efc0ddaf5eb9665844cf default
32 No branch cache
34 33
35 34 $ echo > pfile
36 35 $ hg add pfile
37 36 $ hg qrefresh -m 'patch 1'
38 37 $ show_branch_cache
39 38 tip: 0
40 a7977e38ed2c2942fa6c278030badfef3d180979 0
41 a7977e38ed2c2942fa6c278030badfef3d180979 default
39 No branch cache
42 40
43 41 some regular revisions
44 42
45 43 $ hg qpop
46 44 popping p1
47 45 patch queue now empty
48 46 $ echo foo > foo
49 47 $ hg add foo
50 48 $ echo foo > .hg/branch
51 49 $ hg ci -m 'branch foo'
52 50
53 51 $ echo bar > bar
54 52 $ hg add bar
55 53 $ echo bar > .hg/branch
56 54 $ hg ci -m 'branch bar'
57 55 $ show_branch_cache
58 56 tip: 1
59 57 c229711f16da3d7591f89b1b8d963b79bda22714 1
60 58 c229711f16da3d7591f89b1b8d963b79bda22714 bar
61 59 dc25e3827021582e979f600811852e36cbe57341 foo
62 60
63 61 add some mq patches
64 62
65 63 $ hg qpush
66 64 applying p1
67 65 now at: p1
68 66 $ show_branch_cache
69 67 tip: 2
70 982611f6955f9c48d3365decea203217c945ef0d 2
71 982611f6955f9c48d3365decea203217c945ef0d bar
68 c229711f16da3d7591f89b1b8d963b79bda22714 1
69 c229711f16da3d7591f89b1b8d963b79bda22714 bar
72 70 dc25e3827021582e979f600811852e36cbe57341 foo
73 71
74 72 $ hg qnew -d '0 0' p2
75 73 $ echo foo > .hg/branch
76 74 $ echo foo2 >> foo
77 75 $ hg qrefresh -m 'patch 2'
78 76 $ show_branch_cache 1
79 77 tip: 3
80 982611f6955f9c48d3365decea203217c945ef0d 2
81 982611f6955f9c48d3365decea203217c945ef0d bar
78 c229711f16da3d7591f89b1b8d963b79bda22714 1
79 c229711f16da3d7591f89b1b8d963b79bda22714 bar
82 80 dc25e3827021582e979f600811852e36cbe57341 foo
83 81 branch foo: 3
84 82 branch bar: 2
85 83
86 84 removing the cache
87 85
88 86 $ rm $branches
89 87 $ show_branch_cache 1
90 88 tip: 3
91 89 c229711f16da3d7591f89b1b8d963b79bda22714 1
92 90 c229711f16da3d7591f89b1b8d963b79bda22714 bar
93 91 dc25e3827021582e979f600811852e36cbe57341 foo
94 92 branch foo: 3
95 93 branch bar: 2
96 94
97 95 importing rev 1 (the cache now ends in one of the patches)
98 96
99 97 $ hg qimport -r 1 -n p0
100 98 $ show_branch_cache 1
101 99 tip: 3
102 100 c229711f16da3d7591f89b1b8d963b79bda22714 1
103 101 c229711f16da3d7591f89b1b8d963b79bda22714 bar
104 102 dc25e3827021582e979f600811852e36cbe57341 foo
105 103 branch foo: 3
106 104 branch bar: 2
107 105 $ hg log -r qbase --template 'qbase: {rev}\n'
108 106 qbase: 1
109 107
110 108 detect an invalid cache
111 109
112 110 $ hg qpop -a
113 111 popping p2
114 112 popping p1
115 113 popping p0
116 114 patch queue now empty
117 115 $ hg qpush -a
118 116 applying p0
119 117 applying p1
120 118 applying p2
121 119 now at: p2
122 120 $ show_branch_cache
123 121 tip: 3
124 3fe2e3b237359b5c55cec6ed172ac41d3850fade 1
125 3fe2e3b237359b5c55cec6ed172ac41d3850fade foo
122 dc25e3827021582e979f600811852e36cbe57341 0
123 dc25e3827021582e979f600811852e36cbe57341 foo
126 124
General Comments 0
You need to be logged in to leave comments. Login now