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