##// END OF EJS Templates
qfold: save manually edited commit message into ".hg/last-message.txt"...
FUJIWARA Katsunori -
r20769:1e686e55 stable
parent child Browse files
Show More
@@ -1,3453 +1,3454 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)
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['.']
830 830 mctx = actx = repo[None]
831 831 overwrite = False
832 832 mergedsubstate = subrepo.submerge(repo, wctx, mctx, actx,
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 user = opts.get('user')
1030 1030 date = opts.get('date')
1031 1031 if date:
1032 1032 date = util.parsedate(date)
1033 1033 diffopts = self.diffopts({'git': opts.get('git')})
1034 1034 if opts.get('checkname', True):
1035 1035 self.checkpatchname(patchfn)
1036 1036 inclsubs = checksubstate(repo)
1037 1037 if inclsubs:
1038 1038 inclsubs.append('.hgsubstate')
1039 1039 substatestate = repo.dirstate['.hgsubstate']
1040 1040 if opts.get('include') or opts.get('exclude') or pats:
1041 1041 if inclsubs:
1042 1042 pats = list(pats or []) + inclsubs
1043 1043 match = scmutil.match(repo[None], pats, opts)
1044 1044 # detect missing files in pats
1045 1045 def badfn(f, msg):
1046 1046 if f != '.hgsubstate': # .hgsubstate is auto-created
1047 1047 raise util.Abort('%s: %s' % (f, msg))
1048 1048 match.bad = badfn
1049 1049 changes = repo.status(match=match)
1050 1050 m, a, r, d = changes[:4]
1051 1051 else:
1052 1052 changes = self.checklocalchanges(repo, force=True)
1053 1053 m, a, r, d = changes
1054 1054 match = scmutil.matchfiles(repo, m + a + r + inclsubs)
1055 1055 if len(repo[None].parents()) > 1:
1056 1056 raise util.Abort(_('cannot manage merge changesets'))
1057 1057 commitfiles = m + a + r
1058 1058 self.checktoppatch(repo)
1059 1059 insert = self.fullseriesend()
1060 1060 wlock = repo.wlock()
1061 1061 try:
1062 1062 try:
1063 1063 # if patch file write fails, abort early
1064 1064 p = self.opener(patchfn, "w")
1065 1065 except IOError, e:
1066 1066 raise util.Abort(_('cannot write patch "%s": %s')
1067 1067 % (patchfn, e.strerror))
1068 1068 try:
1069 1069 if self.plainmode:
1070 1070 if user:
1071 1071 p.write("From: " + user + "\n")
1072 1072 if not date:
1073 1073 p.write("\n")
1074 1074 if date:
1075 1075 p.write("Date: %d %d\n\n" % date)
1076 1076 else:
1077 1077 p.write("# HG changeset patch\n")
1078 1078 p.write("# Parent "
1079 1079 + hex(repo[None].p1().node()) + "\n")
1080 1080 if user:
1081 1081 p.write("# User " + user + "\n")
1082 1082 if date:
1083 1083 p.write("# Date %s %s\n\n" % date)
1084 1084 if util.safehasattr(msg, '__call__'):
1085 1085 msg = msg()
1086 1086 repo.savecommitmessage(msg)
1087 1087 commitmsg = msg and msg or ("[mq]: %s" % patchfn)
1088 1088 n = newcommit(repo, None, commitmsg, user, date, match=match,
1089 1089 force=True)
1090 1090 if n is None:
1091 1091 raise util.Abort(_("repo commit failed"))
1092 1092 try:
1093 1093 self.fullseries[insert:insert] = [patchfn]
1094 1094 self.applied.append(statusentry(n, patchfn))
1095 1095 self.parseseries()
1096 1096 self.seriesdirty = True
1097 1097 self.applieddirty = True
1098 1098 if msg:
1099 1099 msg = msg + "\n\n"
1100 1100 p.write(msg)
1101 1101 if commitfiles:
1102 1102 parent = self.qparents(repo, n)
1103 1103 if inclsubs:
1104 1104 self.putsubstate2changes(substatestate, changes)
1105 1105 chunks = patchmod.diff(repo, node1=parent, node2=n,
1106 1106 changes=changes, opts=diffopts)
1107 1107 for chunk in chunks:
1108 1108 p.write(chunk)
1109 1109 p.close()
1110 1110 r = self.qrepo()
1111 1111 if r:
1112 1112 r[None].add([patchfn])
1113 1113 except: # re-raises
1114 1114 repo.rollback()
1115 1115 raise
1116 1116 except Exception:
1117 1117 patchpath = self.join(patchfn)
1118 1118 try:
1119 1119 os.unlink(patchpath)
1120 1120 except OSError:
1121 1121 self.ui.warn(_('error unlinking %s\n') % patchpath)
1122 1122 raise
1123 1123 self.removeundo(repo)
1124 1124 finally:
1125 1125 release(wlock)
1126 1126
1127 1127 def isapplied(self, patch):
1128 1128 """returns (index, rev, patch)"""
1129 1129 for i, a in enumerate(self.applied):
1130 1130 if a.name == patch:
1131 1131 return (i, a.node, a.name)
1132 1132 return None
1133 1133
1134 1134 # if the exact patch name does not exist, we try a few
1135 1135 # variations. If strict is passed, we try only #1
1136 1136 #
1137 1137 # 1) a number (as string) to indicate an offset in the series file
1138 1138 # 2) a unique substring of the patch name was given
1139 1139 # 3) patchname[-+]num to indicate an offset in the series file
1140 1140 def lookup(self, patch, strict=False):
1141 1141 def partialname(s):
1142 1142 if s in self.series:
1143 1143 return s
1144 1144 matches = [x for x in self.series if s in x]
1145 1145 if len(matches) > 1:
1146 1146 self.ui.warn(_('patch name "%s" is ambiguous:\n') % s)
1147 1147 for m in matches:
1148 1148 self.ui.warn(' %s\n' % m)
1149 1149 return None
1150 1150 if matches:
1151 1151 return matches[0]
1152 1152 if self.series and self.applied:
1153 1153 if s == 'qtip':
1154 1154 return self.series[self.seriesend(True) - 1]
1155 1155 if s == 'qbase':
1156 1156 return self.series[0]
1157 1157 return None
1158 1158
1159 1159 if patch in self.series:
1160 1160 return patch
1161 1161
1162 1162 if not os.path.isfile(self.join(patch)):
1163 1163 try:
1164 1164 sno = int(patch)
1165 1165 except (ValueError, OverflowError):
1166 1166 pass
1167 1167 else:
1168 1168 if -len(self.series) <= sno < len(self.series):
1169 1169 return self.series[sno]
1170 1170
1171 1171 if not strict:
1172 1172 res = partialname(patch)
1173 1173 if res:
1174 1174 return res
1175 1175 minus = patch.rfind('-')
1176 1176 if minus >= 0:
1177 1177 res = partialname(patch[:minus])
1178 1178 if res:
1179 1179 i = self.series.index(res)
1180 1180 try:
1181 1181 off = int(patch[minus + 1:] or 1)
1182 1182 except (ValueError, OverflowError):
1183 1183 pass
1184 1184 else:
1185 1185 if i - off >= 0:
1186 1186 return self.series[i - off]
1187 1187 plus = patch.rfind('+')
1188 1188 if plus >= 0:
1189 1189 res = partialname(patch[:plus])
1190 1190 if res:
1191 1191 i = self.series.index(res)
1192 1192 try:
1193 1193 off = int(patch[plus + 1:] or 1)
1194 1194 except (ValueError, OverflowError):
1195 1195 pass
1196 1196 else:
1197 1197 if i + off < len(self.series):
1198 1198 return self.series[i + off]
1199 1199 raise util.Abort(_("patch %s not in series") % patch)
1200 1200
1201 1201 def push(self, repo, patch=None, force=False, list=False, mergeq=None,
1202 1202 all=False, move=False, exact=False, nobackup=False,
1203 1203 keepchanges=False):
1204 1204 self.checkkeepchanges(keepchanges, force)
1205 1205 diffopts = self.diffopts()
1206 1206 wlock = repo.wlock()
1207 1207 try:
1208 1208 heads = []
1209 1209 for hs in repo.branchmap().itervalues():
1210 1210 heads.extend(hs)
1211 1211 if not heads:
1212 1212 heads = [nullid]
1213 1213 if repo.dirstate.p1() not in heads and not exact:
1214 1214 self.ui.status(_("(working directory not at a head)\n"))
1215 1215
1216 1216 if not self.series:
1217 1217 self.ui.warn(_('no patches in series\n'))
1218 1218 return 0
1219 1219
1220 1220 # Suppose our series file is: A B C and the current 'top'
1221 1221 # patch is B. qpush C should be performed (moving forward)
1222 1222 # qpush B is a NOP (no change) qpush A is an error (can't
1223 1223 # go backwards with qpush)
1224 1224 if patch:
1225 1225 patch = self.lookup(patch)
1226 1226 info = self.isapplied(patch)
1227 1227 if info and info[0] >= len(self.applied) - 1:
1228 1228 self.ui.warn(
1229 1229 _('qpush: %s is already at the top\n') % patch)
1230 1230 return 0
1231 1231
1232 1232 pushable, reason = self.pushable(patch)
1233 1233 if pushable:
1234 1234 if self.series.index(patch) < self.seriesend():
1235 1235 raise util.Abort(
1236 1236 _("cannot push to a previous patch: %s") % patch)
1237 1237 else:
1238 1238 if reason:
1239 1239 reason = _('guarded by %s') % reason
1240 1240 else:
1241 1241 reason = _('no matching guards')
1242 1242 self.ui.warn(_("cannot push '%s' - %s\n") % (patch, reason))
1243 1243 return 1
1244 1244 elif all:
1245 1245 patch = self.series[-1]
1246 1246 if self.isapplied(patch):
1247 1247 self.ui.warn(_('all patches are currently applied\n'))
1248 1248 return 0
1249 1249
1250 1250 # Following the above example, starting at 'top' of B:
1251 1251 # qpush should be performed (pushes C), but a subsequent
1252 1252 # qpush without an argument is an error (nothing to
1253 1253 # apply). This allows a loop of "...while hg qpush..." to
1254 1254 # work as it detects an error when done
1255 1255 start = self.seriesend()
1256 1256 if start == len(self.series):
1257 1257 self.ui.warn(_('patch series already fully applied\n'))
1258 1258 return 1
1259 1259 if not force and not keepchanges:
1260 1260 self.checklocalchanges(repo, refresh=self.applied)
1261 1261
1262 1262 if exact:
1263 1263 if keepchanges:
1264 1264 raise util.Abort(
1265 1265 _("cannot use --exact and --keep-changes together"))
1266 1266 if move:
1267 1267 raise util.Abort(_('cannot use --exact and --move '
1268 1268 'together'))
1269 1269 if self.applied:
1270 1270 raise util.Abort(_('cannot push --exact with applied '
1271 1271 'patches'))
1272 1272 root = self.series[start]
1273 1273 target = patchheader(self.join(root), self.plainmode).parent
1274 1274 if not target:
1275 1275 raise util.Abort(
1276 1276 _("%s does not have a parent recorded") % root)
1277 1277 if not repo[target] == repo['.']:
1278 1278 hg.update(repo, target)
1279 1279
1280 1280 if move:
1281 1281 if not patch:
1282 1282 raise util.Abort(_("please specify the patch to move"))
1283 1283 for fullstart, rpn in enumerate(self.fullseries):
1284 1284 # strip markers for patch guards
1285 1285 if self.guard_re.split(rpn, 1)[0] == self.series[start]:
1286 1286 break
1287 1287 for i, rpn in enumerate(self.fullseries[fullstart:]):
1288 1288 # strip markers for patch guards
1289 1289 if self.guard_re.split(rpn, 1)[0] == patch:
1290 1290 break
1291 1291 index = fullstart + i
1292 1292 assert index < len(self.fullseries)
1293 1293 fullpatch = self.fullseries[index]
1294 1294 del self.fullseries[index]
1295 1295 self.fullseries.insert(fullstart, fullpatch)
1296 1296 self.parseseries()
1297 1297 self.seriesdirty = True
1298 1298
1299 1299 self.applieddirty = True
1300 1300 if start > 0:
1301 1301 self.checktoppatch(repo)
1302 1302 if not patch:
1303 1303 patch = self.series[start]
1304 1304 end = start + 1
1305 1305 else:
1306 1306 end = self.series.index(patch, start) + 1
1307 1307
1308 1308 tobackup = set()
1309 1309 if (not nobackup and force) or keepchanges:
1310 1310 m, a, r, d = self.checklocalchanges(repo, force=True)
1311 1311 if keepchanges:
1312 1312 tobackup.update(m + a + r + d)
1313 1313 else:
1314 1314 tobackup.update(m + a)
1315 1315
1316 1316 s = self.series[start:end]
1317 1317 all_files = set()
1318 1318 try:
1319 1319 if mergeq:
1320 1320 ret = self.mergepatch(repo, mergeq, s, diffopts)
1321 1321 else:
1322 1322 ret = self.apply(repo, s, list, all_files=all_files,
1323 1323 tobackup=tobackup, keepchanges=keepchanges)
1324 1324 except: # re-raises
1325 1325 self.ui.warn(_('cleaning up working directory...'))
1326 1326 node = repo.dirstate.p1()
1327 1327 hg.revert(repo, node, None)
1328 1328 # only remove unknown files that we know we touched or
1329 1329 # created while patching
1330 1330 for f in all_files:
1331 1331 if f not in repo.dirstate:
1332 1332 util.unlinkpath(repo.wjoin(f), ignoremissing=True)
1333 1333 self.ui.warn(_('done\n'))
1334 1334 raise
1335 1335
1336 1336 if not self.applied:
1337 1337 return ret[0]
1338 1338 top = self.applied[-1].name
1339 1339 if ret[0] and ret[0] > 1:
1340 1340 msg = _("errors during apply, please fix and refresh %s\n")
1341 1341 self.ui.write(msg % top)
1342 1342 else:
1343 1343 self.ui.write(_("now at: %s\n") % top)
1344 1344 return ret[0]
1345 1345
1346 1346 finally:
1347 1347 wlock.release()
1348 1348
1349 1349 def pop(self, repo, patch=None, force=False, update=True, all=False,
1350 1350 nobackup=False, keepchanges=False):
1351 1351 self.checkkeepchanges(keepchanges, force)
1352 1352 wlock = repo.wlock()
1353 1353 try:
1354 1354 if patch:
1355 1355 # index, rev, patch
1356 1356 info = self.isapplied(patch)
1357 1357 if not info:
1358 1358 patch = self.lookup(patch)
1359 1359 info = self.isapplied(patch)
1360 1360 if not info:
1361 1361 raise util.Abort(_("patch %s is not applied") % patch)
1362 1362
1363 1363 if not self.applied:
1364 1364 # Allow qpop -a to work repeatedly,
1365 1365 # but not qpop without an argument
1366 1366 self.ui.warn(_("no patches applied\n"))
1367 1367 return not all
1368 1368
1369 1369 if all:
1370 1370 start = 0
1371 1371 elif patch:
1372 1372 start = info[0] + 1
1373 1373 else:
1374 1374 start = len(self.applied) - 1
1375 1375
1376 1376 if start >= len(self.applied):
1377 1377 self.ui.warn(_("qpop: %s is already at the top\n") % patch)
1378 1378 return
1379 1379
1380 1380 if not update:
1381 1381 parents = repo.dirstate.parents()
1382 1382 rr = [x.node for x in self.applied]
1383 1383 for p in parents:
1384 1384 if p in rr:
1385 1385 self.ui.warn(_("qpop: forcing dirstate update\n"))
1386 1386 update = True
1387 1387 else:
1388 1388 parents = [p.node() for p in repo[None].parents()]
1389 1389 needupdate = False
1390 1390 for entry in self.applied[start:]:
1391 1391 if entry.node in parents:
1392 1392 needupdate = True
1393 1393 break
1394 1394 update = needupdate
1395 1395
1396 1396 tobackup = set()
1397 1397 if update:
1398 1398 m, a, r, d = self.checklocalchanges(
1399 1399 repo, force=force or keepchanges)
1400 1400 if force:
1401 1401 if not nobackup:
1402 1402 tobackup.update(m + a)
1403 1403 elif keepchanges:
1404 1404 tobackup.update(m + a + r + d)
1405 1405
1406 1406 self.applieddirty = True
1407 1407 end = len(self.applied)
1408 1408 rev = self.applied[start].node
1409 1409
1410 1410 try:
1411 1411 heads = repo.changelog.heads(rev)
1412 1412 except error.LookupError:
1413 1413 node = short(rev)
1414 1414 raise util.Abort(_('trying to pop unknown node %s') % node)
1415 1415
1416 1416 if heads != [self.applied[-1].node]:
1417 1417 raise util.Abort(_("popping would remove a revision not "
1418 1418 "managed by this patch queue"))
1419 1419 if not repo[self.applied[-1].node].mutable():
1420 1420 raise util.Abort(
1421 1421 _("popping would remove an immutable revision"),
1422 1422 hint=_('see "hg help phases" for details'))
1423 1423
1424 1424 # we know there are no local changes, so we can make a simplified
1425 1425 # form of hg.update.
1426 1426 if update:
1427 1427 qp = self.qparents(repo, rev)
1428 1428 ctx = repo[qp]
1429 1429 m, a, r, d = repo.status(qp, '.')[:4]
1430 1430 if d:
1431 1431 raise util.Abort(_("deletions found between repo revs"))
1432 1432
1433 1433 tobackup = set(a + m + r) & tobackup
1434 1434 if keepchanges and tobackup:
1435 1435 raise util.Abort(_("local changes found, refresh first"))
1436 1436 self.backup(repo, tobackup)
1437 1437
1438 1438 for f in a:
1439 1439 util.unlinkpath(repo.wjoin(f), ignoremissing=True)
1440 1440 repo.dirstate.drop(f)
1441 1441 for f in m + r:
1442 1442 fctx = ctx[f]
1443 1443 repo.wwrite(f, fctx.data(), fctx.flags())
1444 1444 repo.dirstate.normal(f)
1445 1445 repo.setparents(qp, nullid)
1446 1446 for patch in reversed(self.applied[start:end]):
1447 1447 self.ui.status(_("popping %s\n") % patch.name)
1448 1448 del self.applied[start:end]
1449 1449 strip(self.ui, repo, [rev], update=False, backup='strip')
1450 1450 for s, state in repo['.'].substate.items():
1451 1451 repo['.'].sub(s).get(state)
1452 1452 if self.applied:
1453 1453 self.ui.write(_("now at: %s\n") % self.applied[-1].name)
1454 1454 else:
1455 1455 self.ui.write(_("patch queue now empty\n"))
1456 1456 finally:
1457 1457 wlock.release()
1458 1458
1459 1459 def diff(self, repo, pats, opts):
1460 1460 top, patch = self.checktoppatch(repo)
1461 1461 if not top:
1462 1462 self.ui.write(_("no patches applied\n"))
1463 1463 return
1464 1464 qp = self.qparents(repo, top)
1465 1465 if opts.get('reverse'):
1466 1466 node1, node2 = None, qp
1467 1467 else:
1468 1468 node1, node2 = qp, None
1469 1469 diffopts = self.diffopts(opts, patch)
1470 1470 self.printdiff(repo, diffopts, node1, node2, files=pats, opts=opts)
1471 1471
1472 1472 def refresh(self, repo, pats=None, **opts):
1473 1473 if not self.applied:
1474 1474 self.ui.write(_("no patches applied\n"))
1475 1475 return 1
1476 1476 msg = opts.get('msg', '').rstrip()
1477 1477 newuser = opts.get('user')
1478 1478 newdate = opts.get('date')
1479 1479 if newdate:
1480 1480 newdate = '%d %d' % util.parsedate(newdate)
1481 1481 wlock = repo.wlock()
1482 1482
1483 1483 try:
1484 1484 self.checktoppatch(repo)
1485 1485 (top, patchfn) = (self.applied[-1].node, self.applied[-1].name)
1486 1486 if repo.changelog.heads(top) != [top]:
1487 1487 raise util.Abort(_("cannot refresh a revision with children"))
1488 1488 if not repo[top].mutable():
1489 1489 raise util.Abort(_("cannot refresh immutable revision"),
1490 1490 hint=_('see "hg help phases" for details'))
1491 1491
1492 1492 cparents = repo.changelog.parents(top)
1493 1493 patchparent = self.qparents(repo, top)
1494 1494
1495 1495 inclsubs = checksubstate(repo, hex(patchparent))
1496 1496 if inclsubs:
1497 1497 inclsubs.append('.hgsubstate')
1498 1498 substatestate = repo.dirstate['.hgsubstate']
1499 1499
1500 1500 ph = patchheader(self.join(patchfn), self.plainmode)
1501 1501 diffopts = self.diffopts({'git': opts.get('git')}, patchfn)
1502 1502 if msg:
1503 1503 ph.setmessage(msg)
1504 1504 if newuser:
1505 1505 ph.setuser(newuser)
1506 1506 if newdate:
1507 1507 ph.setdate(newdate)
1508 1508 ph.setparent(hex(patchparent))
1509 1509
1510 1510 # only commit new patch when write is complete
1511 1511 patchf = self.opener(patchfn, 'w', atomictemp=True)
1512 1512
1513 1513 comments = str(ph)
1514 1514 if comments:
1515 1515 patchf.write(comments)
1516 1516
1517 1517 # update the dirstate in place, strip off the qtip commit
1518 1518 # and then commit.
1519 1519 #
1520 1520 # this should really read:
1521 1521 # mm, dd, aa = repo.status(top, patchparent)[:3]
1522 1522 # but we do it backwards to take advantage of manifest/changelog
1523 1523 # caching against the next repo.status call
1524 1524 mm, aa, dd = repo.status(patchparent, top)[:3]
1525 1525 changes = repo.changelog.read(top)
1526 1526 man = repo.manifest.read(changes[0])
1527 1527 aaa = aa[:]
1528 1528 matchfn = scmutil.match(repo[None], pats, opts)
1529 1529 # in short mode, we only diff the files included in the
1530 1530 # patch already plus specified files
1531 1531 if opts.get('short'):
1532 1532 # if amending a patch, we start with existing
1533 1533 # files plus specified files - unfiltered
1534 1534 match = scmutil.matchfiles(repo, mm + aa + dd + matchfn.files())
1535 1535 # filter with include/exclude options
1536 1536 matchfn = scmutil.match(repo[None], opts=opts)
1537 1537 else:
1538 1538 match = scmutil.matchall(repo)
1539 1539 m, a, r, d = repo.status(match=match)[:4]
1540 1540 mm = set(mm)
1541 1541 aa = set(aa)
1542 1542 dd = set(dd)
1543 1543
1544 1544 # we might end up with files that were added between
1545 1545 # qtip and the dirstate parent, but then changed in the
1546 1546 # local dirstate. in this case, we want them to only
1547 1547 # show up in the added section
1548 1548 for x in m:
1549 1549 if x not in aa:
1550 1550 mm.add(x)
1551 1551 # we might end up with files added by the local dirstate that
1552 1552 # were deleted by the patch. In this case, they should only
1553 1553 # show up in the changed section.
1554 1554 for x in a:
1555 1555 if x in dd:
1556 1556 dd.remove(x)
1557 1557 mm.add(x)
1558 1558 else:
1559 1559 aa.add(x)
1560 1560 # make sure any files deleted in the local dirstate
1561 1561 # are not in the add or change column of the patch
1562 1562 forget = []
1563 1563 for x in d + r:
1564 1564 if x in aa:
1565 1565 aa.remove(x)
1566 1566 forget.append(x)
1567 1567 continue
1568 1568 else:
1569 1569 mm.discard(x)
1570 1570 dd.add(x)
1571 1571
1572 1572 m = list(mm)
1573 1573 r = list(dd)
1574 1574 a = list(aa)
1575 1575
1576 1576 # create 'match' that includes the files to be recommitted.
1577 1577 # apply matchfn via repo.status to ensure correct case handling.
1578 1578 cm, ca, cr, cd = repo.status(patchparent, match=matchfn)[:4]
1579 1579 allmatches = set(cm + ca + cr + cd)
1580 1580 refreshchanges = [x.intersection(allmatches) for x in (mm, aa, dd)]
1581 1581
1582 1582 files = set(inclsubs)
1583 1583 for x in refreshchanges:
1584 1584 files.update(x)
1585 1585 match = scmutil.matchfiles(repo, files)
1586 1586
1587 1587 bmlist = repo[top].bookmarks()
1588 1588
1589 1589 try:
1590 1590 if diffopts.git or diffopts.upgrade:
1591 1591 copies = {}
1592 1592 for dst in a:
1593 1593 src = repo.dirstate.copied(dst)
1594 1594 # during qfold, the source file for copies may
1595 1595 # be removed. Treat this as a simple add.
1596 1596 if src is not None and src in repo.dirstate:
1597 1597 copies.setdefault(src, []).append(dst)
1598 1598 repo.dirstate.add(dst)
1599 1599 # remember the copies between patchparent and qtip
1600 1600 for dst in aaa:
1601 1601 f = repo.file(dst)
1602 1602 src = f.renamed(man[dst])
1603 1603 if src:
1604 1604 copies.setdefault(src[0], []).extend(
1605 1605 copies.get(dst, []))
1606 1606 if dst in a:
1607 1607 copies[src[0]].append(dst)
1608 1608 # we can't copy a file created by the patch itself
1609 1609 if dst in copies:
1610 1610 del copies[dst]
1611 1611 for src, dsts in copies.iteritems():
1612 1612 for dst in dsts:
1613 1613 repo.dirstate.copy(src, dst)
1614 1614 else:
1615 1615 for dst in a:
1616 1616 repo.dirstate.add(dst)
1617 1617 # Drop useless copy information
1618 1618 for f in list(repo.dirstate.copies()):
1619 1619 repo.dirstate.copy(None, f)
1620 1620 for f in r:
1621 1621 repo.dirstate.remove(f)
1622 1622 # if the patch excludes a modified file, mark that
1623 1623 # file with mtime=0 so status can see it.
1624 1624 mm = []
1625 1625 for i in xrange(len(m) - 1, -1, -1):
1626 1626 if not matchfn(m[i]):
1627 1627 mm.append(m[i])
1628 1628 del m[i]
1629 1629 for f in m:
1630 1630 repo.dirstate.normal(f)
1631 1631 for f in mm:
1632 1632 repo.dirstate.normallookup(f)
1633 1633 for f in forget:
1634 1634 repo.dirstate.drop(f)
1635 1635
1636 1636 if not msg:
1637 1637 if not ph.message:
1638 1638 message = "[mq]: %s\n" % patchfn
1639 1639 else:
1640 1640 message = "\n".join(ph.message)
1641 1641 else:
1642 1642 message = msg
1643 1643
1644 1644 user = ph.user or changes[1]
1645 1645
1646 1646 oldphase = repo[top].phase()
1647 1647
1648 1648 # assumes strip can roll itself back if interrupted
1649 1649 repo.setparents(*cparents)
1650 1650 self.applied.pop()
1651 1651 self.applieddirty = True
1652 1652 strip(self.ui, repo, [top], update=False, backup='strip')
1653 1653 except: # re-raises
1654 1654 repo.dirstate.invalidate()
1655 1655 raise
1656 1656
1657 1657 try:
1658 1658 # might be nice to attempt to roll back strip after this
1659 1659
1660 1660 # Ensure we create a new changeset in the same phase than
1661 1661 # the old one.
1662 1662 n = newcommit(repo, oldphase, message, user, ph.date,
1663 1663 match=match, force=True)
1664 1664 # only write patch after a successful commit
1665 1665 c = [list(x) for x in refreshchanges]
1666 1666 if inclsubs:
1667 1667 self.putsubstate2changes(substatestate, c)
1668 1668 chunks = patchmod.diff(repo, patchparent,
1669 1669 changes=c, opts=diffopts)
1670 1670 for chunk in chunks:
1671 1671 patchf.write(chunk)
1672 1672 patchf.close()
1673 1673
1674 1674 marks = repo._bookmarks
1675 1675 for bm in bmlist:
1676 1676 marks[bm] = n
1677 1677 marks.write()
1678 1678
1679 1679 self.applied.append(statusentry(n, patchfn))
1680 1680 except: # re-raises
1681 1681 ctx = repo[cparents[0]]
1682 1682 repo.dirstate.rebuild(ctx.node(), ctx.manifest())
1683 1683 self.savedirty()
1684 1684 self.ui.warn(_('refresh interrupted while patch was popped! '
1685 1685 '(revert --all, qpush to recover)\n'))
1686 1686 raise
1687 1687 finally:
1688 1688 wlock.release()
1689 1689 self.removeundo(repo)
1690 1690
1691 1691 def init(self, repo, create=False):
1692 1692 if not create and os.path.isdir(self.path):
1693 1693 raise util.Abort(_("patch queue directory already exists"))
1694 1694 try:
1695 1695 os.mkdir(self.path)
1696 1696 except OSError, inst:
1697 1697 if inst.errno != errno.EEXIST or not create:
1698 1698 raise
1699 1699 if create:
1700 1700 return self.qrepo(create=True)
1701 1701
1702 1702 def unapplied(self, repo, patch=None):
1703 1703 if patch and patch not in self.series:
1704 1704 raise util.Abort(_("patch %s is not in series file") % patch)
1705 1705 if not patch:
1706 1706 start = self.seriesend()
1707 1707 else:
1708 1708 start = self.series.index(patch) + 1
1709 1709 unapplied = []
1710 1710 for i in xrange(start, len(self.series)):
1711 1711 pushable, reason = self.pushable(i)
1712 1712 if pushable:
1713 1713 unapplied.append((i, self.series[i]))
1714 1714 self.explainpushable(i)
1715 1715 return unapplied
1716 1716
1717 1717 def qseries(self, repo, missing=None, start=0, length=None, status=None,
1718 1718 summary=False):
1719 1719 def displayname(pfx, patchname, state):
1720 1720 if pfx:
1721 1721 self.ui.write(pfx)
1722 1722 if summary:
1723 1723 ph = patchheader(self.join(patchname), self.plainmode)
1724 1724 msg = ph.message and ph.message[0] or ''
1725 1725 if self.ui.formatted():
1726 1726 width = self.ui.termwidth() - len(pfx) - len(patchname) - 2
1727 1727 if width > 0:
1728 1728 msg = util.ellipsis(msg, width)
1729 1729 else:
1730 1730 msg = ''
1731 1731 self.ui.write(patchname, label='qseries.' + state)
1732 1732 self.ui.write(': ')
1733 1733 self.ui.write(msg, label='qseries.message.' + state)
1734 1734 else:
1735 1735 self.ui.write(patchname, label='qseries.' + state)
1736 1736 self.ui.write('\n')
1737 1737
1738 1738 applied = set([p.name for p in self.applied])
1739 1739 if length is None:
1740 1740 length = len(self.series) - start
1741 1741 if not missing:
1742 1742 if self.ui.verbose:
1743 1743 idxwidth = len(str(start + length - 1))
1744 1744 for i in xrange(start, start + length):
1745 1745 patch = self.series[i]
1746 1746 if patch in applied:
1747 1747 char, state = 'A', 'applied'
1748 1748 elif self.pushable(i)[0]:
1749 1749 char, state = 'U', 'unapplied'
1750 1750 else:
1751 1751 char, state = 'G', 'guarded'
1752 1752 pfx = ''
1753 1753 if self.ui.verbose:
1754 1754 pfx = '%*d %s ' % (idxwidth, i, char)
1755 1755 elif status and status != char:
1756 1756 continue
1757 1757 displayname(pfx, patch, state)
1758 1758 else:
1759 1759 msng_list = []
1760 1760 for root, dirs, files in os.walk(self.path):
1761 1761 d = root[len(self.path) + 1:]
1762 1762 for f in files:
1763 1763 fl = os.path.join(d, f)
1764 1764 if (fl not in self.series and
1765 1765 fl not in (self.statuspath, self.seriespath,
1766 1766 self.guardspath)
1767 1767 and not fl.startswith('.')):
1768 1768 msng_list.append(fl)
1769 1769 for x in sorted(msng_list):
1770 1770 pfx = self.ui.verbose and ('D ') or ''
1771 1771 displayname(pfx, x, 'missing')
1772 1772
1773 1773 def issaveline(self, l):
1774 1774 if l.name == '.hg.patches.save.line':
1775 1775 return True
1776 1776
1777 1777 def qrepo(self, create=False):
1778 1778 ui = self.baseui.copy()
1779 1779 if create or os.path.isdir(self.join(".hg")):
1780 1780 return hg.repository(ui, path=self.path, create=create)
1781 1781
1782 1782 def restore(self, repo, rev, delete=None, qupdate=None):
1783 1783 desc = repo[rev].description().strip()
1784 1784 lines = desc.splitlines()
1785 1785 i = 0
1786 1786 datastart = None
1787 1787 series = []
1788 1788 applied = []
1789 1789 qpp = None
1790 1790 for i, line in enumerate(lines):
1791 1791 if line == 'Patch Data:':
1792 1792 datastart = i + 1
1793 1793 elif line.startswith('Dirstate:'):
1794 1794 l = line.rstrip()
1795 1795 l = l[10:].split(' ')
1796 1796 qpp = [bin(x) for x in l]
1797 1797 elif datastart is not None:
1798 1798 l = line.rstrip()
1799 1799 n, name = l.split(':', 1)
1800 1800 if n:
1801 1801 applied.append(statusentry(bin(n), name))
1802 1802 else:
1803 1803 series.append(l)
1804 1804 if datastart is None:
1805 1805 self.ui.warn(_("no saved patch data found\n"))
1806 1806 return 1
1807 1807 self.ui.warn(_("restoring status: %s\n") % lines[0])
1808 1808 self.fullseries = series
1809 1809 self.applied = applied
1810 1810 self.parseseries()
1811 1811 self.seriesdirty = True
1812 1812 self.applieddirty = True
1813 1813 heads = repo.changelog.heads()
1814 1814 if delete:
1815 1815 if rev not in heads:
1816 1816 self.ui.warn(_("save entry has children, leaving it alone\n"))
1817 1817 else:
1818 1818 self.ui.warn(_("removing save entry %s\n") % short(rev))
1819 1819 pp = repo.dirstate.parents()
1820 1820 if rev in pp:
1821 1821 update = True
1822 1822 else:
1823 1823 update = False
1824 1824 strip(self.ui, repo, [rev], update=update, backup='strip')
1825 1825 if qpp:
1826 1826 self.ui.warn(_("saved queue repository parents: %s %s\n") %
1827 1827 (short(qpp[0]), short(qpp[1])))
1828 1828 if qupdate:
1829 1829 self.ui.status(_("updating queue directory\n"))
1830 1830 r = self.qrepo()
1831 1831 if not r:
1832 1832 self.ui.warn(_("unable to load queue repository\n"))
1833 1833 return 1
1834 1834 hg.clean(r, qpp[0])
1835 1835
1836 1836 def save(self, repo, msg=None):
1837 1837 if not self.applied:
1838 1838 self.ui.warn(_("save: no patches applied, exiting\n"))
1839 1839 return 1
1840 1840 if self.issaveline(self.applied[-1]):
1841 1841 self.ui.warn(_("status is already saved\n"))
1842 1842 return 1
1843 1843
1844 1844 if not msg:
1845 1845 msg = _("hg patches saved state")
1846 1846 else:
1847 1847 msg = "hg patches: " + msg.rstrip('\r\n')
1848 1848 r = self.qrepo()
1849 1849 if r:
1850 1850 pp = r.dirstate.parents()
1851 1851 msg += "\nDirstate: %s %s" % (hex(pp[0]), hex(pp[1]))
1852 1852 msg += "\n\nPatch Data:\n"
1853 1853 msg += ''.join('%s\n' % x for x in self.applied)
1854 1854 msg += ''.join(':%s\n' % x for x in self.fullseries)
1855 1855 n = repo.commit(msg, force=True)
1856 1856 if not n:
1857 1857 self.ui.warn(_("repo commit failed\n"))
1858 1858 return 1
1859 1859 self.applied.append(statusentry(n, '.hg.patches.save.line'))
1860 1860 self.applieddirty = True
1861 1861 self.removeundo(repo)
1862 1862
1863 1863 def fullseriesend(self):
1864 1864 if self.applied:
1865 1865 p = self.applied[-1].name
1866 1866 end = self.findseries(p)
1867 1867 if end is None:
1868 1868 return len(self.fullseries)
1869 1869 return end + 1
1870 1870 return 0
1871 1871
1872 1872 def seriesend(self, all_patches=False):
1873 1873 """If all_patches is False, return the index of the next pushable patch
1874 1874 in the series, or the series length. If all_patches is True, return the
1875 1875 index of the first patch past the last applied one.
1876 1876 """
1877 1877 end = 0
1878 1878 def nextpatch(start):
1879 1879 if all_patches or start >= len(self.series):
1880 1880 return start
1881 1881 for i in xrange(start, len(self.series)):
1882 1882 p, reason = self.pushable(i)
1883 1883 if p:
1884 1884 return i
1885 1885 self.explainpushable(i)
1886 1886 return len(self.series)
1887 1887 if self.applied:
1888 1888 p = self.applied[-1].name
1889 1889 try:
1890 1890 end = self.series.index(p)
1891 1891 except ValueError:
1892 1892 return 0
1893 1893 return nextpatch(end + 1)
1894 1894 return nextpatch(end)
1895 1895
1896 1896 def appliedname(self, index):
1897 1897 pname = self.applied[index].name
1898 1898 if not self.ui.verbose:
1899 1899 p = pname
1900 1900 else:
1901 1901 p = str(self.series.index(pname)) + " " + pname
1902 1902 return p
1903 1903
1904 1904 def qimport(self, repo, files, patchname=None, rev=None, existing=None,
1905 1905 force=None, git=False):
1906 1906 def checkseries(patchname):
1907 1907 if patchname in self.series:
1908 1908 raise util.Abort(_('patch %s is already in the series file')
1909 1909 % patchname)
1910 1910
1911 1911 if rev:
1912 1912 if files:
1913 1913 raise util.Abort(_('option "-r" not valid when importing '
1914 1914 'files'))
1915 1915 rev = scmutil.revrange(repo, rev)
1916 1916 rev.sort(reverse=True)
1917 1917 elif not files:
1918 1918 raise util.Abort(_('no files or revisions specified'))
1919 1919 if (len(files) > 1 or len(rev) > 1) and patchname:
1920 1920 raise util.Abort(_('option "-n" not valid when importing multiple '
1921 1921 'patches'))
1922 1922 imported = []
1923 1923 if rev:
1924 1924 # If mq patches are applied, we can only import revisions
1925 1925 # that form a linear path to qbase.
1926 1926 # Otherwise, they should form a linear path to a head.
1927 1927 heads = repo.changelog.heads(repo.changelog.node(rev[-1]))
1928 1928 if len(heads) > 1:
1929 1929 raise util.Abort(_('revision %d is the root of more than one '
1930 1930 'branch') % rev[-1])
1931 1931 if self.applied:
1932 1932 base = repo.changelog.node(rev[0])
1933 1933 if base in [n.node for n in self.applied]:
1934 1934 raise util.Abort(_('revision %d is already managed')
1935 1935 % rev[0])
1936 1936 if heads != [self.applied[-1].node]:
1937 1937 raise util.Abort(_('revision %d is not the parent of '
1938 1938 'the queue') % rev[0])
1939 1939 base = repo.changelog.rev(self.applied[0].node)
1940 1940 lastparent = repo.changelog.parentrevs(base)[0]
1941 1941 else:
1942 1942 if heads != [repo.changelog.node(rev[0])]:
1943 1943 raise util.Abort(_('revision %d has unmanaged children')
1944 1944 % rev[0])
1945 1945 lastparent = None
1946 1946
1947 1947 diffopts = self.diffopts({'git': git})
1948 1948 for r in rev:
1949 1949 if not repo[r].mutable():
1950 1950 raise util.Abort(_('revision %d is not mutable') % r,
1951 1951 hint=_('see "hg help phases" for details'))
1952 1952 p1, p2 = repo.changelog.parentrevs(r)
1953 1953 n = repo.changelog.node(r)
1954 1954 if p2 != nullrev:
1955 1955 raise util.Abort(_('cannot import merge revision %d') % r)
1956 1956 if lastparent and lastparent != r:
1957 1957 raise util.Abort(_('revision %d is not the parent of %d')
1958 1958 % (r, lastparent))
1959 1959 lastparent = p1
1960 1960
1961 1961 if not patchname:
1962 1962 patchname = normname('%d.diff' % r)
1963 1963 checkseries(patchname)
1964 1964 self.checkpatchname(patchname, force)
1965 1965 self.fullseries.insert(0, patchname)
1966 1966
1967 1967 patchf = self.opener(patchname, "w")
1968 1968 cmdutil.export(repo, [n], fp=patchf, opts=diffopts)
1969 1969 patchf.close()
1970 1970
1971 1971 se = statusentry(n, patchname)
1972 1972 self.applied.insert(0, se)
1973 1973
1974 1974 self.added.append(patchname)
1975 1975 imported.append(patchname)
1976 1976 patchname = None
1977 1977 if rev and repo.ui.configbool('mq', 'secret', False):
1978 1978 # if we added anything with --rev, we must move the secret root
1979 1979 phases.retractboundary(repo, phases.secret, [n])
1980 1980 self.parseseries()
1981 1981 self.applieddirty = True
1982 1982 self.seriesdirty = True
1983 1983
1984 1984 for i, filename in enumerate(files):
1985 1985 if existing:
1986 1986 if filename == '-':
1987 1987 raise util.Abort(_('-e is incompatible with import from -'))
1988 1988 filename = normname(filename)
1989 1989 self.checkreservedname(filename)
1990 1990 originpath = self.join(filename)
1991 1991 if not os.path.isfile(originpath):
1992 1992 raise util.Abort(_("patch %s does not exist") % filename)
1993 1993
1994 1994 if patchname:
1995 1995 self.checkpatchname(patchname, force)
1996 1996
1997 1997 self.ui.write(_('renaming %s to %s\n')
1998 1998 % (filename, patchname))
1999 1999 util.rename(originpath, self.join(patchname))
2000 2000 else:
2001 2001 patchname = filename
2002 2002
2003 2003 else:
2004 2004 if filename == '-' and not patchname:
2005 2005 raise util.Abort(_('need --name to import a patch from -'))
2006 2006 elif not patchname:
2007 2007 patchname = normname(os.path.basename(filename.rstrip('/')))
2008 2008 self.checkpatchname(patchname, force)
2009 2009 try:
2010 2010 if filename == '-':
2011 2011 text = self.ui.fin.read()
2012 2012 else:
2013 2013 fp = hg.openpath(self.ui, filename)
2014 2014 text = fp.read()
2015 2015 fp.close()
2016 2016 except (OSError, IOError):
2017 2017 raise util.Abort(_("unable to read file %s") % filename)
2018 2018 patchf = self.opener(patchname, "w")
2019 2019 patchf.write(text)
2020 2020 patchf.close()
2021 2021 if not force:
2022 2022 checkseries(patchname)
2023 2023 if patchname not in self.series:
2024 2024 index = self.fullseriesend() + i
2025 2025 self.fullseries[index:index] = [patchname]
2026 2026 self.parseseries()
2027 2027 self.seriesdirty = True
2028 2028 self.ui.warn(_("adding %s to series file\n") % patchname)
2029 2029 self.added.append(patchname)
2030 2030 imported.append(patchname)
2031 2031 patchname = None
2032 2032
2033 2033 self.removeundo(repo)
2034 2034 return imported
2035 2035
2036 2036 def fixkeepchangesopts(ui, opts):
2037 2037 if (not ui.configbool('mq', 'keepchanges') or opts.get('force')
2038 2038 or opts.get('exact')):
2039 2039 return opts
2040 2040 opts = dict(opts)
2041 2041 opts['keep_changes'] = True
2042 2042 return opts
2043 2043
2044 2044 @command("qdelete|qremove|qrm",
2045 2045 [('k', 'keep', None, _('keep patch file')),
2046 2046 ('r', 'rev', [],
2047 2047 _('stop managing a revision (DEPRECATED)'), _('REV'))],
2048 2048 _('hg qdelete [-k] [PATCH]...'))
2049 2049 def delete(ui, repo, *patches, **opts):
2050 2050 """remove patches from queue
2051 2051
2052 2052 The patches must not be applied, and at least one patch is required. Exact
2053 2053 patch identifiers must be given. With -k/--keep, the patch files are
2054 2054 preserved in the patch directory.
2055 2055
2056 2056 To stop managing a patch and move it into permanent history,
2057 2057 use the :hg:`qfinish` command."""
2058 2058 q = repo.mq
2059 2059 q.delete(repo, patches, opts)
2060 2060 q.savedirty()
2061 2061 return 0
2062 2062
2063 2063 @command("qapplied",
2064 2064 [('1', 'last', None, _('show only the preceding applied patch'))
2065 2065 ] + seriesopts,
2066 2066 _('hg qapplied [-1] [-s] [PATCH]'))
2067 2067 def applied(ui, repo, patch=None, **opts):
2068 2068 """print the patches already applied
2069 2069
2070 2070 Returns 0 on success."""
2071 2071
2072 2072 q = repo.mq
2073 2073
2074 2074 if patch:
2075 2075 if patch not in q.series:
2076 2076 raise util.Abort(_("patch %s is not in series file") % patch)
2077 2077 end = q.series.index(patch) + 1
2078 2078 else:
2079 2079 end = q.seriesend(True)
2080 2080
2081 2081 if opts.get('last') and not end:
2082 2082 ui.write(_("no patches applied\n"))
2083 2083 return 1
2084 2084 elif opts.get('last') and end == 1:
2085 2085 ui.write(_("only one patch applied\n"))
2086 2086 return 1
2087 2087 elif opts.get('last'):
2088 2088 start = end - 2
2089 2089 end = 1
2090 2090 else:
2091 2091 start = 0
2092 2092
2093 2093 q.qseries(repo, length=end, start=start, status='A',
2094 2094 summary=opts.get('summary'))
2095 2095
2096 2096
2097 2097 @command("qunapplied",
2098 2098 [('1', 'first', None, _('show only the first patch'))] + seriesopts,
2099 2099 _('hg qunapplied [-1] [-s] [PATCH]'))
2100 2100 def unapplied(ui, repo, patch=None, **opts):
2101 2101 """print the patches not yet applied
2102 2102
2103 2103 Returns 0 on success."""
2104 2104
2105 2105 q = repo.mq
2106 2106 if patch:
2107 2107 if patch not in q.series:
2108 2108 raise util.Abort(_("patch %s is not in series file") % patch)
2109 2109 start = q.series.index(patch) + 1
2110 2110 else:
2111 2111 start = q.seriesend(True)
2112 2112
2113 2113 if start == len(q.series) and opts.get('first'):
2114 2114 ui.write(_("all patches applied\n"))
2115 2115 return 1
2116 2116
2117 2117 length = opts.get('first') and 1 or None
2118 2118 q.qseries(repo, start=start, length=length, status='U',
2119 2119 summary=opts.get('summary'))
2120 2120
2121 2121 @command("qimport",
2122 2122 [('e', 'existing', None, _('import file in patch directory')),
2123 2123 ('n', 'name', '',
2124 2124 _('name of patch file'), _('NAME')),
2125 2125 ('f', 'force', None, _('overwrite existing files')),
2126 2126 ('r', 'rev', [],
2127 2127 _('place existing revisions under mq control'), _('REV')),
2128 2128 ('g', 'git', None, _('use git extended diff format')),
2129 2129 ('P', 'push', None, _('qpush after importing'))],
2130 2130 _('hg qimport [-e] [-n NAME] [-f] [-g] [-P] [-r REV]... [FILE]...'))
2131 2131 def qimport(ui, repo, *filename, **opts):
2132 2132 """import a patch or existing changeset
2133 2133
2134 2134 The patch is inserted into the series after the last applied
2135 2135 patch. If no patches have been applied, qimport prepends the patch
2136 2136 to the series.
2137 2137
2138 2138 The patch will have the same name as its source file unless you
2139 2139 give it a new one with -n/--name.
2140 2140
2141 2141 You can register an existing patch inside the patch directory with
2142 2142 the -e/--existing flag.
2143 2143
2144 2144 With -f/--force, an existing patch of the same name will be
2145 2145 overwritten.
2146 2146
2147 2147 An existing changeset may be placed under mq control with -r/--rev
2148 2148 (e.g. qimport --rev . -n patch will place the current revision
2149 2149 under mq control). With -g/--git, patches imported with --rev will
2150 2150 use the git diff format. See the diffs help topic for information
2151 2151 on why this is important for preserving rename/copy information
2152 2152 and permission changes. Use :hg:`qfinish` to remove changesets
2153 2153 from mq control.
2154 2154
2155 2155 To import a patch from standard input, pass - as the patch file.
2156 2156 When importing from standard input, a patch name must be specified
2157 2157 using the --name flag.
2158 2158
2159 2159 To import an existing patch while renaming it::
2160 2160
2161 2161 hg qimport -e existing-patch -n new-name
2162 2162
2163 2163 Returns 0 if import succeeded.
2164 2164 """
2165 2165 lock = repo.lock() # cause this may move phase
2166 2166 try:
2167 2167 q = repo.mq
2168 2168 try:
2169 2169 imported = q.qimport(
2170 2170 repo, filename, patchname=opts.get('name'),
2171 2171 existing=opts.get('existing'), force=opts.get('force'),
2172 2172 rev=opts.get('rev'), git=opts.get('git'))
2173 2173 finally:
2174 2174 q.savedirty()
2175 2175 finally:
2176 2176 lock.release()
2177 2177
2178 2178 if imported and opts.get('push') and not opts.get('rev'):
2179 2179 return q.push(repo, imported[-1])
2180 2180 return 0
2181 2181
2182 2182 def qinit(ui, repo, create):
2183 2183 """initialize a new queue repository
2184 2184
2185 2185 This command also creates a series file for ordering patches, and
2186 2186 an mq-specific .hgignore file in the queue repository, to exclude
2187 2187 the status and guards files (these contain mostly transient state).
2188 2188
2189 2189 Returns 0 if initialization succeeded."""
2190 2190 q = repo.mq
2191 2191 r = q.init(repo, create)
2192 2192 q.savedirty()
2193 2193 if r:
2194 2194 if not os.path.exists(r.wjoin('.hgignore')):
2195 2195 fp = r.wopener('.hgignore', 'w')
2196 2196 fp.write('^\\.hg\n')
2197 2197 fp.write('^\\.mq\n')
2198 2198 fp.write('syntax: glob\n')
2199 2199 fp.write('status\n')
2200 2200 fp.write('guards\n')
2201 2201 fp.close()
2202 2202 if not os.path.exists(r.wjoin('series')):
2203 2203 r.wopener('series', 'w').close()
2204 2204 r[None].add(['.hgignore', 'series'])
2205 2205 commands.add(ui, r)
2206 2206 return 0
2207 2207
2208 2208 @command("^qinit",
2209 2209 [('c', 'create-repo', None, _('create queue repository'))],
2210 2210 _('hg qinit [-c]'))
2211 2211 def init(ui, repo, **opts):
2212 2212 """init a new queue repository (DEPRECATED)
2213 2213
2214 2214 The queue repository is unversioned by default. If
2215 2215 -c/--create-repo is specified, qinit will create a separate nested
2216 2216 repository for patches (qinit -c may also be run later to convert
2217 2217 an unversioned patch repository into a versioned one). You can use
2218 2218 qcommit to commit changes to this queue repository.
2219 2219
2220 2220 This command is deprecated. Without -c, it's implied by other relevant
2221 2221 commands. With -c, use :hg:`init --mq` instead."""
2222 2222 return qinit(ui, repo, create=opts.get('create_repo'))
2223 2223
2224 2224 @command("qclone",
2225 2225 [('', 'pull', None, _('use pull protocol to copy metadata')),
2226 2226 ('U', 'noupdate', None,
2227 2227 _('do not update the new working directories')),
2228 2228 ('', 'uncompressed', None,
2229 2229 _('use uncompressed transfer (fast over LAN)')),
2230 2230 ('p', 'patches', '',
2231 2231 _('location of source patch repository'), _('REPO')),
2232 2232 ] + commands.remoteopts,
2233 2233 _('hg qclone [OPTION]... SOURCE [DEST]'))
2234 2234 def clone(ui, source, dest=None, **opts):
2235 2235 '''clone main and patch repository at same time
2236 2236
2237 2237 If source is local, destination will have no patches applied. If
2238 2238 source is remote, this command can not check if patches are
2239 2239 applied in source, so cannot guarantee that patches are not
2240 2240 applied in destination. If you clone remote repository, be sure
2241 2241 before that it has no patches applied.
2242 2242
2243 2243 Source patch repository is looked for in <src>/.hg/patches by
2244 2244 default. Use -p <url> to change.
2245 2245
2246 2246 The patch directory must be a nested Mercurial repository, as
2247 2247 would be created by :hg:`init --mq`.
2248 2248
2249 2249 Return 0 on success.
2250 2250 '''
2251 2251 def patchdir(repo):
2252 2252 """compute a patch repo url from a repo object"""
2253 2253 url = repo.url()
2254 2254 if url.endswith('/'):
2255 2255 url = url[:-1]
2256 2256 return url + '/.hg/patches'
2257 2257
2258 2258 # main repo (destination and sources)
2259 2259 if dest is None:
2260 2260 dest = hg.defaultdest(source)
2261 2261 sr = hg.peer(ui, opts, ui.expandpath(source))
2262 2262
2263 2263 # patches repo (source only)
2264 2264 if opts.get('patches'):
2265 2265 patchespath = ui.expandpath(opts.get('patches'))
2266 2266 else:
2267 2267 patchespath = patchdir(sr)
2268 2268 try:
2269 2269 hg.peer(ui, opts, patchespath)
2270 2270 except error.RepoError:
2271 2271 raise util.Abort(_('versioned patch repository not found'
2272 2272 ' (see init --mq)'))
2273 2273 qbase, destrev = None, None
2274 2274 if sr.local():
2275 2275 repo = sr.local()
2276 2276 if repo.mq.applied and repo[qbase].phase() != phases.secret:
2277 2277 qbase = repo.mq.applied[0].node
2278 2278 if not hg.islocal(dest):
2279 2279 heads = set(repo.heads())
2280 2280 destrev = list(heads.difference(repo.heads(qbase)))
2281 2281 destrev.append(repo.changelog.parents(qbase)[0])
2282 2282 elif sr.capable('lookup'):
2283 2283 try:
2284 2284 qbase = sr.lookup('qbase')
2285 2285 except error.RepoError:
2286 2286 pass
2287 2287
2288 2288 ui.note(_('cloning main repository\n'))
2289 2289 sr, dr = hg.clone(ui, opts, sr.url(), dest,
2290 2290 pull=opts.get('pull'),
2291 2291 rev=destrev,
2292 2292 update=False,
2293 2293 stream=opts.get('uncompressed'))
2294 2294
2295 2295 ui.note(_('cloning patch repository\n'))
2296 2296 hg.clone(ui, opts, opts.get('patches') or patchdir(sr), patchdir(dr),
2297 2297 pull=opts.get('pull'), update=not opts.get('noupdate'),
2298 2298 stream=opts.get('uncompressed'))
2299 2299
2300 2300 if dr.local():
2301 2301 repo = dr.local()
2302 2302 if qbase:
2303 2303 ui.note(_('stripping applied patches from destination '
2304 2304 'repository\n'))
2305 2305 strip(ui, repo, [qbase], update=False, backup=None)
2306 2306 if not opts.get('noupdate'):
2307 2307 ui.note(_('updating destination repository\n'))
2308 2308 hg.update(repo, repo.changelog.tip())
2309 2309
2310 2310 @command("qcommit|qci",
2311 2311 commands.table["^commit|ci"][1],
2312 2312 _('hg qcommit [OPTION]... [FILE]...'))
2313 2313 def commit(ui, repo, *pats, **opts):
2314 2314 """commit changes in the queue repository (DEPRECATED)
2315 2315
2316 2316 This command is deprecated; use :hg:`commit --mq` instead."""
2317 2317 q = repo.mq
2318 2318 r = q.qrepo()
2319 2319 if not r:
2320 2320 raise util.Abort('no queue repository')
2321 2321 commands.commit(r.ui, r, *pats, **opts)
2322 2322
2323 2323 @command("qseries",
2324 2324 [('m', 'missing', None, _('print patches not in series')),
2325 2325 ] + seriesopts,
2326 2326 _('hg qseries [-ms]'))
2327 2327 def series(ui, repo, **opts):
2328 2328 """print the entire series file
2329 2329
2330 2330 Returns 0 on success."""
2331 2331 repo.mq.qseries(repo, missing=opts.get('missing'),
2332 2332 summary=opts.get('summary'))
2333 2333 return 0
2334 2334
2335 2335 @command("qtop", seriesopts, _('hg qtop [-s]'))
2336 2336 def top(ui, repo, **opts):
2337 2337 """print the name of the current patch
2338 2338
2339 2339 Returns 0 on success."""
2340 2340 q = repo.mq
2341 2341 t = q.applied and q.seriesend(True) or 0
2342 2342 if t:
2343 2343 q.qseries(repo, start=t - 1, length=1, status='A',
2344 2344 summary=opts.get('summary'))
2345 2345 else:
2346 2346 ui.write(_("no patches applied\n"))
2347 2347 return 1
2348 2348
2349 2349 @command("qnext", seriesopts, _('hg qnext [-s]'))
2350 2350 def next(ui, repo, **opts):
2351 2351 """print the name of the next pushable patch
2352 2352
2353 2353 Returns 0 on success."""
2354 2354 q = repo.mq
2355 2355 end = q.seriesend()
2356 2356 if end == len(q.series):
2357 2357 ui.write(_("all patches applied\n"))
2358 2358 return 1
2359 2359 q.qseries(repo, start=end, length=1, summary=opts.get('summary'))
2360 2360
2361 2361 @command("qprev", seriesopts, _('hg qprev [-s]'))
2362 2362 def prev(ui, repo, **opts):
2363 2363 """print the name of the preceding applied patch
2364 2364
2365 2365 Returns 0 on success."""
2366 2366 q = repo.mq
2367 2367 l = len(q.applied)
2368 2368 if l == 1:
2369 2369 ui.write(_("only one patch applied\n"))
2370 2370 return 1
2371 2371 if not l:
2372 2372 ui.write(_("no patches applied\n"))
2373 2373 return 1
2374 2374 idx = q.series.index(q.applied[-2].name)
2375 2375 q.qseries(repo, start=idx, length=1, status='A',
2376 2376 summary=opts.get('summary'))
2377 2377
2378 2378 def setupheaderopts(ui, opts):
2379 2379 if not opts.get('user') and opts.get('currentuser'):
2380 2380 opts['user'] = ui.username()
2381 2381 if not opts.get('date') and opts.get('currentdate'):
2382 2382 opts['date'] = "%d %d" % util.makedate()
2383 2383
2384 2384 @command("^qnew",
2385 2385 [('e', 'edit', None, _('edit commit message')),
2386 2386 ('f', 'force', None, _('import uncommitted changes (DEPRECATED)')),
2387 2387 ('g', 'git', None, _('use git extended diff format')),
2388 2388 ('U', 'currentuser', None, _('add "From: <current user>" to patch')),
2389 2389 ('u', 'user', '',
2390 2390 _('add "From: <USER>" to patch'), _('USER')),
2391 2391 ('D', 'currentdate', None, _('add "Date: <current date>" to patch')),
2392 2392 ('d', 'date', '',
2393 2393 _('add "Date: <DATE>" to patch'), _('DATE'))
2394 2394 ] + commands.walkopts + commands.commitopts,
2395 2395 _('hg qnew [-e] [-m TEXT] [-l FILE] PATCH [FILE]...'))
2396 2396 def new(ui, repo, patch, *args, **opts):
2397 2397 """create a new patch
2398 2398
2399 2399 qnew creates a new patch on top of the currently-applied patch (if
2400 2400 any). The patch will be initialized with any outstanding changes
2401 2401 in the working directory. You may also use -I/--include,
2402 2402 -X/--exclude, and/or a list of files after the patch name to add
2403 2403 only changes to matching files to the new patch, leaving the rest
2404 2404 as uncommitted modifications.
2405 2405
2406 2406 -u/--user and -d/--date can be used to set the (given) user and
2407 2407 date, respectively. -U/--currentuser and -D/--currentdate set user
2408 2408 to current user and date to current date.
2409 2409
2410 2410 -e/--edit, -m/--message or -l/--logfile set the patch header as
2411 2411 well as the commit message. If none is specified, the header is
2412 2412 empty and the commit message is '[mq]: PATCH'.
2413 2413
2414 2414 Use the -g/--git option to keep the patch in the git extended diff
2415 2415 format. Read the diffs help topic for more information on why this
2416 2416 is important for preserving permission changes and copy/rename
2417 2417 information.
2418 2418
2419 2419 Returns 0 on successful creation of a new patch.
2420 2420 """
2421 2421 msg = cmdutil.logmessage(ui, opts)
2422 2422 def getmsg():
2423 2423 return ui.edit(msg, opts.get('user') or ui.username())
2424 2424 q = repo.mq
2425 2425 opts['msg'] = msg
2426 2426 if opts.get('edit'):
2427 2427 opts['msg'] = getmsg
2428 2428 else:
2429 2429 opts['msg'] = msg
2430 2430 setupheaderopts(ui, opts)
2431 2431 q.new(repo, patch, *args, **opts)
2432 2432 q.savedirty()
2433 2433 return 0
2434 2434
2435 2435 @command("^qrefresh",
2436 2436 [('e', 'edit', None, _('edit commit message')),
2437 2437 ('g', 'git', None, _('use git extended diff format')),
2438 2438 ('s', 'short', None,
2439 2439 _('refresh only files already in the patch and specified files')),
2440 2440 ('U', 'currentuser', None,
2441 2441 _('add/update author field in patch with current user')),
2442 2442 ('u', 'user', '',
2443 2443 _('add/update author field in patch with given user'), _('USER')),
2444 2444 ('D', 'currentdate', None,
2445 2445 _('add/update date field in patch with current date')),
2446 2446 ('d', 'date', '',
2447 2447 _('add/update date field in patch with given date'), _('DATE'))
2448 2448 ] + commands.walkopts + commands.commitopts,
2449 2449 _('hg qrefresh [-I] [-X] [-e] [-m TEXT] [-l FILE] [-s] [FILE]...'))
2450 2450 def refresh(ui, repo, *pats, **opts):
2451 2451 """update the current patch
2452 2452
2453 2453 If any file patterns are provided, the refreshed patch will
2454 2454 contain only the modifications that match those patterns; the
2455 2455 remaining modifications will remain in the working directory.
2456 2456
2457 2457 If -s/--short is specified, files currently included in the patch
2458 2458 will be refreshed just like matched files and remain in the patch.
2459 2459
2460 2460 If -e/--edit is specified, Mercurial will start your configured editor for
2461 2461 you to enter a message. In case qrefresh fails, you will find a backup of
2462 2462 your message in ``.hg/last-message.txt``.
2463 2463
2464 2464 hg add/remove/copy/rename work as usual, though you might want to
2465 2465 use git-style patches (-g/--git or [diff] git=1) to track copies
2466 2466 and renames. See the diffs help topic for more information on the
2467 2467 git diff format.
2468 2468
2469 2469 Returns 0 on success.
2470 2470 """
2471 2471 q = repo.mq
2472 2472 message = cmdutil.logmessage(ui, opts)
2473 2473 if opts.get('edit'):
2474 2474 if not q.applied:
2475 2475 ui.write(_("no patches applied\n"))
2476 2476 return 1
2477 2477 if message:
2478 2478 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
2479 2479 patch = q.applied[-1].name
2480 2480 ph = patchheader(q.join(patch), q.plainmode)
2481 2481 message = ui.edit('\n'.join(ph.message), ph.user or ui.username())
2482 2482 # We don't want to lose the patch message if qrefresh fails (issue2062)
2483 2483 repo.savecommitmessage(message)
2484 2484 setupheaderopts(ui, opts)
2485 2485 wlock = repo.wlock()
2486 2486 try:
2487 2487 ret = q.refresh(repo, pats, msg=message, **opts)
2488 2488 q.savedirty()
2489 2489 return ret
2490 2490 finally:
2491 2491 wlock.release()
2492 2492
2493 2493 @command("^qdiff",
2494 2494 commands.diffopts + commands.diffopts2 + commands.walkopts,
2495 2495 _('hg qdiff [OPTION]... [FILE]...'))
2496 2496 def diff(ui, repo, *pats, **opts):
2497 2497 """diff of the current patch and subsequent modifications
2498 2498
2499 2499 Shows a diff which includes the current patch as well as any
2500 2500 changes which have been made in the working directory since the
2501 2501 last refresh (thus showing what the current patch would become
2502 2502 after a qrefresh).
2503 2503
2504 2504 Use :hg:`diff` if you only want to see the changes made since the
2505 2505 last qrefresh, or :hg:`export qtip` if you want to see changes
2506 2506 made by the current patch without including changes made since the
2507 2507 qrefresh.
2508 2508
2509 2509 Returns 0 on success.
2510 2510 """
2511 2511 repo.mq.diff(repo, pats, opts)
2512 2512 return 0
2513 2513
2514 2514 @command('qfold',
2515 2515 [('e', 'edit', None, _('edit patch header')),
2516 2516 ('k', 'keep', None, _('keep folded patch files')),
2517 2517 ] + commands.commitopts,
2518 2518 _('hg qfold [-e] [-k] [-m TEXT] [-l FILE] PATCH...'))
2519 2519 def fold(ui, repo, *files, **opts):
2520 2520 """fold the named patches into the current patch
2521 2521
2522 2522 Patches must not yet be applied. Each patch will be successively
2523 2523 applied to the current patch in the order given. If all the
2524 2524 patches apply successfully, the current patch will be refreshed
2525 2525 with the new cumulative patch, and the folded patches will be
2526 2526 deleted. With -k/--keep, the folded patch files will not be
2527 2527 removed afterwards.
2528 2528
2529 2529 The header for each folded patch will be concatenated with the
2530 2530 current patch header, separated by a line of ``* * *``.
2531 2531
2532 2532 Returns 0 on success."""
2533 2533 q = repo.mq
2534 2534 if not files:
2535 2535 raise util.Abort(_('qfold requires at least one patch name'))
2536 2536 if not q.checktoppatch(repo)[0]:
2537 2537 raise util.Abort(_('no patches applied'))
2538 2538 q.checklocalchanges(repo)
2539 2539
2540 2540 message = cmdutil.logmessage(ui, opts)
2541 2541 if opts.get('edit'):
2542 2542 if message:
2543 2543 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
2544 2544
2545 2545 parent = q.lookup('qtip')
2546 2546 patches = []
2547 2547 messages = []
2548 2548 for f in files:
2549 2549 p = q.lookup(f)
2550 2550 if p in patches or p == parent:
2551 2551 ui.warn(_('skipping already folded patch %s\n') % p)
2552 2552 if q.isapplied(p):
2553 2553 raise util.Abort(_('qfold cannot fold already applied patch %s')
2554 2554 % p)
2555 2555 patches.append(p)
2556 2556
2557 2557 for p in patches:
2558 2558 if not message:
2559 2559 ph = patchheader(q.join(p), q.plainmode)
2560 2560 if ph.message:
2561 2561 messages.append(ph.message)
2562 2562 pf = q.join(p)
2563 2563 (patchsuccess, files, fuzz) = q.patch(repo, pf)
2564 2564 if not patchsuccess:
2565 2565 raise util.Abort(_('error folding patch %s') % p)
2566 2566
2567 2567 if not message:
2568 2568 ph = patchheader(q.join(parent), q.plainmode)
2569 2569 message, user = ph.message, ph.user
2570 2570 for msg in messages:
2571 2571 if msg:
2572 2572 if message:
2573 2573 message.append('* * *')
2574 2574 message.extend(msg)
2575 2575 message = '\n'.join(message)
2576 2576
2577 2577 if opts.get('edit'):
2578 2578 message = ui.edit(message, user or ui.username())
2579 repo.savecommitmessage(message)
2579 2580
2580 2581 diffopts = q.patchopts(q.diffopts(), *patches)
2581 2582 wlock = repo.wlock()
2582 2583 try:
2583 2584 q.refresh(repo, msg=message, git=diffopts.git)
2584 2585 q.delete(repo, patches, opts)
2585 2586 q.savedirty()
2586 2587 finally:
2587 2588 wlock.release()
2588 2589
2589 2590 @command("qgoto",
2590 2591 [('', 'keep-changes', None,
2591 2592 _('tolerate non-conflicting local changes')),
2592 2593 ('f', 'force', None, _('overwrite any local changes')),
2593 2594 ('', 'no-backup', None, _('do not save backup copies of files'))],
2594 2595 _('hg qgoto [OPTION]... PATCH'))
2595 2596 def goto(ui, repo, patch, **opts):
2596 2597 '''push or pop patches until named patch is at top of stack
2597 2598
2598 2599 Returns 0 on success.'''
2599 2600 opts = fixkeepchangesopts(ui, opts)
2600 2601 q = repo.mq
2601 2602 patch = q.lookup(patch)
2602 2603 nobackup = opts.get('no_backup')
2603 2604 keepchanges = opts.get('keep_changes')
2604 2605 if q.isapplied(patch):
2605 2606 ret = q.pop(repo, patch, force=opts.get('force'), nobackup=nobackup,
2606 2607 keepchanges=keepchanges)
2607 2608 else:
2608 2609 ret = q.push(repo, patch, force=opts.get('force'), nobackup=nobackup,
2609 2610 keepchanges=keepchanges)
2610 2611 q.savedirty()
2611 2612 return ret
2612 2613
2613 2614 @command("qguard",
2614 2615 [('l', 'list', None, _('list all patches and guards')),
2615 2616 ('n', 'none', None, _('drop all guards'))],
2616 2617 _('hg qguard [-l] [-n] [PATCH] [-- [+GUARD]... [-GUARD]...]'))
2617 2618 def guard(ui, repo, *args, **opts):
2618 2619 '''set or print guards for a patch
2619 2620
2620 2621 Guards control whether a patch can be pushed. A patch with no
2621 2622 guards is always pushed. A patch with a positive guard ("+foo") is
2622 2623 pushed only if the :hg:`qselect` command has activated it. A patch with
2623 2624 a negative guard ("-foo") is never pushed if the :hg:`qselect` command
2624 2625 has activated it.
2625 2626
2626 2627 With no arguments, print the currently active guards.
2627 2628 With arguments, set guards for the named patch.
2628 2629
2629 2630 .. note::
2630 2631
2631 2632 Specifying negative guards now requires '--'.
2632 2633
2633 2634 To set guards on another patch::
2634 2635
2635 2636 hg qguard other.patch -- +2.6.17 -stable
2636 2637
2637 2638 Returns 0 on success.
2638 2639 '''
2639 2640 def status(idx):
2640 2641 guards = q.seriesguards[idx] or ['unguarded']
2641 2642 if q.series[idx] in applied:
2642 2643 state = 'applied'
2643 2644 elif q.pushable(idx)[0]:
2644 2645 state = 'unapplied'
2645 2646 else:
2646 2647 state = 'guarded'
2647 2648 label = 'qguard.patch qguard.%s qseries.%s' % (state, state)
2648 2649 ui.write('%s: ' % ui.label(q.series[idx], label))
2649 2650
2650 2651 for i, guard in enumerate(guards):
2651 2652 if guard.startswith('+'):
2652 2653 ui.write(guard, label='qguard.positive')
2653 2654 elif guard.startswith('-'):
2654 2655 ui.write(guard, label='qguard.negative')
2655 2656 else:
2656 2657 ui.write(guard, label='qguard.unguarded')
2657 2658 if i != len(guards) - 1:
2658 2659 ui.write(' ')
2659 2660 ui.write('\n')
2660 2661 q = repo.mq
2661 2662 applied = set(p.name for p in q.applied)
2662 2663 patch = None
2663 2664 args = list(args)
2664 2665 if opts.get('list'):
2665 2666 if args or opts.get('none'):
2666 2667 raise util.Abort(_('cannot mix -l/--list with options or '
2667 2668 'arguments'))
2668 2669 for i in xrange(len(q.series)):
2669 2670 status(i)
2670 2671 return
2671 2672 if not args or args[0][0:1] in '-+':
2672 2673 if not q.applied:
2673 2674 raise util.Abort(_('no patches applied'))
2674 2675 patch = q.applied[-1].name
2675 2676 if patch is None and args[0][0:1] not in '-+':
2676 2677 patch = args.pop(0)
2677 2678 if patch is None:
2678 2679 raise util.Abort(_('no patch to work with'))
2679 2680 if args or opts.get('none'):
2680 2681 idx = q.findseries(patch)
2681 2682 if idx is None:
2682 2683 raise util.Abort(_('no patch named %s') % patch)
2683 2684 q.setguards(idx, args)
2684 2685 q.savedirty()
2685 2686 else:
2686 2687 status(q.series.index(q.lookup(patch)))
2687 2688
2688 2689 @command("qheader", [], _('hg qheader [PATCH]'))
2689 2690 def header(ui, repo, patch=None):
2690 2691 """print the header of the topmost or specified patch
2691 2692
2692 2693 Returns 0 on success."""
2693 2694 q = repo.mq
2694 2695
2695 2696 if patch:
2696 2697 patch = q.lookup(patch)
2697 2698 else:
2698 2699 if not q.applied:
2699 2700 ui.write(_('no patches applied\n'))
2700 2701 return 1
2701 2702 patch = q.lookup('qtip')
2702 2703 ph = patchheader(q.join(patch), q.plainmode)
2703 2704
2704 2705 ui.write('\n'.join(ph.message) + '\n')
2705 2706
2706 2707 def lastsavename(path):
2707 2708 (directory, base) = os.path.split(path)
2708 2709 names = os.listdir(directory)
2709 2710 namere = re.compile("%s.([0-9]+)" % base)
2710 2711 maxindex = None
2711 2712 maxname = None
2712 2713 for f in names:
2713 2714 m = namere.match(f)
2714 2715 if m:
2715 2716 index = int(m.group(1))
2716 2717 if maxindex is None or index > maxindex:
2717 2718 maxindex = index
2718 2719 maxname = f
2719 2720 if maxname:
2720 2721 return (os.path.join(directory, maxname), maxindex)
2721 2722 return (None, None)
2722 2723
2723 2724 def savename(path):
2724 2725 (last, index) = lastsavename(path)
2725 2726 if last is None:
2726 2727 index = 0
2727 2728 newpath = path + ".%d" % (index + 1)
2728 2729 return newpath
2729 2730
2730 2731 @command("^qpush",
2731 2732 [('', 'keep-changes', None,
2732 2733 _('tolerate non-conflicting local changes')),
2733 2734 ('f', 'force', None, _('apply on top of local changes')),
2734 2735 ('e', 'exact', None,
2735 2736 _('apply the target patch to its recorded parent')),
2736 2737 ('l', 'list', None, _('list patch name in commit text')),
2737 2738 ('a', 'all', None, _('apply all patches')),
2738 2739 ('m', 'merge', None, _('merge from another queue (DEPRECATED)')),
2739 2740 ('n', 'name', '',
2740 2741 _('merge queue name (DEPRECATED)'), _('NAME')),
2741 2742 ('', 'move', None,
2742 2743 _('reorder patch series and apply only the patch')),
2743 2744 ('', 'no-backup', None, _('do not save backup copies of files'))],
2744 2745 _('hg qpush [-f] [-l] [-a] [--move] [PATCH | INDEX]'))
2745 2746 def push(ui, repo, patch=None, **opts):
2746 2747 """push the next patch onto the stack
2747 2748
2748 2749 By default, abort if the working directory contains uncommitted
2749 2750 changes. With --keep-changes, abort only if the uncommitted files
2750 2751 overlap with patched files. With -f/--force, backup and patch over
2751 2752 uncommitted changes.
2752 2753
2753 2754 Return 0 on success.
2754 2755 """
2755 2756 q = repo.mq
2756 2757 mergeq = None
2757 2758
2758 2759 opts = fixkeepchangesopts(ui, opts)
2759 2760 if opts.get('merge'):
2760 2761 if opts.get('name'):
2761 2762 newpath = repo.join(opts.get('name'))
2762 2763 else:
2763 2764 newpath, i = lastsavename(q.path)
2764 2765 if not newpath:
2765 2766 ui.warn(_("no saved queues found, please use -n\n"))
2766 2767 return 1
2767 2768 mergeq = queue(ui, repo.baseui, repo.path, newpath)
2768 2769 ui.warn(_("merging with queue at: %s\n") % mergeq.path)
2769 2770 ret = q.push(repo, patch, force=opts.get('force'), list=opts.get('list'),
2770 2771 mergeq=mergeq, all=opts.get('all'), move=opts.get('move'),
2771 2772 exact=opts.get('exact'), nobackup=opts.get('no_backup'),
2772 2773 keepchanges=opts.get('keep_changes'))
2773 2774 return ret
2774 2775
2775 2776 @command("^qpop",
2776 2777 [('a', 'all', None, _('pop all patches')),
2777 2778 ('n', 'name', '',
2778 2779 _('queue name to pop (DEPRECATED)'), _('NAME')),
2779 2780 ('', 'keep-changes', None,
2780 2781 _('tolerate non-conflicting local changes')),
2781 2782 ('f', 'force', None, _('forget any local changes to patched files')),
2782 2783 ('', 'no-backup', None, _('do not save backup copies of files'))],
2783 2784 _('hg qpop [-a] [-f] [PATCH | INDEX]'))
2784 2785 def pop(ui, repo, patch=None, **opts):
2785 2786 """pop the current patch off the stack
2786 2787
2787 2788 Without argument, pops off the top of the patch stack. If given a
2788 2789 patch name, keeps popping off patches until the named patch is at
2789 2790 the top of the stack.
2790 2791
2791 2792 By default, abort if the working directory contains uncommitted
2792 2793 changes. With --keep-changes, abort only if the uncommitted files
2793 2794 overlap with patched files. With -f/--force, backup and discard
2794 2795 changes made to such files.
2795 2796
2796 2797 Return 0 on success.
2797 2798 """
2798 2799 opts = fixkeepchangesopts(ui, opts)
2799 2800 localupdate = True
2800 2801 if opts.get('name'):
2801 2802 q = queue(ui, repo.baseui, repo.path, repo.join(opts.get('name')))
2802 2803 ui.warn(_('using patch queue: %s\n') % q.path)
2803 2804 localupdate = False
2804 2805 else:
2805 2806 q = repo.mq
2806 2807 ret = q.pop(repo, patch, force=opts.get('force'), update=localupdate,
2807 2808 all=opts.get('all'), nobackup=opts.get('no_backup'),
2808 2809 keepchanges=opts.get('keep_changes'))
2809 2810 q.savedirty()
2810 2811 return ret
2811 2812
2812 2813 @command("qrename|qmv", [], _('hg qrename PATCH1 [PATCH2]'))
2813 2814 def rename(ui, repo, patch, name=None, **opts):
2814 2815 """rename a patch
2815 2816
2816 2817 With one argument, renames the current patch to PATCH1.
2817 2818 With two arguments, renames PATCH1 to PATCH2.
2818 2819
2819 2820 Returns 0 on success."""
2820 2821 q = repo.mq
2821 2822 if not name:
2822 2823 name = patch
2823 2824 patch = None
2824 2825
2825 2826 if patch:
2826 2827 patch = q.lookup(patch)
2827 2828 else:
2828 2829 if not q.applied:
2829 2830 ui.write(_('no patches applied\n'))
2830 2831 return
2831 2832 patch = q.lookup('qtip')
2832 2833 absdest = q.join(name)
2833 2834 if os.path.isdir(absdest):
2834 2835 name = normname(os.path.join(name, os.path.basename(patch)))
2835 2836 absdest = q.join(name)
2836 2837 q.checkpatchname(name)
2837 2838
2838 2839 ui.note(_('renaming %s to %s\n') % (patch, name))
2839 2840 i = q.findseries(patch)
2840 2841 guards = q.guard_re.findall(q.fullseries[i])
2841 2842 q.fullseries[i] = name + ''.join([' #' + g for g in guards])
2842 2843 q.parseseries()
2843 2844 q.seriesdirty = True
2844 2845
2845 2846 info = q.isapplied(patch)
2846 2847 if info:
2847 2848 q.applied[info[0]] = statusentry(info[1], name)
2848 2849 q.applieddirty = True
2849 2850
2850 2851 destdir = os.path.dirname(absdest)
2851 2852 if not os.path.isdir(destdir):
2852 2853 os.makedirs(destdir)
2853 2854 util.rename(q.join(patch), absdest)
2854 2855 r = q.qrepo()
2855 2856 if r and patch in r.dirstate:
2856 2857 wctx = r[None]
2857 2858 wlock = r.wlock()
2858 2859 try:
2859 2860 if r.dirstate[patch] == 'a':
2860 2861 r.dirstate.drop(patch)
2861 2862 r.dirstate.add(name)
2862 2863 else:
2863 2864 wctx.copy(patch, name)
2864 2865 wctx.forget([patch])
2865 2866 finally:
2866 2867 wlock.release()
2867 2868
2868 2869 q.savedirty()
2869 2870
2870 2871 @command("qrestore",
2871 2872 [('d', 'delete', None, _('delete save entry')),
2872 2873 ('u', 'update', None, _('update queue working directory'))],
2873 2874 _('hg qrestore [-d] [-u] REV'))
2874 2875 def restore(ui, repo, rev, **opts):
2875 2876 """restore the queue state saved by a revision (DEPRECATED)
2876 2877
2877 2878 This command is deprecated, use :hg:`rebase` instead."""
2878 2879 rev = repo.lookup(rev)
2879 2880 q = repo.mq
2880 2881 q.restore(repo, rev, delete=opts.get('delete'),
2881 2882 qupdate=opts.get('update'))
2882 2883 q.savedirty()
2883 2884 return 0
2884 2885
2885 2886 @command("qsave",
2886 2887 [('c', 'copy', None, _('copy patch directory')),
2887 2888 ('n', 'name', '',
2888 2889 _('copy directory name'), _('NAME')),
2889 2890 ('e', 'empty', None, _('clear queue status file')),
2890 2891 ('f', 'force', None, _('force copy'))] + commands.commitopts,
2891 2892 _('hg qsave [-m TEXT] [-l FILE] [-c] [-n NAME] [-e] [-f]'))
2892 2893 def save(ui, repo, **opts):
2893 2894 """save current queue state (DEPRECATED)
2894 2895
2895 2896 This command is deprecated, use :hg:`rebase` instead."""
2896 2897 q = repo.mq
2897 2898 message = cmdutil.logmessage(ui, opts)
2898 2899 ret = q.save(repo, msg=message)
2899 2900 if ret:
2900 2901 return ret
2901 2902 q.savedirty() # save to .hg/patches before copying
2902 2903 if opts.get('copy'):
2903 2904 path = q.path
2904 2905 if opts.get('name'):
2905 2906 newpath = os.path.join(q.basepath, opts.get('name'))
2906 2907 if os.path.exists(newpath):
2907 2908 if not os.path.isdir(newpath):
2908 2909 raise util.Abort(_('destination %s exists and is not '
2909 2910 'a directory') % newpath)
2910 2911 if not opts.get('force'):
2911 2912 raise util.Abort(_('destination %s exists, '
2912 2913 'use -f to force') % newpath)
2913 2914 else:
2914 2915 newpath = savename(path)
2915 2916 ui.warn(_("copy %s to %s\n") % (path, newpath))
2916 2917 util.copyfiles(path, newpath)
2917 2918 if opts.get('empty'):
2918 2919 del q.applied[:]
2919 2920 q.applieddirty = True
2920 2921 q.savedirty()
2921 2922 return 0
2922 2923
2923 2924
2924 2925 @command("qselect",
2925 2926 [('n', 'none', None, _('disable all guards')),
2926 2927 ('s', 'series', None, _('list all guards in series file')),
2927 2928 ('', 'pop', None, _('pop to before first guarded applied patch')),
2928 2929 ('', 'reapply', None, _('pop, then reapply patches'))],
2929 2930 _('hg qselect [OPTION]... [GUARD]...'))
2930 2931 def select(ui, repo, *args, **opts):
2931 2932 '''set or print guarded patches to push
2932 2933
2933 2934 Use the :hg:`qguard` command to set or print guards on patch, then use
2934 2935 qselect to tell mq which guards to use. A patch will be pushed if
2935 2936 it has no guards or any positive guards match the currently
2936 2937 selected guard, but will not be pushed if any negative guards
2937 2938 match the current guard. For example::
2938 2939
2939 2940 qguard foo.patch -- -stable (negative guard)
2940 2941 qguard bar.patch +stable (positive guard)
2941 2942 qselect stable
2942 2943
2943 2944 This activates the "stable" guard. mq will skip foo.patch (because
2944 2945 it has a negative match) but push bar.patch (because it has a
2945 2946 positive match).
2946 2947
2947 2948 With no arguments, prints the currently active guards.
2948 2949 With one argument, sets the active guard.
2949 2950
2950 2951 Use -n/--none to deactivate guards (no other arguments needed).
2951 2952 When no guards are active, patches with positive guards are
2952 2953 skipped and patches with negative guards are pushed.
2953 2954
2954 2955 qselect can change the guards on applied patches. It does not pop
2955 2956 guarded patches by default. Use --pop to pop back to the last
2956 2957 applied patch that is not guarded. Use --reapply (which implies
2957 2958 --pop) to push back to the current patch afterwards, but skip
2958 2959 guarded patches.
2959 2960
2960 2961 Use -s/--series to print a list of all guards in the series file
2961 2962 (no other arguments needed). Use -v for more information.
2962 2963
2963 2964 Returns 0 on success.'''
2964 2965
2965 2966 q = repo.mq
2966 2967 guards = q.active()
2967 2968 if args or opts.get('none'):
2968 2969 old_unapplied = q.unapplied(repo)
2969 2970 old_guarded = [i for i in xrange(len(q.applied)) if
2970 2971 not q.pushable(i)[0]]
2971 2972 q.setactive(args)
2972 2973 q.savedirty()
2973 2974 if not args:
2974 2975 ui.status(_('guards deactivated\n'))
2975 2976 if not opts.get('pop') and not opts.get('reapply'):
2976 2977 unapplied = q.unapplied(repo)
2977 2978 guarded = [i for i in xrange(len(q.applied))
2978 2979 if not q.pushable(i)[0]]
2979 2980 if len(unapplied) != len(old_unapplied):
2980 2981 ui.status(_('number of unguarded, unapplied patches has '
2981 2982 'changed from %d to %d\n') %
2982 2983 (len(old_unapplied), len(unapplied)))
2983 2984 if len(guarded) != len(old_guarded):
2984 2985 ui.status(_('number of guarded, applied patches has changed '
2985 2986 'from %d to %d\n') %
2986 2987 (len(old_guarded), len(guarded)))
2987 2988 elif opts.get('series'):
2988 2989 guards = {}
2989 2990 noguards = 0
2990 2991 for gs in q.seriesguards:
2991 2992 if not gs:
2992 2993 noguards += 1
2993 2994 for g in gs:
2994 2995 guards.setdefault(g, 0)
2995 2996 guards[g] += 1
2996 2997 if ui.verbose:
2997 2998 guards['NONE'] = noguards
2998 2999 guards = guards.items()
2999 3000 guards.sort(key=lambda x: x[0][1:])
3000 3001 if guards:
3001 3002 ui.note(_('guards in series file:\n'))
3002 3003 for guard, count in guards:
3003 3004 ui.note('%2d ' % count)
3004 3005 ui.write(guard, '\n')
3005 3006 else:
3006 3007 ui.note(_('no guards in series file\n'))
3007 3008 else:
3008 3009 if guards:
3009 3010 ui.note(_('active guards:\n'))
3010 3011 for g in guards:
3011 3012 ui.write(g, '\n')
3012 3013 else:
3013 3014 ui.write(_('no active guards\n'))
3014 3015 reapply = opts.get('reapply') and q.applied and q.appliedname(-1)
3015 3016 popped = False
3016 3017 if opts.get('pop') or opts.get('reapply'):
3017 3018 for i in xrange(len(q.applied)):
3018 3019 pushable, reason = q.pushable(i)
3019 3020 if not pushable:
3020 3021 ui.status(_('popping guarded patches\n'))
3021 3022 popped = True
3022 3023 if i == 0:
3023 3024 q.pop(repo, all=True)
3024 3025 else:
3025 3026 q.pop(repo, str(i - 1))
3026 3027 break
3027 3028 if popped:
3028 3029 try:
3029 3030 if reapply:
3030 3031 ui.status(_('reapplying unguarded patches\n'))
3031 3032 q.push(repo, reapply)
3032 3033 finally:
3033 3034 q.savedirty()
3034 3035
3035 3036 @command("qfinish",
3036 3037 [('a', 'applied', None, _('finish all applied changesets'))],
3037 3038 _('hg qfinish [-a] [REV]...'))
3038 3039 def finish(ui, repo, *revrange, **opts):
3039 3040 """move applied patches into repository history
3040 3041
3041 3042 Finishes the specified revisions (corresponding to applied
3042 3043 patches) by moving them out of mq control into regular repository
3043 3044 history.
3044 3045
3045 3046 Accepts a revision range or the -a/--applied option. If --applied
3046 3047 is specified, all applied mq revisions are removed from mq
3047 3048 control. Otherwise, the given revisions must be at the base of the
3048 3049 stack of applied patches.
3049 3050
3050 3051 This can be especially useful if your changes have been applied to
3051 3052 an upstream repository, or if you are about to push your changes
3052 3053 to upstream.
3053 3054
3054 3055 Returns 0 on success.
3055 3056 """
3056 3057 if not opts.get('applied') and not revrange:
3057 3058 raise util.Abort(_('no revisions specified'))
3058 3059 elif opts.get('applied'):
3059 3060 revrange = ('qbase::qtip',) + revrange
3060 3061
3061 3062 q = repo.mq
3062 3063 if not q.applied:
3063 3064 ui.status(_('no patches applied\n'))
3064 3065 return 0
3065 3066
3066 3067 revs = scmutil.revrange(repo, revrange)
3067 3068 if repo['.'].rev() in revs and repo[None].files():
3068 3069 ui.warn(_('warning: uncommitted changes in the working directory\n'))
3069 3070 # queue.finish may changes phases but leave the responsibility to lock the
3070 3071 # repo to the caller to avoid deadlock with wlock. This command code is
3071 3072 # responsibility for this locking.
3072 3073 lock = repo.lock()
3073 3074 try:
3074 3075 q.finish(repo, revs)
3075 3076 q.savedirty()
3076 3077 finally:
3077 3078 lock.release()
3078 3079 return 0
3079 3080
3080 3081 @command("qqueue",
3081 3082 [('l', 'list', False, _('list all available queues')),
3082 3083 ('', 'active', False, _('print name of active queue')),
3083 3084 ('c', 'create', False, _('create new queue')),
3084 3085 ('', 'rename', False, _('rename active queue')),
3085 3086 ('', 'delete', False, _('delete reference to queue')),
3086 3087 ('', 'purge', False, _('delete queue, and remove patch dir')),
3087 3088 ],
3088 3089 _('[OPTION] [QUEUE]'))
3089 3090 def qqueue(ui, repo, name=None, **opts):
3090 3091 '''manage multiple patch queues
3091 3092
3092 3093 Supports switching between different patch queues, as well as creating
3093 3094 new patch queues and deleting existing ones.
3094 3095
3095 3096 Omitting a queue name or specifying -l/--list will show you the registered
3096 3097 queues - by default the "normal" patches queue is registered. The currently
3097 3098 active queue will be marked with "(active)". Specifying --active will print
3098 3099 only the name of the active queue.
3099 3100
3100 3101 To create a new queue, use -c/--create. The queue is automatically made
3101 3102 active, except in the case where there are applied patches from the
3102 3103 currently active queue in the repository. Then the queue will only be
3103 3104 created and switching will fail.
3104 3105
3105 3106 To delete an existing queue, use --delete. You cannot delete the currently
3106 3107 active queue.
3107 3108
3108 3109 Returns 0 on success.
3109 3110 '''
3110 3111 q = repo.mq
3111 3112 _defaultqueue = 'patches'
3112 3113 _allqueues = 'patches.queues'
3113 3114 _activequeue = 'patches.queue'
3114 3115
3115 3116 def _getcurrent():
3116 3117 cur = os.path.basename(q.path)
3117 3118 if cur.startswith('patches-'):
3118 3119 cur = cur[8:]
3119 3120 return cur
3120 3121
3121 3122 def _noqueues():
3122 3123 try:
3123 3124 fh = repo.opener(_allqueues, 'r')
3124 3125 fh.close()
3125 3126 except IOError:
3126 3127 return True
3127 3128
3128 3129 return False
3129 3130
3130 3131 def _getqueues():
3131 3132 current = _getcurrent()
3132 3133
3133 3134 try:
3134 3135 fh = repo.opener(_allqueues, 'r')
3135 3136 queues = [queue.strip() for queue in fh if queue.strip()]
3136 3137 fh.close()
3137 3138 if current not in queues:
3138 3139 queues.append(current)
3139 3140 except IOError:
3140 3141 queues = [_defaultqueue]
3141 3142
3142 3143 return sorted(queues)
3143 3144
3144 3145 def _setactive(name):
3145 3146 if q.applied:
3146 3147 raise util.Abort(_('new queue created, but cannot make active '
3147 3148 'as patches are applied'))
3148 3149 _setactivenocheck(name)
3149 3150
3150 3151 def _setactivenocheck(name):
3151 3152 fh = repo.opener(_activequeue, 'w')
3152 3153 if name != 'patches':
3153 3154 fh.write(name)
3154 3155 fh.close()
3155 3156
3156 3157 def _addqueue(name):
3157 3158 fh = repo.opener(_allqueues, 'a')
3158 3159 fh.write('%s\n' % (name,))
3159 3160 fh.close()
3160 3161
3161 3162 def _queuedir(name):
3162 3163 if name == 'patches':
3163 3164 return repo.join('patches')
3164 3165 else:
3165 3166 return repo.join('patches-' + name)
3166 3167
3167 3168 def _validname(name):
3168 3169 for n in name:
3169 3170 if n in ':\\/.':
3170 3171 return False
3171 3172 return True
3172 3173
3173 3174 def _delete(name):
3174 3175 if name not in existing:
3175 3176 raise util.Abort(_('cannot delete queue that does not exist'))
3176 3177
3177 3178 current = _getcurrent()
3178 3179
3179 3180 if name == current:
3180 3181 raise util.Abort(_('cannot delete currently active queue'))
3181 3182
3182 3183 fh = repo.opener('patches.queues.new', 'w')
3183 3184 for queue in existing:
3184 3185 if queue == name:
3185 3186 continue
3186 3187 fh.write('%s\n' % (queue,))
3187 3188 fh.close()
3188 3189 util.rename(repo.join('patches.queues.new'), repo.join(_allqueues))
3189 3190
3190 3191 if not name or opts.get('list') or opts.get('active'):
3191 3192 current = _getcurrent()
3192 3193 if opts.get('active'):
3193 3194 ui.write('%s\n' % (current,))
3194 3195 return
3195 3196 for queue in _getqueues():
3196 3197 ui.write('%s' % (queue,))
3197 3198 if queue == current and not ui.quiet:
3198 3199 ui.write(_(' (active)\n'))
3199 3200 else:
3200 3201 ui.write('\n')
3201 3202 return
3202 3203
3203 3204 if not _validname(name):
3204 3205 raise util.Abort(
3205 3206 _('invalid queue name, may not contain the characters ":\\/."'))
3206 3207
3207 3208 existing = _getqueues()
3208 3209
3209 3210 if opts.get('create'):
3210 3211 if name in existing:
3211 3212 raise util.Abort(_('queue "%s" already exists') % name)
3212 3213 if _noqueues():
3213 3214 _addqueue(_defaultqueue)
3214 3215 _addqueue(name)
3215 3216 _setactive(name)
3216 3217 elif opts.get('rename'):
3217 3218 current = _getcurrent()
3218 3219 if name == current:
3219 3220 raise util.Abort(_('can\'t rename "%s" to its current name') % name)
3220 3221 if name in existing:
3221 3222 raise util.Abort(_('queue "%s" already exists') % name)
3222 3223
3223 3224 olddir = _queuedir(current)
3224 3225 newdir = _queuedir(name)
3225 3226
3226 3227 if os.path.exists(newdir):
3227 3228 raise util.Abort(_('non-queue directory "%s" already exists') %
3228 3229 newdir)
3229 3230
3230 3231 fh = repo.opener('patches.queues.new', 'w')
3231 3232 for queue in existing:
3232 3233 if queue == current:
3233 3234 fh.write('%s\n' % (name,))
3234 3235 if os.path.exists(olddir):
3235 3236 util.rename(olddir, newdir)
3236 3237 else:
3237 3238 fh.write('%s\n' % (queue,))
3238 3239 fh.close()
3239 3240 util.rename(repo.join('patches.queues.new'), repo.join(_allqueues))
3240 3241 _setactivenocheck(name)
3241 3242 elif opts.get('delete'):
3242 3243 _delete(name)
3243 3244 elif opts.get('purge'):
3244 3245 if name in existing:
3245 3246 _delete(name)
3246 3247 qdir = _queuedir(name)
3247 3248 if os.path.exists(qdir):
3248 3249 shutil.rmtree(qdir)
3249 3250 else:
3250 3251 if name not in existing:
3251 3252 raise util.Abort(_('use --create to create a new queue'))
3252 3253 _setactive(name)
3253 3254
3254 3255 def mqphasedefaults(repo, roots):
3255 3256 """callback used to set mq changeset as secret when no phase data exists"""
3256 3257 if repo.mq.applied:
3257 3258 if repo.ui.configbool('mq', 'secret', False):
3258 3259 mqphase = phases.secret
3259 3260 else:
3260 3261 mqphase = phases.draft
3261 3262 qbase = repo[repo.mq.applied[0].node]
3262 3263 roots[mqphase].add(qbase.node())
3263 3264 return roots
3264 3265
3265 3266 def reposetup(ui, repo):
3266 3267 class mqrepo(repo.__class__):
3267 3268 @localrepo.unfilteredpropertycache
3268 3269 def mq(self):
3269 3270 return queue(self.ui, self.baseui, self.path)
3270 3271
3271 3272 def abortifwdirpatched(self, errmsg, force=False):
3272 3273 if self.mq.applied and self.mq.checkapplied and not force:
3273 3274 parents = self.dirstate.parents()
3274 3275 patches = [s.node for s in self.mq.applied]
3275 3276 if parents[0] in patches or parents[1] in patches:
3276 3277 raise util.Abort(errmsg)
3277 3278
3278 3279 def commit(self, text="", user=None, date=None, match=None,
3279 3280 force=False, editor=False, extra={}):
3280 3281 self.abortifwdirpatched(
3281 3282 _('cannot commit over an applied mq patch'),
3282 3283 force)
3283 3284
3284 3285 return super(mqrepo, self).commit(text, user, date, match, force,
3285 3286 editor, extra)
3286 3287
3287 3288 def checkpush(self, force, revs):
3288 3289 if self.mq.applied and self.mq.checkapplied and not force:
3289 3290 outapplied = [e.node for e in self.mq.applied]
3290 3291 if revs:
3291 3292 # Assume applied patches have no non-patch descendants and
3292 3293 # are not on remote already. Filtering any changeset not
3293 3294 # pushed.
3294 3295 heads = set(revs)
3295 3296 for node in reversed(outapplied):
3296 3297 if node in heads:
3297 3298 break
3298 3299 else:
3299 3300 outapplied.pop()
3300 3301 # looking for pushed and shared changeset
3301 3302 for node in outapplied:
3302 3303 if self[node].phase() < phases.secret:
3303 3304 raise util.Abort(_('source has mq patches applied'))
3304 3305 # no non-secret patches pushed
3305 3306 super(mqrepo, self).checkpush(force, revs)
3306 3307
3307 3308 def _findtags(self):
3308 3309 '''augment tags from base class with patch tags'''
3309 3310 result = super(mqrepo, self)._findtags()
3310 3311
3311 3312 q = self.mq
3312 3313 if not q.applied:
3313 3314 return result
3314 3315
3315 3316 mqtags = [(patch.node, patch.name) for patch in q.applied]
3316 3317
3317 3318 try:
3318 3319 # for now ignore filtering business
3319 3320 self.unfiltered().changelog.rev(mqtags[-1][0])
3320 3321 except error.LookupError:
3321 3322 self.ui.warn(_('mq status file refers to unknown node %s\n')
3322 3323 % short(mqtags[-1][0]))
3323 3324 return result
3324 3325
3325 3326 # do not add fake tags for filtered revisions
3326 3327 included = self.changelog.hasnode
3327 3328 mqtags = [mqt for mqt in mqtags if included(mqt[0])]
3328 3329 if not mqtags:
3329 3330 return result
3330 3331
3331 3332 mqtags.append((mqtags[-1][0], 'qtip'))
3332 3333 mqtags.append((mqtags[0][0], 'qbase'))
3333 3334 mqtags.append((self.changelog.parents(mqtags[0][0])[0], 'qparent'))
3334 3335 tags = result[0]
3335 3336 for patch in mqtags:
3336 3337 if patch[1] in tags:
3337 3338 self.ui.warn(_('tag %s overrides mq patch of the same '
3338 3339 'name\n') % patch[1])
3339 3340 else:
3340 3341 tags[patch[1]] = patch[0]
3341 3342
3342 3343 return result
3343 3344
3344 3345 if repo.local():
3345 3346 repo.__class__ = mqrepo
3346 3347
3347 3348 repo._phasedefaults.append(mqphasedefaults)
3348 3349
3349 3350 def mqimport(orig, ui, repo, *args, **kwargs):
3350 3351 if (util.safehasattr(repo, 'abortifwdirpatched')
3351 3352 and not kwargs.get('no_commit', False)):
3352 3353 repo.abortifwdirpatched(_('cannot import over an applied patch'),
3353 3354 kwargs.get('force'))
3354 3355 return orig(ui, repo, *args, **kwargs)
3355 3356
3356 3357 def mqinit(orig, ui, *args, **kwargs):
3357 3358 mq = kwargs.pop('mq', None)
3358 3359
3359 3360 if not mq:
3360 3361 return orig(ui, *args, **kwargs)
3361 3362
3362 3363 if args:
3363 3364 repopath = args[0]
3364 3365 if not hg.islocal(repopath):
3365 3366 raise util.Abort(_('only a local queue repository '
3366 3367 'may be initialized'))
3367 3368 else:
3368 3369 repopath = cmdutil.findrepo(os.getcwd())
3369 3370 if not repopath:
3370 3371 raise util.Abort(_('there is no Mercurial repository here '
3371 3372 '(.hg not found)'))
3372 3373 repo = hg.repository(ui, repopath)
3373 3374 return qinit(ui, repo, True)
3374 3375
3375 3376 def mqcommand(orig, ui, repo, *args, **kwargs):
3376 3377 """Add --mq option to operate on patch repository instead of main"""
3377 3378
3378 3379 # some commands do not like getting unknown options
3379 3380 mq = kwargs.pop('mq', None)
3380 3381
3381 3382 if not mq:
3382 3383 return orig(ui, repo, *args, **kwargs)
3383 3384
3384 3385 q = repo.mq
3385 3386 r = q.qrepo()
3386 3387 if not r:
3387 3388 raise util.Abort(_('no queue repository'))
3388 3389 return orig(r.ui, r, *args, **kwargs)
3389 3390
3390 3391 def summaryhook(ui, repo):
3391 3392 q = repo.mq
3392 3393 m = []
3393 3394 a, u = len(q.applied), len(q.unapplied(repo))
3394 3395 if a:
3395 3396 m.append(ui.label(_("%d applied"), 'qseries.applied') % a)
3396 3397 if u:
3397 3398 m.append(ui.label(_("%d unapplied"), 'qseries.unapplied') % u)
3398 3399 if m:
3399 3400 # i18n: column positioning for "hg summary"
3400 3401 ui.write(_("mq: %s\n") % ', '.join(m))
3401 3402 else:
3402 3403 # i18n: column positioning for "hg summary"
3403 3404 ui.note(_("mq: (empty queue)\n"))
3404 3405
3405 3406 def revsetmq(repo, subset, x):
3406 3407 """``mq()``
3407 3408 Changesets managed by MQ.
3408 3409 """
3409 3410 revset.getargs(x, 0, 0, _("mq takes no arguments"))
3410 3411 applied = set([repo[r.node].rev() for r in repo.mq.applied])
3411 3412 return [r for r in subset if r in applied]
3412 3413
3413 3414 # tell hggettext to extract docstrings from these functions:
3414 3415 i18nfunctions = [revsetmq]
3415 3416
3416 3417 def extsetup(ui):
3417 3418 # Ensure mq wrappers are called first, regardless of extension load order by
3418 3419 # NOT wrapping in uisetup() and instead deferring to init stage two here.
3419 3420 mqopt = [('', 'mq', None, _("operate on patch repository"))]
3420 3421
3421 3422 extensions.wrapcommand(commands.table, 'import', mqimport)
3422 3423 cmdutil.summaryhooks.add('mq', summaryhook)
3423 3424
3424 3425 entry = extensions.wrapcommand(commands.table, 'init', mqinit)
3425 3426 entry[1].extend(mqopt)
3426 3427
3427 3428 nowrap = set(commands.norepo.split(" "))
3428 3429
3429 3430 def dotable(cmdtable):
3430 3431 for cmd in cmdtable.keys():
3431 3432 cmd = cmdutil.parsealiases(cmd)[0]
3432 3433 if cmd in nowrap:
3433 3434 continue
3434 3435 entry = extensions.wrapcommand(cmdtable, cmd, mqcommand)
3435 3436 entry[1].extend(mqopt)
3436 3437
3437 3438 dotable(commands.table)
3438 3439
3439 3440 for extname, extmodule in extensions.extensions():
3440 3441 if extmodule.__file__ != __file__:
3441 3442 dotable(getattr(extmodule, 'cmdtable', {}))
3442 3443
3443 3444 revset.symbols['mq'] = revsetmq
3444 3445
3445 3446 colortable = {'qguard.negative': 'red',
3446 3447 'qguard.positive': 'yellow',
3447 3448 'qguard.unguarded': 'green',
3448 3449 'qseries.applied': 'blue bold underline',
3449 3450 'qseries.guarded': 'black bold',
3450 3451 'qseries.missing': 'red bold',
3451 3452 'qseries.unapplied': 'black bold'}
3452 3453
3453 3454 commands.inferrepo += " qnew qrefresh qdiff qcommit"
@@ -1,144 +1,180 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
24 24 $ hg qpop p1
25 25 popping p3
26 26 popping p2
27 27 now at: p1
28 28
29 29 $ hg qdiff
30 30 diff -r 07f494440405 a
31 31 --- a/a
32 32 +++ b/a
33 33 @@ -1,1 +1,2 @@
34 34 a
35 35 +a
36 36
37 37 $ hg qfold p2
38 38 $ grep git .hg/patches/p1 && echo 'git patch found!'
39 39 [1]
40 40
41 41 $ hg qser
42 42 p1
43 43 p3
44 44
45 45 $ hg qdiff
46 46 diff -r 07f494440405 a
47 47 --- a/a
48 48 +++ b/a
49 49 @@ -1,1 +1,3 @@
50 50 a
51 51 +a
52 52 +b
53 53
54 54 Fold with local changes:
55 55
56 56 $ echo d >> a
57 57 $ hg qfold p3
58 58 abort: local changes found, refresh first
59 59 [255]
60 60
61 61 $ hg diff -c .
62 62 diff -r 07f494440405 -r ???????????? a (glob)
63 63 --- a/a
64 64 +++ b/a
65 65 @@ -1,1 +1,3 @@
66 66 a
67 67 +a
68 68 +b
69 69
70 70 $ hg revert -a --no-backup
71 71 reverting a
72 72
73 73 Fold git patch into a regular patch, expect git patch:
74 74
75 75 $ echo a >> a
76 76 $ hg qnew -f regular
77 77 $ hg cp a aa
78 78 $ hg qnew --git -f git
79 79
80 80 $ hg qpop
81 81 popping git
82 82 now at: regular
83 83
84 84 $ hg qfold git
85 85
86 86 $ cat .hg/patches/regular
87 87 # HG changeset patch
88 88 # Parent ???????????????????????????????????????? (glob)
89 89
90 90 diff --git a/a b/a
91 91 --- a/a
92 92 +++ b/a
93 93 @@ -1,3 +1,4 @@
94 94 a
95 95 a
96 96 b
97 97 +a
98 98 diff --git a/a b/aa
99 99 copy from a
100 100 copy to aa
101 101 --- a/a
102 102 +++ b/aa
103 103 @@ -1,3 +1,4 @@
104 104 a
105 105 a
106 106 b
107 107 +a
108 108
109 109 $ hg qpop
110 110 popping regular
111 111 now at: p1
112 112
113 113 $ hg qdel regular
114 114
115 115 Fold regular patch into a git patch, expect git patch:
116 116
117 117 $ hg cp a aa
118 118 $ hg qnew --git -f git
119 119 $ echo b >> aa
120 120 $ hg qnew -f regular
121 121
122 122 $ hg qpop
123 123 popping regular
124 124 now at: git
125 125
126 126 $ hg qfold regular
127 127
128 128 $ cat .hg/patches/git
129 129 # HG changeset patch
130 130 # Parent ???????????????????????????????????????? (glob)
131 131
132 132 diff --git a/a b/aa
133 133 copy from a
134 134 copy to aa
135 135 --- a/a
136 136 +++ b/aa
137 137 @@ -1,3 +1,4 @@
138 138 a
139 139 a
140 140 b
141 141 +b
142 142
143 Test saving last-message.txt:
144
145 $ hg qrefresh -m "original message"
146
147 $ cat > $TESTDIR/commitfailure.py <<EOF
148 > from mercurial import util
149 > def reposetup(ui, repo):
150 > class commitfailure(repo.__class__):
151 > def commit(self, *args, **kwargs):
152 > raise util.Abort('emulating unexpected abort')
153 > repo.__class__ = commitfailure
154 > EOF
155
156 $ cat > .hg/hgrc <<EOF
157 > [extensions]
158 > commitfailure = $TESTDIR/commitfailure.py
159 > EOF
160
161 $ cat > $TESTDIR/editor.sh << EOF
162 > echo "==== before editing"
163 > cat \$1
164 > echo "===="
165 > (echo; echo "test saving last-message.txt") >> \$1
166 > EOF
167
168 $ rm -f .hg/last-message.txt
169 $ HGEDITOR="sh $TESTDIR/editor.sh" hg qfold -e p3
170 ==== before editing
171 original message====
172 refresh interrupted while patch was popped! (revert --all, qpush to recover)
173 abort: emulating unexpected abort
174 [255]
175 $ cat .hg/last-message.txt
176 original message
177 test saving last-message.txt
178
143 179 $ cd ..
144 180
General Comments 0
You need to be logged in to leave comments. Login now