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