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