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