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