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