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