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