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