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