##// END OF EJS Templates
strip: pull core strip logic into its own function
Matt Mackall -
r4700:a2e025fc default
parent child Browse files
Show More
@@ -1,2302 +1,2305 b''
1 1 # queue.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
6 6 # of the GNU General Public License, incorporated herein by reference.
7 7
8 8 '''patch management and development
9 9
10 10 This extension lets you work with a stack of patches in a Mercurial
11 11 repository. It manages two stacks of patches - all known patches, and
12 12 applied patches (subset of known patches).
13 13
14 14 Known patches are represented as patch files in the .hg/patches
15 15 directory. Applied patches are both patch files and changesets.
16 16
17 17 Common tasks (use "hg help command" for more details):
18 18
19 19 prepare repository to work with patches qinit
20 20 create new patch qnew
21 21 import existing patch qimport
22 22
23 23 print patch series qseries
24 24 print applied patches qapplied
25 25 print name of top applied patch qtop
26 26
27 27 add known patch to applied stack qpush
28 28 remove patch from applied stack qpop
29 29 refresh contents of top applied patch qrefresh
30 30 '''
31 31
32 32 from mercurial.i18n import _
33 33 from mercurial import commands, cmdutil, hg, patch, revlog, util, changegroup
34 34 import os, sys, re, errno
35 35
36 36 commands.norepo += " qclone qversion"
37 37
38 38 # Patch names looks like unix-file names.
39 39 # They must be joinable with queue directory and result in the patch path.
40 40 normname = util.normpath
41 41
42 def _strip(ui, repo, rev, backup="all"):
43 def limitheads(chlog, stop):
44 """return the list of all nodes that have no children"""
45 p = {}
46 h = []
47 stoprev = 0
48 if stop in chlog.nodemap:
49 stoprev = chlog.rev(stop)
50
51 for r in xrange(chlog.count() - 1, -1, -1):
52 n = chlog.node(r)
53 if n not in p:
54 h.append(n)
55 if n == stop:
56 break
57 if r < stoprev:
58 break
59 for pn in chlog.parents(n):
60 p[pn] = 1
61 return h
62
63 def bundle(cg):
64 backupdir = repo.join("strip-backup")
65 if not os.path.isdir(backupdir):
66 os.mkdir(backupdir)
67 name = os.path.join(backupdir, "%s" % revlog.short(rev))
68 name = savename(name)
69 ui.warn("saving bundle to %s\n" % name)
70 return changegroup.writebundle(cg, name, "HG10BZ")
71
72 def stripall(revnum):
73 mm = repo.changectx(rev).manifest()
74 seen = {}
75
76 for x in xrange(revnum, repo.changelog.count()):
77 for f in repo.changectx(x).files():
78 if f in seen:
79 continue
80 seen[f] = 1
81 if f in mm:
82 filerev = mm[f]
83 else:
84 filerev = 0
85 seen[f] = filerev
86 # we go in two steps here so the strip loop happens in a
87 # sensible order. When stripping many files, this helps keep
88 # our disk access patterns under control.
89 seen_list = seen.keys()
90 seen_list.sort()
91 for f in seen_list:
92 ff = repo.file(f)
93 filerev = seen[f]
94 if filerev != 0:
95 if filerev in ff.nodemap:
96 filerev = ff.rev(filerev)
97 else:
98 filerev = 0
99 ff.strip(filerev, revnum)
100
101 chlog = repo.changelog
102 # TODO delete the undo files, and handle undo of merge sets
103 pp = chlog.parents(rev)
104 revnum = chlog.rev(rev)
105
106 # save is a list of all the branches we are truncating away
107 # that we actually want to keep. changegroup will be used
108 # to preserve them and add them back after the truncate
109 saveheads = []
110 savebases = {}
111
112 heads = limitheads(chlog, rev)
113 seen = {}
114
115 # search through all the heads, finding those where the revision
116 # we want to strip away is an ancestor. Also look for merges
117 # that might be turned into new heads by the strip.
118 while heads:
119 h = heads.pop()
120 n = h
121 while True:
122 seen[n] = 1
123 pp = chlog.parents(n)
124 if pp[1] != revlog.nullid:
125 for p in pp:
126 if chlog.rev(p) > revnum and p not in seen:
127 heads.append(p)
128 if pp[0] == revlog.nullid:
129 break
130 if chlog.rev(pp[0]) < revnum:
131 break
132 n = pp[0]
133 if n == rev:
134 break
135 r = chlog.reachable(h, rev)
136 if rev not in r:
137 saveheads.append(h)
138 for x in r:
139 if chlog.rev(x) > revnum:
140 savebases[x] = 1
141
142 # create a changegroup for all the branches we need to keep
143 if backup == "all":
144 backupch = repo.changegroupsubset([rev], chlog.heads(), 'strip')
145 bundle(backupch)
146 if saveheads:
147 backupch = repo.changegroupsubset(savebases.keys(), saveheads, 'strip')
148 chgrpfile = bundle(backupch)
149
150 stripall(revnum)
151
152 change = chlog.read(rev)
153 chlog.strip(revnum, revnum)
154 repo.manifest.strip(repo.manifest.rev(change[0]), revnum)
155 if saveheads:
156 ui.status("adding branch\n")
157 commands.unbundle(ui, repo, "file:%s" % chgrpfile, update=False)
158 if backup != "strip":
159 os.unlink(chgrpfile)
160
42 161 class statusentry:
43 162 def __init__(self, rev, name=None):
44 163 if not name:
45 164 fields = rev.split(':', 1)
46 165 if len(fields) == 2:
47 166 self.rev, self.name = fields
48 167 else:
49 168 self.rev, self.name = None, None
50 169 else:
51 170 self.rev, self.name = rev, name
52 171
53 172 def __str__(self):
54 173 return self.rev + ':' + self.name
55 174
56 175 class queue:
57 176 def __init__(self, ui, path, patchdir=None):
58 177 self.basepath = path
59 178 self.path = patchdir or os.path.join(path, "patches")
60 179 self.opener = util.opener(self.path)
61 180 self.ui = ui
62 181 self.applied = []
63 182 self.full_series = []
64 183 self.applied_dirty = 0
65 184 self.series_dirty = 0
66 185 self.series_path = "series"
67 186 self.status_path = "status"
68 187 self.guards_path = "guards"
69 188 self.active_guards = None
70 189 self.guards_dirty = False
71 190 self._diffopts = None
72 191
73 192 if os.path.exists(self.join(self.series_path)):
74 193 self.full_series = self.opener(self.series_path).read().splitlines()
75 194 self.parse_series()
76 195
77 196 if os.path.exists(self.join(self.status_path)):
78 197 lines = self.opener(self.status_path).read().splitlines()
79 198 self.applied = [statusentry(l) for l in lines]
80 199
81 200 def diffopts(self):
82 201 if self._diffopts is None:
83 202 self._diffopts = patch.diffopts(self.ui)
84 203 return self._diffopts
85 204
86 205 def join(self, *p):
87 206 return os.path.join(self.path, *p)
88 207
89 208 def find_series(self, patch):
90 209 pre = re.compile("(\s*)([^#]+)")
91 210 index = 0
92 211 for l in self.full_series:
93 212 m = pre.match(l)
94 213 if m:
95 214 s = m.group(2)
96 215 s = s.rstrip()
97 216 if s == patch:
98 217 return index
99 218 index += 1
100 219 return None
101 220
102 221 guard_re = re.compile(r'\s?#([-+][^-+# \t\r\n\f][^# \t\r\n\f]*)')
103 222
104 223 def parse_series(self):
105 224 self.series = []
106 225 self.series_guards = []
107 226 for l in self.full_series:
108 227 h = l.find('#')
109 228 if h == -1:
110 229 patch = l
111 230 comment = ''
112 231 elif h == 0:
113 232 continue
114 233 else:
115 234 patch = l[:h]
116 235 comment = l[h:]
117 236 patch = patch.strip()
118 237 if patch:
119 238 if patch in self.series:
120 239 raise util.Abort(_('%s appears more than once in %s') %
121 240 (patch, self.join(self.series_path)))
122 241 self.series.append(patch)
123 242 self.series_guards.append(self.guard_re.findall(comment))
124 243
125 244 def check_guard(self, guard):
126 245 bad_chars = '# \t\r\n\f'
127 246 first = guard[0]
128 247 for c in '-+':
129 248 if first == c:
130 249 return (_('guard %r starts with invalid character: %r') %
131 250 (guard, c))
132 251 for c in bad_chars:
133 252 if c in guard:
134 253 return _('invalid character in guard %r: %r') % (guard, c)
135 254
136 255 def set_active(self, guards):
137 256 for guard in guards:
138 257 bad = self.check_guard(guard)
139 258 if bad:
140 259 raise util.Abort(bad)
141 260 guards = dict.fromkeys(guards).keys()
142 261 guards.sort()
143 262 self.ui.debug('active guards: %s\n' % ' '.join(guards))
144 263 self.active_guards = guards
145 264 self.guards_dirty = True
146 265
147 266 def active(self):
148 267 if self.active_guards is None:
149 268 self.active_guards = []
150 269 try:
151 270 guards = self.opener(self.guards_path).read().split()
152 271 except IOError, err:
153 272 if err.errno != errno.ENOENT: raise
154 273 guards = []
155 274 for i, guard in enumerate(guards):
156 275 bad = self.check_guard(guard)
157 276 if bad:
158 277 self.ui.warn('%s:%d: %s\n' %
159 278 (self.join(self.guards_path), i + 1, bad))
160 279 else:
161 280 self.active_guards.append(guard)
162 281 return self.active_guards
163 282
164 283 def set_guards(self, idx, guards):
165 284 for g in guards:
166 285 if len(g) < 2:
167 286 raise util.Abort(_('guard %r too short') % g)
168 287 if g[0] not in '-+':
169 288 raise util.Abort(_('guard %r starts with invalid char') % g)
170 289 bad = self.check_guard(g[1:])
171 290 if bad:
172 291 raise util.Abort(bad)
173 292 drop = self.guard_re.sub('', self.full_series[idx])
174 293 self.full_series[idx] = drop + ''.join([' #' + g for g in guards])
175 294 self.parse_series()
176 295 self.series_dirty = True
177 296
178 297 def pushable(self, idx):
179 298 if isinstance(idx, str):
180 299 idx = self.series.index(idx)
181 300 patchguards = self.series_guards[idx]
182 301 if not patchguards:
183 302 return True, None
184 303 default = False
185 304 guards = self.active()
186 305 exactneg = [g for g in patchguards if g[0] == '-' and g[1:] in guards]
187 306 if exactneg:
188 307 return False, exactneg[0]
189 308 pos = [g for g in patchguards if g[0] == '+']
190 309 exactpos = [g for g in pos if g[1:] in guards]
191 310 if pos:
192 311 if exactpos:
193 312 return True, exactpos[0]
194 313 return False, pos
195 314 return True, ''
196 315
197 316 def explain_pushable(self, idx, all_patches=False):
198 317 write = all_patches and self.ui.write or self.ui.warn
199 318 if all_patches or self.ui.verbose:
200 319 if isinstance(idx, str):
201 320 idx = self.series.index(idx)
202 321 pushable, why = self.pushable(idx)
203 322 if all_patches and pushable:
204 323 if why is None:
205 324 write(_('allowing %s - no guards in effect\n') %
206 325 self.series[idx])
207 326 else:
208 327 if not why:
209 328 write(_('allowing %s - no matching negative guards\n') %
210 329 self.series[idx])
211 330 else:
212 331 write(_('allowing %s - guarded by %r\n') %
213 332 (self.series[idx], why))
214 333 if not pushable:
215 334 if why:
216 335 write(_('skipping %s - guarded by %r\n') %
217 336 (self.series[idx], why))
218 337 else:
219 338 write(_('skipping %s - no matching guards\n') %
220 339 self.series[idx])
221 340
222 341 def save_dirty(self):
223 342 def write_list(items, path):
224 343 fp = self.opener(path, 'w')
225 344 for i in items:
226 345 print >> fp, i
227 346 fp.close()
228 347 if self.applied_dirty: write_list(map(str, self.applied), self.status_path)
229 348 if self.series_dirty: write_list(self.full_series, self.series_path)
230 349 if self.guards_dirty: write_list(self.active_guards, self.guards_path)
231 350
232 351 def readheaders(self, patch):
233 352 def eatdiff(lines):
234 353 while lines:
235 354 l = lines[-1]
236 355 if (l.startswith("diff -") or
237 356 l.startswith("Index:") or
238 357 l.startswith("===========")):
239 358 del lines[-1]
240 359 else:
241 360 break
242 361 def eatempty(lines):
243 362 while lines:
244 363 l = lines[-1]
245 364 if re.match('\s*$', l):
246 365 del lines[-1]
247 366 else:
248 367 break
249 368
250 369 pf = self.join(patch)
251 370 message = []
252 371 comments = []
253 372 user = None
254 373 date = None
255 374 format = None
256 375 subject = None
257 376 diffstart = 0
258 377
259 378 for line in file(pf):
260 379 line = line.rstrip()
261 380 if line.startswith('diff --git'):
262 381 diffstart = 2
263 382 break
264 383 if diffstart:
265 384 if line.startswith('+++ '):
266 385 diffstart = 2
267 386 break
268 387 if line.startswith("--- "):
269 388 diffstart = 1
270 389 continue
271 390 elif format == "hgpatch":
272 391 # parse values when importing the result of an hg export
273 392 if line.startswith("# User "):
274 393 user = line[7:]
275 394 elif line.startswith("# Date "):
276 395 date = line[7:]
277 396 elif not line.startswith("# ") and line:
278 397 message.append(line)
279 398 format = None
280 399 elif line == '# HG changeset patch':
281 400 format = "hgpatch"
282 401 elif (format != "tagdone" and (line.startswith("Subject: ") or
283 402 line.startswith("subject: "))):
284 403 subject = line[9:]
285 404 format = "tag"
286 405 elif (format != "tagdone" and (line.startswith("From: ") or
287 406 line.startswith("from: "))):
288 407 user = line[6:]
289 408 format = "tag"
290 409 elif format == "tag" and line == "":
291 410 # when looking for tags (subject: from: etc) they
292 411 # end once you find a blank line in the source
293 412 format = "tagdone"
294 413 elif message or line:
295 414 message.append(line)
296 415 comments.append(line)
297 416
298 417 eatdiff(message)
299 418 eatdiff(comments)
300 419 eatempty(message)
301 420 eatempty(comments)
302 421
303 422 # make sure message isn't empty
304 423 if format and format.startswith("tag") and subject:
305 424 message.insert(0, "")
306 425 message.insert(0, subject)
307 426 return (message, comments, user, date, diffstart > 1)
308 427
309 428 def removeundo(self, repo):
310 429 undo = repo.sjoin('undo')
311 430 if not os.path.exists(undo):
312 431 return
313 432 try:
314 433 os.unlink(undo)
315 434 except OSError, inst:
316 435 self.ui.warn('error removing undo: %s\n' % str(inst))
317 436
318 437 def printdiff(self, repo, node1, node2=None, files=None,
319 438 fp=None, changes=None, opts={}):
320 439 fns, matchfn, anypats = cmdutil.matchpats(repo, files, opts)
321 440
322 441 patch.diff(repo, node1, node2, fns, match=matchfn,
323 442 fp=fp, changes=changes, opts=self.diffopts())
324 443
325 444 def mergeone(self, repo, mergeq, head, patch, rev, wlock):
326 445 # first try just applying the patch
327 446 (err, n) = self.apply(repo, [ patch ], update_status=False,
328 447 strict=True, merge=rev, wlock=wlock)
329 448
330 449 if err == 0:
331 450 return (err, n)
332 451
333 452 if n is None:
334 453 raise util.Abort(_("apply failed for patch %s") % patch)
335 454
336 455 self.ui.warn("patch didn't work out, merging %s\n" % patch)
337 456
338 457 # apply failed, strip away that rev and merge.
339 458 hg.clean(repo, head, wlock=wlock)
340 459 self.strip(repo, n, update=False, backup='strip', wlock=wlock)
341 460
342 461 ctx = repo.changectx(rev)
343 462 ret = hg.merge(repo, rev, wlock=wlock)
344 463 if ret:
345 464 raise util.Abort(_("update returned %d") % ret)
346 465 n = repo.commit(None, ctx.description(), ctx.user(),
347 466 force=1, wlock=wlock)
348 467 if n == None:
349 468 raise util.Abort(_("repo commit failed"))
350 469 try:
351 470 message, comments, user, date, patchfound = mergeq.readheaders(patch)
352 471 except:
353 472 raise util.Abort(_("unable to read %s") % patch)
354 473
355 474 patchf = self.opener(patch, "w")
356 475 if comments:
357 476 comments = "\n".join(comments) + '\n\n'
358 477 patchf.write(comments)
359 478 self.printdiff(repo, head, n, fp=patchf)
360 479 patchf.close()
361 480 self.removeundo(repo)
362 481 return (0, n)
363 482
364 483 def qparents(self, repo, rev=None):
365 484 if rev is None:
366 485 (p1, p2) = repo.dirstate.parents()
367 486 if p2 == revlog.nullid:
368 487 return p1
369 488 if len(self.applied) == 0:
370 489 return None
371 490 return revlog.bin(self.applied[-1].rev)
372 491 pp = repo.changelog.parents(rev)
373 492 if pp[1] != revlog.nullid:
374 493 arevs = [ x.rev for x in self.applied ]
375 494 p0 = revlog.hex(pp[0])
376 495 p1 = revlog.hex(pp[1])
377 496 if p0 in arevs:
378 497 return pp[0]
379 498 if p1 in arevs:
380 499 return pp[1]
381 500 return pp[0]
382 501
383 502 def mergepatch(self, repo, mergeq, series, wlock):
384 503 if len(self.applied) == 0:
385 504 # each of the patches merged in will have two parents. This
386 505 # can confuse the qrefresh, qdiff, and strip code because it
387 506 # needs to know which parent is actually in the patch queue.
388 507 # so, we insert a merge marker with only one parent. This way
389 508 # the first patch in the queue is never a merge patch
390 509 #
391 510 pname = ".hg.patches.merge.marker"
392 511 n = repo.commit(None, '[mq]: merge marker', user=None, force=1,
393 512 wlock=wlock)
394 513 self.removeundo(repo)
395 514 self.applied.append(statusentry(revlog.hex(n), pname))
396 515 self.applied_dirty = 1
397 516
398 517 head = self.qparents(repo)
399 518
400 519 for patch in series:
401 520 patch = mergeq.lookup(patch, strict=True)
402 521 if not patch:
403 522 self.ui.warn("patch %s does not exist\n" % patch)
404 523 return (1, None)
405 524 pushable, reason = self.pushable(patch)
406 525 if not pushable:
407 526 self.explain_pushable(patch, all_patches=True)
408 527 continue
409 528 info = mergeq.isapplied(patch)
410 529 if not info:
411 530 self.ui.warn("patch %s is not applied\n" % patch)
412 531 return (1, None)
413 532 rev = revlog.bin(info[1])
414 533 (err, head) = self.mergeone(repo, mergeq, head, patch, rev, wlock)
415 534 if head:
416 535 self.applied.append(statusentry(revlog.hex(head), patch))
417 536 self.applied_dirty = 1
418 537 if err:
419 538 return (err, head)
420 539 self.save_dirty()
421 540 return (0, head)
422 541
423 542 def patch(self, repo, patchfile):
424 543 '''Apply patchfile to the working directory.
425 544 patchfile: file name of patch'''
426 545 files = {}
427 546 try:
428 547 fuzz = patch.patch(patchfile, self.ui, strip=1, cwd=repo.root,
429 548 files=files)
430 549 except Exception, inst:
431 550 self.ui.note(str(inst) + '\n')
432 551 if not self.ui.verbose:
433 552 self.ui.warn("patch failed, unable to continue (try -v)\n")
434 553 return (False, files, False)
435 554
436 555 return (True, files, fuzz)
437 556
438 557 def apply(self, repo, series, list=False, update_status=True,
439 558 strict=False, patchdir=None, merge=None, wlock=None,
440 559 all_files={}):
441 560 if not wlock:
442 561 wlock = repo.wlock()
443 562 lock = repo.lock()
444 563 tr = repo.transaction()
445 564 try:
446 565 ret = self._apply(tr, repo, series, list, update_status,
447 566 strict, patchdir, merge, wlock,
448 567 lock=lock, all_files=all_files)
449 568 tr.close()
450 569 self.save_dirty()
451 570 return ret
452 571 except:
453 572 try:
454 573 tr.abort()
455 574 finally:
456 575 repo.invalidate()
457 576 repo.dirstate.invalidate()
458 577 raise
459 578
460 579 def _apply(self, tr, repo, series, list=False, update_status=True,
461 580 strict=False, patchdir=None, merge=None, wlock=None,
462 581 lock=None, all_files={}):
463 582 # TODO unify with commands.py
464 583 if not patchdir:
465 584 patchdir = self.path
466 585 err = 0
467 586 n = None
468 587 for patchname in series:
469 588 pushable, reason = self.pushable(patchname)
470 589 if not pushable:
471 590 self.explain_pushable(patchname, all_patches=True)
472 591 continue
473 592 self.ui.warn("applying %s\n" % patchname)
474 593 pf = os.path.join(patchdir, patchname)
475 594
476 595 try:
477 596 message, comments, user, date, patchfound = self.readheaders(patchname)
478 597 except:
479 598 self.ui.warn("Unable to read %s\n" % patchname)
480 599 err = 1
481 600 break
482 601
483 602 if not message:
484 603 message = "imported patch %s\n" % patchname
485 604 else:
486 605 if list:
487 606 message.append("\nimported patch %s" % patchname)
488 607 message = '\n'.join(message)
489 608
490 609 (patcherr, files, fuzz) = self.patch(repo, pf)
491 610 all_files.update(files)
492 611 patcherr = not patcherr
493 612
494 613 if merge and files:
495 614 # Mark as removed/merged and update dirstate parent info
496 615 removed = []
497 616 merged = []
498 617 for f in files:
499 618 if os.path.exists(repo.dirstate.wjoin(f)):
500 619 merged.append(f)
501 620 else:
502 621 removed.append(f)
503 622 repo.dirstate.update(repo.dirstate.filterfiles(removed), 'r')
504 623 repo.dirstate.update(repo.dirstate.filterfiles(merged), 'm')
505 624 p1, p2 = repo.dirstate.parents()
506 625 repo.dirstate.setparents(p1, merge)
507 626 files = patch.updatedir(self.ui, repo, files, wlock=wlock)
508 627 n = repo.commit(files, message, user, date, force=1, lock=lock,
509 628 wlock=wlock)
510 629
511 630 if n == None:
512 631 raise util.Abort(_("repo commit failed"))
513 632
514 633 if update_status:
515 634 self.applied.append(statusentry(revlog.hex(n), patchname))
516 635
517 636 if patcherr:
518 637 if not patchfound:
519 638 self.ui.warn("patch %s is empty\n" % patchname)
520 639 err = 0
521 640 else:
522 641 self.ui.warn("patch failed, rejects left in working dir\n")
523 642 err = 1
524 643 break
525 644
526 645 if fuzz and strict:
527 646 self.ui.warn("fuzz found when applying patch, stopping\n")
528 647 err = 1
529 648 break
530 649 self.removeundo(repo)
531 650 return (err, n)
532 651
533 652 def delete(self, repo, patches, opts):
534 653 realpatches = []
535 654 for patch in patches:
536 655 patch = self.lookup(patch, strict=True)
537 656 info = self.isapplied(patch)
538 657 if info:
539 658 raise util.Abort(_("cannot delete applied patch %s") % patch)
540 659 if patch not in self.series:
541 660 raise util.Abort(_("patch %s not in series file") % patch)
542 661 realpatches.append(patch)
543 662
544 663 appliedbase = 0
545 664 if opts.get('rev'):
546 665 if not self.applied:
547 666 raise util.Abort(_('no patches applied'))
548 667 revs = cmdutil.revrange(repo, opts['rev'])
549 668 if len(revs) > 1 and revs[0] > revs[1]:
550 669 revs.reverse()
551 670 for rev in revs:
552 671 if appliedbase >= len(self.applied):
553 672 raise util.Abort(_("revision %d is not managed") % rev)
554 673
555 674 base = revlog.bin(self.applied[appliedbase].rev)
556 675 node = repo.changelog.node(rev)
557 676 if node != base:
558 677 raise util.Abort(_("cannot delete revision %d above "
559 678 "applied patches") % rev)
560 679 realpatches.append(self.applied[appliedbase].name)
561 680 appliedbase += 1
562 681
563 682 if not opts.get('keep'):
564 683 r = self.qrepo()
565 684 if r:
566 685 r.remove(realpatches, True)
567 686 else:
568 687 for p in realpatches:
569 688 os.unlink(self.join(p))
570 689
571 690 if appliedbase:
572 691 del self.applied[:appliedbase]
573 692 self.applied_dirty = 1
574 693 indices = [self.find_series(p) for p in realpatches]
575 694 indices.sort()
576 695 for i in indices[-1::-1]:
577 696 del self.full_series[i]
578 697 self.parse_series()
579 698 self.series_dirty = 1
580 699
581 700 def check_toppatch(self, repo):
582 701 if len(self.applied) > 0:
583 702 top = revlog.bin(self.applied[-1].rev)
584 703 pp = repo.dirstate.parents()
585 704 if top not in pp:
586 705 raise util.Abort(_("queue top not at same revision as working directory"))
587 706 return top
588 707 return None
589 708 def check_localchanges(self, repo, force=False, refresh=True):
590 709 m, a, r, d = repo.status()[:4]
591 710 if m or a or r or d:
592 711 if not force:
593 712 if refresh:
594 713 raise util.Abort(_("local changes found, refresh first"))
595 714 else:
596 715 raise util.Abort(_("local changes found"))
597 716 return m, a, r, d
598 717 def new(self, repo, patch, msg=None, force=None):
599 718 if os.path.exists(self.join(patch)):
600 719 raise util.Abort(_('patch "%s" already exists') % patch)
601 720 m, a, r, d = self.check_localchanges(repo, force)
602 721 commitfiles = m + a + r
603 722 self.check_toppatch(repo)
604 723 wlock = repo.wlock()
605 724 insert = self.full_series_end()
606 725 if msg:
607 726 n = repo.commit(commitfiles, "[mq]: %s" % msg, force=True,
608 727 wlock=wlock)
609 728 else:
610 729 n = repo.commit(commitfiles,
611 730 "New patch: %s" % patch, force=True, wlock=wlock)
612 731 if n == None:
613 732 raise util.Abort(_("repo commit failed"))
614 733 self.full_series[insert:insert] = [patch]
615 734 self.applied.append(statusentry(revlog.hex(n), patch))
616 735 self.parse_series()
617 736 self.series_dirty = 1
618 737 self.applied_dirty = 1
619 738 p = self.opener(patch, "w")
620 739 if msg:
621 740 msg = msg + "\n"
622 741 p.write(msg)
623 742 p.close()
624 743 wlock = None
625 744 r = self.qrepo()
626 745 if r: r.add([patch])
627 746 if commitfiles:
628 747 self.refresh(repo, short=True)
629 748 self.removeundo(repo)
630 749
631 750 def strip(self, repo, rev, update=True, backup="all", wlock=None):
632 def limitheads(chlog, stop):
633 """return the list of all nodes that have no children"""
634 p = {}
635 h = []
636 stoprev = 0
637 if stop in chlog.nodemap:
638 stoprev = chlog.rev(stop)
639
640 for r in xrange(chlog.count() - 1, -1, -1):
641 n = chlog.node(r)
642 if n not in p:
643 h.append(n)
644 if n == stop:
645 break
646 if r < stoprev:
647 break
648 for pn in chlog.parents(n):
649 p[pn] = 1
650 return h
651
652 def bundle(cg):
653 backupdir = repo.join("strip-backup")
654 if not os.path.isdir(backupdir):
655 os.mkdir(backupdir)
656 name = os.path.join(backupdir, "%s" % revlog.short(rev))
657 name = savename(name)
658 self.ui.warn("saving bundle to %s\n" % name)
659 return changegroup.writebundle(cg, name, "HG10BZ")
660
661 def stripall(revnum):
662 mm = repo.changectx(rev).manifest()
663 seen = {}
664
665 for x in xrange(revnum, repo.changelog.count()):
666 for f in repo.changectx(x).files():
667 if f in seen:
668 continue
669 seen[f] = 1
670 if f in mm:
671 filerev = mm[f]
672 else:
673 filerev = 0
674 seen[f] = filerev
675 # we go in two steps here so the strip loop happens in a
676 # sensible order. When stripping many files, this helps keep
677 # our disk access patterns under control.
678 seen_list = seen.keys()
679 seen_list.sort()
680 for f in seen_list:
681 ff = repo.file(f)
682 filerev = seen[f]
683 if filerev != 0:
684 if filerev in ff.nodemap:
685 filerev = ff.rev(filerev)
686 else:
687 filerev = 0
688 ff.strip(filerev, revnum)
689
690 751 if not wlock:
691 752 wlock = repo.wlock()
692 753 lock = repo.lock()
693 chlog = repo.changelog
694 # TODO delete the undo files, and handle undo of merge sets
695 pp = chlog.parents(rev)
696 revnum = chlog.rev(rev)
697 754
698 755 if update:
699 756 self.check_localchanges(repo, refresh=False)
700 757 urev = self.qparents(repo, rev)
701 758 hg.clean(repo, urev, wlock=wlock)
702 759 repo.dirstate.write()
703 760
704 # save is a list of all the branches we are truncating away
705 # that we actually want to keep. changegroup will be used
706 # to preserve them and add them back after the truncate
707 saveheads = []
708 savebases = {}
709
710 heads = limitheads(chlog, rev)
711 seen = {}
712
713 # search through all the heads, finding those where the revision
714 # we want to strip away is an ancestor. Also look for merges
715 # that might be turned into new heads by the strip.
716 while heads:
717 h = heads.pop()
718 n = h
719 while True:
720 seen[n] = 1
721 pp = chlog.parents(n)
722 if pp[1] != revlog.nullid:
723 for p in pp:
724 if chlog.rev(p) > revnum and p not in seen:
725 heads.append(p)
726 if pp[0] == revlog.nullid:
727 break
728 if chlog.rev(pp[0]) < revnum:
729 break
730 n = pp[0]
731 if n == rev:
732 break
733 r = chlog.reachable(h, rev)
734 if rev not in r:
735 saveheads.append(h)
736 for x in r:
737 if chlog.rev(x) > revnum:
738 savebases[x] = 1
739
740 # create a changegroup for all the branches we need to keep
741 if backup == "all":
742 backupch = repo.changegroupsubset([rev], chlog.heads(), 'strip')
743 bundle(backupch)
744 if saveheads:
745 backupch = repo.changegroupsubset(savebases.keys(), saveheads, 'strip')
746 chgrpfile = bundle(backupch)
747
748 stripall(revnum)
749
750 change = chlog.read(rev)
751 chlog.strip(revnum, revnum)
752 repo.manifest.strip(repo.manifest.rev(change[0]), revnum)
753 761 self.removeundo(repo)
754 if saveheads:
755 self.ui.status("adding branch\n")
756 commands.unbundle(self.ui, repo, "file:%s" % chgrpfile,
757 update=False)
758 if backup != "strip":
759 os.unlink(chgrpfile)
762 _strip(self.ui, repo, rev, backup)
760 763
761 764 def isapplied(self, patch):
762 765 """returns (index, rev, patch)"""
763 766 for i in xrange(len(self.applied)):
764 767 a = self.applied[i]
765 768 if a.name == patch:
766 769 return (i, a.rev, a.name)
767 770 return None
768 771
769 772 # if the exact patch name does not exist, we try a few
770 773 # variations. If strict is passed, we try only #1
771 774 #
772 775 # 1) a number to indicate an offset in the series file
773 776 # 2) a unique substring of the patch name was given
774 777 # 3) patchname[-+]num to indicate an offset in the series file
775 778 def lookup(self, patch, strict=False):
776 779 patch = patch and str(patch)
777 780
778 781 def partial_name(s):
779 782 if s in self.series:
780 783 return s
781 784 matches = [x for x in self.series if s in x]
782 785 if len(matches) > 1:
783 786 self.ui.warn(_('patch name "%s" is ambiguous:\n') % s)
784 787 for m in matches:
785 788 self.ui.warn(' %s\n' % m)
786 789 return None
787 790 if matches:
788 791 return matches[0]
789 792 if len(self.series) > 0 and len(self.applied) > 0:
790 793 if s == 'qtip':
791 794 return self.series[self.series_end(True)-1]
792 795 if s == 'qbase':
793 796 return self.series[0]
794 797 return None
795 798 if patch == None:
796 799 return None
797 800
798 801 # we don't want to return a partial match until we make
799 802 # sure the file name passed in does not exist (checked below)
800 803 res = partial_name(patch)
801 804 if res and res == patch:
802 805 return res
803 806
804 807 if not os.path.isfile(self.join(patch)):
805 808 try:
806 809 sno = int(patch)
807 810 except(ValueError, OverflowError):
808 811 pass
809 812 else:
810 813 if sno < len(self.series):
811 814 return self.series[sno]
812 815 if not strict:
813 816 # return any partial match made above
814 817 if res:
815 818 return res
816 819 minus = patch.rfind('-')
817 820 if minus >= 0:
818 821 res = partial_name(patch[:minus])
819 822 if res:
820 823 i = self.series.index(res)
821 824 try:
822 825 off = int(patch[minus+1:] or 1)
823 826 except(ValueError, OverflowError):
824 827 pass
825 828 else:
826 829 if i - off >= 0:
827 830 return self.series[i - off]
828 831 plus = patch.rfind('+')
829 832 if plus >= 0:
830 833 res = partial_name(patch[:plus])
831 834 if res:
832 835 i = self.series.index(res)
833 836 try:
834 837 off = int(patch[plus+1:] or 1)
835 838 except(ValueError, OverflowError):
836 839 pass
837 840 else:
838 841 if i + off < len(self.series):
839 842 return self.series[i + off]
840 843 raise util.Abort(_("patch %s not in series") % patch)
841 844
842 845 def push(self, repo, patch=None, force=False, list=False,
843 846 mergeq=None, wlock=None):
844 847 if not wlock:
845 848 wlock = repo.wlock()
846 849 patch = self.lookup(patch)
847 850 # Suppose our series file is: A B C and the current 'top' patch is B.
848 851 # qpush C should be performed (moving forward)
849 852 # qpush B is a NOP (no change)
850 853 # qpush A is an error (can't go backwards with qpush)
851 854 if patch:
852 855 info = self.isapplied(patch)
853 856 if info:
854 857 if info[0] < len(self.applied) - 1:
855 858 raise util.Abort(_("cannot push to a previous patch: %s") %
856 859 patch)
857 860 if info[0] < len(self.series) - 1:
858 861 self.ui.warn(_('qpush: %s is already at the top\n') % patch)
859 862 else:
860 863 self.ui.warn(_('all patches are currently applied\n'))
861 864 return
862 865
863 866 # Following the above example, starting at 'top' of B:
864 867 # qpush should be performed (pushes C), but a subsequent qpush without
865 868 # an argument is an error (nothing to apply). This allows a loop
866 869 # of "...while hg qpush..." to work as it detects an error when done
867 870 if self.series_end() == len(self.series):
868 871 self.ui.warn(_('patch series already fully applied\n'))
869 872 return 1
870 873 if not force:
871 874 self.check_localchanges(repo)
872 875
873 876 self.applied_dirty = 1;
874 877 start = self.series_end()
875 878 if start > 0:
876 879 self.check_toppatch(repo)
877 880 if not patch:
878 881 patch = self.series[start]
879 882 end = start + 1
880 883 else:
881 884 end = self.series.index(patch, start) + 1
882 885 s = self.series[start:end]
883 886 all_files = {}
884 887 try:
885 888 if mergeq:
886 889 ret = self.mergepatch(repo, mergeq, s, wlock)
887 890 else:
888 891 ret = self.apply(repo, s, list, wlock=wlock,
889 892 all_files=all_files)
890 893 except:
891 894 self.ui.warn(_('cleaning up working directory...'))
892 895 node = repo.dirstate.parents()[0]
893 896 hg.revert(repo, node, None, wlock)
894 897 unknown = repo.status(wlock=wlock)[4]
895 898 # only remove unknown files that we know we touched or
896 899 # created while patching
897 900 for f in unknown:
898 901 if f in all_files:
899 902 util.unlink(repo.wjoin(f))
900 903 self.ui.warn(_('done\n'))
901 904 raise
902 905 top = self.applied[-1].name
903 906 if ret[0]:
904 907 self.ui.write("Errors during apply, please fix and refresh %s\n" %
905 908 top)
906 909 else:
907 910 self.ui.write("Now at: %s\n" % top)
908 911 return ret[0]
909 912
910 913 def pop(self, repo, patch=None, force=False, update=True, all=False,
911 914 wlock=None):
912 915 def getfile(f, rev):
913 916 t = repo.file(f).read(rev)
914 917 repo.wfile(f, "w").write(t)
915 918
916 919 if not wlock:
917 920 wlock = repo.wlock()
918 921 if patch:
919 922 # index, rev, patch
920 923 info = self.isapplied(patch)
921 924 if not info:
922 925 patch = self.lookup(patch)
923 926 info = self.isapplied(patch)
924 927 if not info:
925 928 raise util.Abort(_("patch %s is not applied") % patch)
926 929
927 930 if len(self.applied) == 0:
928 931 # Allow qpop -a to work repeatedly,
929 932 # but not qpop without an argument
930 933 self.ui.warn(_("no patches applied\n"))
931 934 return not all
932 935
933 936 if not update:
934 937 parents = repo.dirstate.parents()
935 938 rr = [ revlog.bin(x.rev) for x in self.applied ]
936 939 for p in parents:
937 940 if p in rr:
938 941 self.ui.warn("qpop: forcing dirstate update\n")
939 942 update = True
940 943
941 944 if not force and update:
942 945 self.check_localchanges(repo)
943 946
944 947 self.applied_dirty = 1;
945 948 end = len(self.applied)
946 949 if not patch:
947 950 if all:
948 951 popi = 0
949 952 else:
950 953 popi = len(self.applied) - 1
951 954 else:
952 955 popi = info[0] + 1
953 956 if popi >= end:
954 957 self.ui.warn("qpop: %s is already at the top\n" % patch)
955 958 return
956 959 info = [ popi ] + [self.applied[popi].rev, self.applied[popi].name]
957 960
958 961 start = info[0]
959 962 rev = revlog.bin(info[1])
960 963
961 964 # we know there are no local changes, so we can make a simplified
962 965 # form of hg.update.
963 966 if update:
964 967 top = self.check_toppatch(repo)
965 968 qp = self.qparents(repo, rev)
966 969 changes = repo.changelog.read(qp)
967 970 mmap = repo.manifest.read(changes[0])
968 971 m, a, r, d, u = repo.status(qp, top)[:5]
969 972 if d:
970 973 raise util.Abort("deletions found between repo revs")
971 974 for f in m:
972 975 getfile(f, mmap[f])
973 976 for f in r:
974 977 getfile(f, mmap[f])
975 978 util.set_exec(repo.wjoin(f), mmap.execf(f))
976 979 repo.dirstate.update(m + r, 'n')
977 980 for f in a:
978 981 try:
979 982 os.unlink(repo.wjoin(f))
980 983 except OSError, e:
981 984 if e.errno != errno.ENOENT:
982 985 raise
983 986 try: os.removedirs(os.path.dirname(repo.wjoin(f)))
984 987 except: pass
985 988 if a:
986 989 repo.dirstate.forget(a)
987 990 repo.dirstate.setparents(qp, revlog.nullid)
988 991 self.strip(repo, rev, update=False, backup='strip', wlock=wlock)
989 992 del self.applied[start:end]
990 993 if len(self.applied):
991 994 self.ui.write("Now at: %s\n" % self.applied[-1].name)
992 995 else:
993 996 self.ui.write("Patch queue now empty\n")
994 997
995 998 def diff(self, repo, pats, opts):
996 999 top = self.check_toppatch(repo)
997 1000 if not top:
998 1001 self.ui.write("No patches applied\n")
999 1002 return
1000 1003 qp = self.qparents(repo, top)
1001 1004 if opts.get('git'):
1002 1005 self.diffopts().git = True
1003 1006 self.printdiff(repo, qp, files=pats, opts=opts)
1004 1007
1005 1008 def refresh(self, repo, pats=None, **opts):
1006 1009 if len(self.applied) == 0:
1007 1010 self.ui.write("No patches applied\n")
1008 1011 return 1
1009 1012 wlock = repo.wlock()
1010 1013 self.check_toppatch(repo)
1011 1014 (top, patchfn) = (self.applied[-1].rev, self.applied[-1].name)
1012 1015 top = revlog.bin(top)
1013 1016 cparents = repo.changelog.parents(top)
1014 1017 patchparent = self.qparents(repo, top)
1015 1018 message, comments, user, date, patchfound = self.readheaders(patchfn)
1016 1019
1017 1020 patchf = self.opener(patchfn, "w")
1018 1021 msg = opts.get('msg', '').rstrip()
1019 1022 if msg:
1020 1023 if comments:
1021 1024 # Remove existing message.
1022 1025 ci = 0
1023 1026 subj = None
1024 1027 for mi in xrange(len(message)):
1025 1028 if comments[ci].lower().startswith('subject: '):
1026 1029 subj = comments[ci][9:]
1027 1030 while message[mi] != comments[ci] and message[mi] != subj:
1028 1031 ci += 1
1029 1032 del comments[ci]
1030 1033 comments.append(msg)
1031 1034 if comments:
1032 1035 comments = "\n".join(comments) + '\n\n'
1033 1036 patchf.write(comments)
1034 1037
1035 1038 if opts.get('git'):
1036 1039 self.diffopts().git = True
1037 1040 fns, matchfn, anypats = cmdutil.matchpats(repo, pats, opts)
1038 1041 tip = repo.changelog.tip()
1039 1042 if top == tip:
1040 1043 # if the top of our patch queue is also the tip, there is an
1041 1044 # optimization here. We update the dirstate in place and strip
1042 1045 # off the tip commit. Then just commit the current directory
1043 1046 # tree. We can also send repo.commit the list of files
1044 1047 # changed to speed up the diff
1045 1048 #
1046 1049 # in short mode, we only diff the files included in the
1047 1050 # patch already
1048 1051 #
1049 1052 # this should really read:
1050 1053 # mm, dd, aa, aa2, uu = repo.status(tip, patchparent)[:5]
1051 1054 # but we do it backwards to take advantage of manifest/chlog
1052 1055 # caching against the next repo.status call
1053 1056 #
1054 1057 mm, aa, dd, aa2, uu = repo.status(patchparent, tip)[:5]
1055 1058 changes = repo.changelog.read(tip)
1056 1059 man = repo.manifest.read(changes[0])
1057 1060 aaa = aa[:]
1058 1061 if opts.get('short'):
1059 1062 filelist = mm + aa + dd
1060 1063 match = dict.fromkeys(filelist).__contains__
1061 1064 else:
1062 1065 filelist = None
1063 1066 match = util.always
1064 1067 m, a, r, d, u = repo.status(files=filelist, match=match)[:5]
1065 1068
1066 1069 # we might end up with files that were added between tip and
1067 1070 # the dirstate parent, but then changed in the local dirstate.
1068 1071 # in this case, we want them to only show up in the added section
1069 1072 for x in m:
1070 1073 if x not in aa:
1071 1074 mm.append(x)
1072 1075 # we might end up with files added by the local dirstate that
1073 1076 # were deleted by the patch. In this case, they should only
1074 1077 # show up in the changed section.
1075 1078 for x in a:
1076 1079 if x in dd:
1077 1080 del dd[dd.index(x)]
1078 1081 mm.append(x)
1079 1082 else:
1080 1083 aa.append(x)
1081 1084 # make sure any files deleted in the local dirstate
1082 1085 # are not in the add or change column of the patch
1083 1086 forget = []
1084 1087 for x in d + r:
1085 1088 if x in aa:
1086 1089 del aa[aa.index(x)]
1087 1090 forget.append(x)
1088 1091 continue
1089 1092 elif x in mm:
1090 1093 del mm[mm.index(x)]
1091 1094 dd.append(x)
1092 1095
1093 1096 m = util.unique(mm)
1094 1097 r = util.unique(dd)
1095 1098 a = util.unique(aa)
1096 1099 c = [filter(matchfn, l) for l in (m, a, r, [], u)]
1097 1100 filelist = util.unique(c[0] + c[1] + c[2])
1098 1101 patch.diff(repo, patchparent, files=filelist, match=matchfn,
1099 1102 fp=patchf, changes=c, opts=self.diffopts())
1100 1103 patchf.close()
1101 1104
1102 1105 repo.dirstate.setparents(*cparents)
1103 1106 copies = {}
1104 1107 for dst in a:
1105 1108 src = repo.dirstate.copied(dst)
1106 1109 if src is None:
1107 1110 continue
1108 1111 copies.setdefault(src, []).append(dst)
1109 1112 repo.dirstate.update(a, 'a')
1110 1113 # remember the copies between patchparent and tip
1111 1114 # this may be slow, so don't do it if we're not tracking copies
1112 1115 if self.diffopts().git:
1113 1116 for dst in aaa:
1114 1117 f = repo.file(dst)
1115 1118 src = f.renamed(man[dst])
1116 1119 if src:
1117 1120 copies[src[0]] = copies.get(dst, [])
1118 1121 if dst in a:
1119 1122 copies[src[0]].append(dst)
1120 1123 # we can't copy a file created by the patch itself
1121 1124 if dst in copies:
1122 1125 del copies[dst]
1123 1126 for src, dsts in copies.iteritems():
1124 1127 for dst in dsts:
1125 1128 repo.dirstate.copy(src, dst)
1126 1129 repo.dirstate.update(r, 'r')
1127 1130 # if the patch excludes a modified file, mark that file with mtime=0
1128 1131 # so status can see it.
1129 1132 mm = []
1130 1133 for i in xrange(len(m)-1, -1, -1):
1131 1134 if not matchfn(m[i]):
1132 1135 mm.append(m[i])
1133 1136 del m[i]
1134 1137 repo.dirstate.update(m, 'n')
1135 1138 repo.dirstate.update(mm, 'n', st_mtime=-1, st_size=-1)
1136 1139 repo.dirstate.forget(forget)
1137 1140
1138 1141 if not msg:
1139 1142 if not message:
1140 1143 message = "patch queue: %s\n" % patchfn
1141 1144 else:
1142 1145 message = "\n".join(message)
1143 1146 else:
1144 1147 message = msg
1145 1148
1146 1149 self.strip(repo, top, update=False, backup='strip', wlock=wlock)
1147 1150 n = repo.commit(filelist, message, changes[1], match=matchfn,
1148 1151 force=1, wlock=wlock)
1149 1152 self.applied[-1] = statusentry(revlog.hex(n), patchfn)
1150 1153 self.applied_dirty = 1
1151 1154 self.removeundo(repo)
1152 1155 else:
1153 1156 self.printdiff(repo, patchparent, fp=patchf)
1154 1157 patchf.close()
1155 1158 added = repo.status()[1]
1156 1159 for a in added:
1157 1160 f = repo.wjoin(a)
1158 1161 try:
1159 1162 os.unlink(f)
1160 1163 except OSError, e:
1161 1164 if e.errno != errno.ENOENT:
1162 1165 raise
1163 1166 try: os.removedirs(os.path.dirname(f))
1164 1167 except: pass
1165 1168 # forget the file copies in the dirstate
1166 1169 # push should readd the files later on
1167 1170 repo.dirstate.forget(added)
1168 1171 self.pop(repo, force=True, wlock=wlock)
1169 1172 self.push(repo, force=True, wlock=wlock)
1170 1173
1171 1174 def init(self, repo, create=False):
1172 1175 if not create and os.path.isdir(self.path):
1173 1176 raise util.Abort(_("patch queue directory already exists"))
1174 1177 try:
1175 1178 os.mkdir(self.path)
1176 1179 except OSError, inst:
1177 1180 if inst.errno != errno.EEXIST or not create:
1178 1181 raise
1179 1182 if create:
1180 1183 return self.qrepo(create=True)
1181 1184
1182 1185 def unapplied(self, repo, patch=None):
1183 1186 if patch and patch not in self.series:
1184 1187 raise util.Abort(_("patch %s is not in series file") % patch)
1185 1188 if not patch:
1186 1189 start = self.series_end()
1187 1190 else:
1188 1191 start = self.series.index(patch) + 1
1189 1192 unapplied = []
1190 1193 for i in xrange(start, len(self.series)):
1191 1194 pushable, reason = self.pushable(i)
1192 1195 if pushable:
1193 1196 unapplied.append((i, self.series[i]))
1194 1197 self.explain_pushable(i)
1195 1198 return unapplied
1196 1199
1197 1200 def qseries(self, repo, missing=None, start=0, length=None, status=None,
1198 1201 summary=False):
1199 1202 def displayname(patchname):
1200 1203 if summary:
1201 1204 msg = self.readheaders(patchname)[0]
1202 1205 msg = msg and ': ' + msg[0] or ': '
1203 1206 else:
1204 1207 msg = ''
1205 1208 return '%s%s' % (patchname, msg)
1206 1209
1207 1210 applied = dict.fromkeys([p.name for p in self.applied])
1208 1211 if length is None:
1209 1212 length = len(self.series) - start
1210 1213 if not missing:
1211 1214 for i in xrange(start, start+length):
1212 1215 patch = self.series[i]
1213 1216 if patch in applied:
1214 1217 stat = 'A'
1215 1218 elif self.pushable(i)[0]:
1216 1219 stat = 'U'
1217 1220 else:
1218 1221 stat = 'G'
1219 1222 pfx = ''
1220 1223 if self.ui.verbose:
1221 1224 pfx = '%d %s ' % (i, stat)
1222 1225 elif status and status != stat:
1223 1226 continue
1224 1227 self.ui.write('%s%s\n' % (pfx, displayname(patch)))
1225 1228 else:
1226 1229 msng_list = []
1227 1230 for root, dirs, files in os.walk(self.path):
1228 1231 d = root[len(self.path) + 1:]
1229 1232 for f in files:
1230 1233 fl = os.path.join(d, f)
1231 1234 if (fl not in self.series and
1232 1235 fl not in (self.status_path, self.series_path,
1233 1236 self.guards_path)
1234 1237 and not fl.startswith('.')):
1235 1238 msng_list.append(fl)
1236 1239 msng_list.sort()
1237 1240 for x in msng_list:
1238 1241 pfx = self.ui.verbose and ('D ') or ''
1239 1242 self.ui.write("%s%s\n" % (pfx, displayname(x)))
1240 1243
1241 1244 def issaveline(self, l):
1242 1245 if l.name == '.hg.patches.save.line':
1243 1246 return True
1244 1247
1245 1248 def qrepo(self, create=False):
1246 1249 if create or os.path.isdir(self.join(".hg")):
1247 1250 return hg.repository(self.ui, path=self.path, create=create)
1248 1251
1249 1252 def restore(self, repo, rev, delete=None, qupdate=None):
1250 1253 c = repo.changelog.read(rev)
1251 1254 desc = c[4].strip()
1252 1255 lines = desc.splitlines()
1253 1256 i = 0
1254 1257 datastart = None
1255 1258 series = []
1256 1259 applied = []
1257 1260 qpp = None
1258 1261 for i in xrange(0, len(lines)):
1259 1262 if lines[i] == 'Patch Data:':
1260 1263 datastart = i + 1
1261 1264 elif lines[i].startswith('Dirstate:'):
1262 1265 l = lines[i].rstrip()
1263 1266 l = l[10:].split(' ')
1264 1267 qpp = [ hg.bin(x) for x in l ]
1265 1268 elif datastart != None:
1266 1269 l = lines[i].rstrip()
1267 1270 se = statusentry(l)
1268 1271 file_ = se.name
1269 1272 if se.rev:
1270 1273 applied.append(se)
1271 1274 else:
1272 1275 series.append(file_)
1273 1276 if datastart == None:
1274 1277 self.ui.warn("No saved patch data found\n")
1275 1278 return 1
1276 1279 self.ui.warn("restoring status: %s\n" % lines[0])
1277 1280 self.full_series = series
1278 1281 self.applied = applied
1279 1282 self.parse_series()
1280 1283 self.series_dirty = 1
1281 1284 self.applied_dirty = 1
1282 1285 heads = repo.changelog.heads()
1283 1286 if delete:
1284 1287 if rev not in heads:
1285 1288 self.ui.warn("save entry has children, leaving it alone\n")
1286 1289 else:
1287 1290 self.ui.warn("removing save entry %s\n" % hg.short(rev))
1288 1291 pp = repo.dirstate.parents()
1289 1292 if rev in pp:
1290 1293 update = True
1291 1294 else:
1292 1295 update = False
1293 1296 self.strip(repo, rev, update=update, backup='strip')
1294 1297 if qpp:
1295 1298 self.ui.warn("saved queue repository parents: %s %s\n" %
1296 1299 (hg.short(qpp[0]), hg.short(qpp[1])))
1297 1300 if qupdate:
1298 1301 print "queue directory updating"
1299 1302 r = self.qrepo()
1300 1303 if not r:
1301 1304 self.ui.warn("Unable to load queue repository\n")
1302 1305 return 1
1303 1306 hg.clean(r, qpp[0])
1304 1307
1305 1308 def save(self, repo, msg=None):
1306 1309 if len(self.applied) == 0:
1307 1310 self.ui.warn("save: no patches applied, exiting\n")
1308 1311 return 1
1309 1312 if self.issaveline(self.applied[-1]):
1310 1313 self.ui.warn("status is already saved\n")
1311 1314 return 1
1312 1315
1313 1316 ar = [ ':' + x for x in self.full_series ]
1314 1317 if not msg:
1315 1318 msg = "hg patches saved state"
1316 1319 else:
1317 1320 msg = "hg patches: " + msg.rstrip('\r\n')
1318 1321 r = self.qrepo()
1319 1322 if r:
1320 1323 pp = r.dirstate.parents()
1321 1324 msg += "\nDirstate: %s %s" % (hg.hex(pp[0]), hg.hex(pp[1]))
1322 1325 msg += "\n\nPatch Data:\n"
1323 1326 text = msg + "\n".join([str(x) for x in self.applied]) + '\n' + (ar and
1324 1327 "\n".join(ar) + '\n' or "")
1325 1328 n = repo.commit(None, text, user=None, force=1)
1326 1329 if not n:
1327 1330 self.ui.warn("repo commit failed\n")
1328 1331 return 1
1329 1332 self.applied.append(statusentry(revlog.hex(n),'.hg.patches.save.line'))
1330 1333 self.applied_dirty = 1
1331 1334 self.removeundo(repo)
1332 1335
1333 1336 def full_series_end(self):
1334 1337 if len(self.applied) > 0:
1335 1338 p = self.applied[-1].name
1336 1339 end = self.find_series(p)
1337 1340 if end == None:
1338 1341 return len(self.full_series)
1339 1342 return end + 1
1340 1343 return 0
1341 1344
1342 1345 def series_end(self, all_patches=False):
1343 1346 """If all_patches is False, return the index of the next pushable patch
1344 1347 in the series, or the series length. If all_patches is True, return the
1345 1348 index of the first patch past the last applied one.
1346 1349 """
1347 1350 end = 0
1348 1351 def next(start):
1349 1352 if all_patches:
1350 1353 return start
1351 1354 i = start
1352 1355 while i < len(self.series):
1353 1356 p, reason = self.pushable(i)
1354 1357 if p:
1355 1358 break
1356 1359 self.explain_pushable(i)
1357 1360 i += 1
1358 1361 return i
1359 1362 if len(self.applied) > 0:
1360 1363 p = self.applied[-1].name
1361 1364 try:
1362 1365 end = self.series.index(p)
1363 1366 except ValueError:
1364 1367 return 0
1365 1368 return next(end + 1)
1366 1369 return next(end)
1367 1370
1368 1371 def appliedname(self, index):
1369 1372 pname = self.applied[index].name
1370 1373 if not self.ui.verbose:
1371 1374 p = pname
1372 1375 else:
1373 1376 p = str(self.series.index(pname)) + " " + pname
1374 1377 return p
1375 1378
1376 1379 def qimport(self, repo, files, patchname=None, rev=None, existing=None,
1377 1380 force=None, git=False):
1378 1381 def checkseries(patchname):
1379 1382 if patchname in self.series:
1380 1383 raise util.Abort(_('patch %s is already in the series file')
1381 1384 % patchname)
1382 1385 def checkfile(patchname):
1383 1386 if not force and os.path.exists(self.join(patchname)):
1384 1387 raise util.Abort(_('patch "%s" already exists')
1385 1388 % patchname)
1386 1389
1387 1390 if rev:
1388 1391 if files:
1389 1392 raise util.Abort(_('option "-r" not valid when importing '
1390 1393 'files'))
1391 1394 rev = cmdutil.revrange(repo, rev)
1392 1395 rev.sort(lambda x, y: cmp(y, x))
1393 1396 if (len(files) > 1 or len(rev) > 1) and patchname:
1394 1397 raise util.Abort(_('option "-n" not valid when importing multiple '
1395 1398 'patches'))
1396 1399 i = 0
1397 1400 added = []
1398 1401 if rev:
1399 1402 # If mq patches are applied, we can only import revisions
1400 1403 # that form a linear path to qbase.
1401 1404 # Otherwise, they should form a linear path to a head.
1402 1405 heads = repo.changelog.heads(repo.changelog.node(rev[-1]))
1403 1406 if len(heads) > 1:
1404 1407 raise util.Abort(_('revision %d is the root of more than one '
1405 1408 'branch') % rev[-1])
1406 1409 if self.applied:
1407 1410 base = revlog.hex(repo.changelog.node(rev[0]))
1408 1411 if base in [n.rev for n in self.applied]:
1409 1412 raise util.Abort(_('revision %d is already managed')
1410 1413 % rev[0])
1411 1414 if heads != [revlog.bin(self.applied[-1].rev)]:
1412 1415 raise util.Abort(_('revision %d is not the parent of '
1413 1416 'the queue') % rev[0])
1414 1417 base = repo.changelog.rev(revlog.bin(self.applied[0].rev))
1415 1418 lastparent = repo.changelog.parentrevs(base)[0]
1416 1419 else:
1417 1420 if heads != [repo.changelog.node(rev[0])]:
1418 1421 raise util.Abort(_('revision %d has unmanaged children')
1419 1422 % rev[0])
1420 1423 lastparent = None
1421 1424
1422 1425 if git:
1423 1426 self.diffopts().git = True
1424 1427
1425 1428 for r in rev:
1426 1429 p1, p2 = repo.changelog.parentrevs(r)
1427 1430 n = repo.changelog.node(r)
1428 1431 if p2 != revlog.nullrev:
1429 1432 raise util.Abort(_('cannot import merge revision %d') % r)
1430 1433 if lastparent and lastparent != r:
1431 1434 raise util.Abort(_('revision %d is not the parent of %d')
1432 1435 % (r, lastparent))
1433 1436 lastparent = p1
1434 1437
1435 1438 if not patchname:
1436 1439 patchname = normname('%d.diff' % r)
1437 1440 checkseries(patchname)
1438 1441 checkfile(patchname)
1439 1442 self.full_series.insert(0, patchname)
1440 1443
1441 1444 patchf = self.opener(patchname, "w")
1442 1445 patch.export(repo, [n], fp=patchf, opts=self.diffopts())
1443 1446 patchf.close()
1444 1447
1445 1448 se = statusentry(revlog.hex(n), patchname)
1446 1449 self.applied.insert(0, se)
1447 1450
1448 1451 added.append(patchname)
1449 1452 patchname = None
1450 1453 self.parse_series()
1451 1454 self.applied_dirty = 1
1452 1455
1453 1456 for filename in files:
1454 1457 if existing:
1455 1458 if filename == '-':
1456 1459 raise util.Abort(_('-e is incompatible with import from -'))
1457 1460 if not patchname:
1458 1461 patchname = normname(filename)
1459 1462 if not os.path.isfile(self.join(patchname)):
1460 1463 raise util.Abort(_("patch %s does not exist") % patchname)
1461 1464 else:
1462 1465 try:
1463 1466 if filename == '-':
1464 1467 if not patchname:
1465 1468 raise util.Abort(_('need --name to import a patch from -'))
1466 1469 text = sys.stdin.read()
1467 1470 else:
1468 1471 text = file(filename).read()
1469 1472 except IOError:
1470 1473 raise util.Abort(_("unable to read %s") % patchname)
1471 1474 if not patchname:
1472 1475 patchname = normname(os.path.basename(filename))
1473 1476 checkfile(patchname)
1474 1477 patchf = self.opener(patchname, "w")
1475 1478 patchf.write(text)
1476 1479 checkseries(patchname)
1477 1480 index = self.full_series_end() + i
1478 1481 self.full_series[index:index] = [patchname]
1479 1482 self.parse_series()
1480 1483 self.ui.warn("adding %s to series file\n" % patchname)
1481 1484 i += 1
1482 1485 added.append(patchname)
1483 1486 patchname = None
1484 1487 self.series_dirty = 1
1485 1488 qrepo = self.qrepo()
1486 1489 if qrepo:
1487 1490 qrepo.add(added)
1488 1491
1489 1492 def delete(ui, repo, *patches, **opts):
1490 1493 """remove patches from queue
1491 1494
1492 1495 With --rev, mq will stop managing the named revisions. The
1493 1496 patches must be applied and at the base of the stack. This option
1494 1497 is useful when the patches have been applied upstream.
1495 1498
1496 1499 Otherwise, the patches must not be applied.
1497 1500
1498 1501 With --keep, the patch files are preserved in the patch directory."""
1499 1502 q = repo.mq
1500 1503 q.delete(repo, patches, opts)
1501 1504 q.save_dirty()
1502 1505 return 0
1503 1506
1504 1507 def applied(ui, repo, patch=None, **opts):
1505 1508 """print the patches already applied"""
1506 1509 q = repo.mq
1507 1510 if patch:
1508 1511 if patch not in q.series:
1509 1512 raise util.Abort(_("patch %s is not in series file") % patch)
1510 1513 end = q.series.index(patch) + 1
1511 1514 else:
1512 1515 end = q.series_end(True)
1513 1516 return q.qseries(repo, length=end, status='A', summary=opts.get('summary'))
1514 1517
1515 1518 def unapplied(ui, repo, patch=None, **opts):
1516 1519 """print the patches not yet applied"""
1517 1520 q = repo.mq
1518 1521 if patch:
1519 1522 if patch not in q.series:
1520 1523 raise util.Abort(_("patch %s is not in series file") % patch)
1521 1524 start = q.series.index(patch) + 1
1522 1525 else:
1523 1526 start = q.series_end(True)
1524 1527 q.qseries(repo, start=start, status='U', summary=opts.get('summary'))
1525 1528
1526 1529 def qimport(ui, repo, *filename, **opts):
1527 1530 """import a patch
1528 1531
1529 1532 The patch will have the same name as its source file unless you
1530 1533 give it a new one with --name.
1531 1534
1532 1535 You can register an existing patch inside the patch directory
1533 1536 with the --existing flag.
1534 1537
1535 1538 With --force, an existing patch of the same name will be overwritten.
1536 1539
1537 1540 An existing changeset may be placed under mq control with --rev
1538 1541 (e.g. qimport --rev tip -n patch will place tip under mq control).
1539 1542 With --git, patches imported with --rev will use the git diff
1540 1543 format.
1541 1544 """
1542 1545 q = repo.mq
1543 1546 q.qimport(repo, filename, patchname=opts['name'],
1544 1547 existing=opts['existing'], force=opts['force'], rev=opts['rev'],
1545 1548 git=opts['git'])
1546 1549 q.save_dirty()
1547 1550 return 0
1548 1551
1549 1552 def init(ui, repo, **opts):
1550 1553 """init a new queue repository
1551 1554
1552 1555 The queue repository is unversioned by default. If -c is
1553 1556 specified, qinit will create a separate nested repository
1554 1557 for patches. Use qcommit to commit changes to this queue
1555 1558 repository."""
1556 1559 q = repo.mq
1557 1560 r = q.init(repo, create=opts['create_repo'])
1558 1561 q.save_dirty()
1559 1562 if r:
1560 1563 if not os.path.exists(r.wjoin('.hgignore')):
1561 1564 fp = r.wopener('.hgignore', 'w')
1562 1565 fp.write('syntax: glob\n')
1563 1566 fp.write('status\n')
1564 1567 fp.write('guards\n')
1565 1568 fp.close()
1566 1569 if not os.path.exists(r.wjoin('series')):
1567 1570 r.wopener('series', 'w').close()
1568 1571 r.add(['.hgignore', 'series'])
1569 1572 commands.add(ui, r)
1570 1573 return 0
1571 1574
1572 1575 def clone(ui, source, dest=None, **opts):
1573 1576 '''clone main and patch repository at same time
1574 1577
1575 1578 If source is local, destination will have no patches applied. If
1576 1579 source is remote, this command can not check if patches are
1577 1580 applied in source, so cannot guarantee that patches are not
1578 1581 applied in destination. If you clone remote repository, be sure
1579 1582 before that it has no patches applied.
1580 1583
1581 1584 Source patch repository is looked for in <src>/.hg/patches by
1582 1585 default. Use -p <url> to change.
1583 1586 '''
1584 1587 cmdutil.setremoteconfig(ui, opts)
1585 1588 if dest is None:
1586 1589 dest = hg.defaultdest(source)
1587 1590 sr = hg.repository(ui, ui.expandpath(source))
1588 1591 qbase, destrev = None, None
1589 1592 if sr.local():
1590 1593 if sr.mq.applied:
1591 1594 qbase = revlog.bin(sr.mq.applied[0].rev)
1592 1595 if not hg.islocal(dest):
1593 1596 heads = dict.fromkeys(sr.heads())
1594 1597 for h in sr.heads(qbase):
1595 1598 del heads[h]
1596 1599 destrev = heads.keys()
1597 1600 destrev.append(sr.changelog.parents(qbase)[0])
1598 1601 ui.note(_('cloning main repo\n'))
1599 1602 sr, dr = hg.clone(ui, sr.url(), dest,
1600 1603 pull=opts['pull'],
1601 1604 rev=destrev,
1602 1605 update=False,
1603 1606 stream=opts['uncompressed'])
1604 1607 ui.note(_('cloning patch repo\n'))
1605 1608 spr, dpr = hg.clone(ui, opts['patches'] or (sr.url() + '/.hg/patches'),
1606 1609 dr.url() + '/.hg/patches',
1607 1610 pull=opts['pull'],
1608 1611 update=not opts['noupdate'],
1609 1612 stream=opts['uncompressed'])
1610 1613 if dr.local():
1611 1614 if qbase:
1612 1615 ui.note(_('stripping applied patches from destination repo\n'))
1613 1616 dr.mq.strip(dr, qbase, update=False, backup=None)
1614 1617 if not opts['noupdate']:
1615 1618 ui.note(_('updating destination repo\n'))
1616 1619 hg.update(dr, dr.changelog.tip())
1617 1620
1618 1621 def commit(ui, repo, *pats, **opts):
1619 1622 """commit changes in the queue repository"""
1620 1623 q = repo.mq
1621 1624 r = q.qrepo()
1622 1625 if not r: raise util.Abort('no queue repository')
1623 1626 commands.commit(r.ui, r, *pats, **opts)
1624 1627
1625 1628 def series(ui, repo, **opts):
1626 1629 """print the entire series file"""
1627 1630 repo.mq.qseries(repo, missing=opts['missing'], summary=opts['summary'])
1628 1631 return 0
1629 1632
1630 1633 def top(ui, repo, **opts):
1631 1634 """print the name of the current patch"""
1632 1635 q = repo.mq
1633 1636 t = q.applied and q.series_end(True) or 0
1634 1637 if t:
1635 1638 return q.qseries(repo, start=t-1, length=1, status='A',
1636 1639 summary=opts.get('summary'))
1637 1640 else:
1638 1641 ui.write("No patches applied\n")
1639 1642 return 1
1640 1643
1641 1644 def next(ui, repo, **opts):
1642 1645 """print the name of the next patch"""
1643 1646 q = repo.mq
1644 1647 end = q.series_end()
1645 1648 if end == len(q.series):
1646 1649 ui.write("All patches applied\n")
1647 1650 return 1
1648 1651 return q.qseries(repo, start=end, length=1, summary=opts.get('summary'))
1649 1652
1650 1653 def prev(ui, repo, **opts):
1651 1654 """print the name of the previous patch"""
1652 1655 q = repo.mq
1653 1656 l = len(q.applied)
1654 1657 if l == 1:
1655 1658 ui.write("Only one patch applied\n")
1656 1659 return 1
1657 1660 if not l:
1658 1661 ui.write("No patches applied\n")
1659 1662 return 1
1660 1663 return q.qseries(repo, start=l-2, length=1, status='A',
1661 1664 summary=opts.get('summary'))
1662 1665
1663 1666 def new(ui, repo, patch, **opts):
1664 1667 """create a new patch
1665 1668
1666 1669 qnew creates a new patch on top of the currently-applied patch
1667 1670 (if any). It will refuse to run if there are any outstanding
1668 1671 changes unless -f is specified, in which case the patch will
1669 1672 be initialised with them.
1670 1673
1671 1674 -e, -m or -l set the patch header as well as the commit message.
1672 1675 If none is specified, the patch header is empty and the
1673 1676 commit message is 'New patch: PATCH'"""
1674 1677 q = repo.mq
1675 1678 message = cmdutil.logmessage(opts)
1676 1679 if opts['edit']:
1677 1680 message = ui.edit(message, ui.username())
1678 1681 q.new(repo, patch, msg=message, force=opts['force'])
1679 1682 q.save_dirty()
1680 1683 return 0
1681 1684
1682 1685 def refresh(ui, repo, *pats, **opts):
1683 1686 """update the current patch
1684 1687
1685 1688 If any file patterns are provided, the refreshed patch will contain only
1686 1689 the modifications that match those patterns; the remaining modifications
1687 1690 will remain in the working directory.
1688 1691
1689 1692 hg add/remove/copy/rename work as usual, though you might want to use
1690 1693 git-style patches (--git or [diff] git=1) to track copies and renames.
1691 1694 """
1692 1695 q = repo.mq
1693 1696 message = cmdutil.logmessage(opts)
1694 1697 if opts['edit']:
1695 1698 if message:
1696 1699 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
1697 1700 patch = q.applied[-1].name
1698 1701 (message, comment, user, date, hasdiff) = q.readheaders(patch)
1699 1702 message = ui.edit('\n'.join(message), user or ui.username())
1700 1703 ret = q.refresh(repo, pats, msg=message, **opts)
1701 1704 q.save_dirty()
1702 1705 return ret
1703 1706
1704 1707 def diff(ui, repo, *pats, **opts):
1705 1708 """diff of the current patch"""
1706 1709 repo.mq.diff(repo, pats, opts)
1707 1710 return 0
1708 1711
1709 1712 def fold(ui, repo, *files, **opts):
1710 1713 """fold the named patches into the current patch
1711 1714
1712 1715 Patches must not yet be applied. Each patch will be successively
1713 1716 applied to the current patch in the order given. If all the
1714 1717 patches apply successfully, the current patch will be refreshed
1715 1718 with the new cumulative patch, and the folded patches will
1716 1719 be deleted. With -k/--keep, the folded patch files will not
1717 1720 be removed afterwards.
1718 1721
1719 1722 The header for each folded patch will be concatenated with
1720 1723 the current patch header, separated by a line of '* * *'."""
1721 1724
1722 1725 q = repo.mq
1723 1726
1724 1727 if not files:
1725 1728 raise util.Abort(_('qfold requires at least one patch name'))
1726 1729 if not q.check_toppatch(repo):
1727 1730 raise util.Abort(_('No patches applied'))
1728 1731
1729 1732 message = cmdutil.logmessage(opts)
1730 1733 if opts['edit']:
1731 1734 if message:
1732 1735 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
1733 1736
1734 1737 parent = q.lookup('qtip')
1735 1738 patches = []
1736 1739 messages = []
1737 1740 for f in files:
1738 1741 p = q.lookup(f)
1739 1742 if p in patches or p == parent:
1740 1743 ui.warn(_('Skipping already folded patch %s') % p)
1741 1744 if q.isapplied(p):
1742 1745 raise util.Abort(_('qfold cannot fold already applied patch %s') % p)
1743 1746 patches.append(p)
1744 1747
1745 1748 for p in patches:
1746 1749 if not message:
1747 1750 messages.append(q.readheaders(p)[0])
1748 1751 pf = q.join(p)
1749 1752 (patchsuccess, files, fuzz) = q.patch(repo, pf)
1750 1753 if not patchsuccess:
1751 1754 raise util.Abort(_('Error folding patch %s') % p)
1752 1755 patch.updatedir(ui, repo, files)
1753 1756
1754 1757 if not message:
1755 1758 message, comments, user = q.readheaders(parent)[0:3]
1756 1759 for msg in messages:
1757 1760 message.append('* * *')
1758 1761 message.extend(msg)
1759 1762 message = '\n'.join(message)
1760 1763
1761 1764 if opts['edit']:
1762 1765 message = ui.edit(message, user or ui.username())
1763 1766
1764 1767 q.refresh(repo, msg=message)
1765 1768 q.delete(repo, patches, opts)
1766 1769 q.save_dirty()
1767 1770
1768 1771 def goto(ui, repo, patch, **opts):
1769 1772 '''push or pop patches until named patch is at top of stack'''
1770 1773 q = repo.mq
1771 1774 patch = q.lookup(patch)
1772 1775 if q.isapplied(patch):
1773 1776 ret = q.pop(repo, patch, force=opts['force'])
1774 1777 else:
1775 1778 ret = q.push(repo, patch, force=opts['force'])
1776 1779 q.save_dirty()
1777 1780 return ret
1778 1781
1779 1782 def guard(ui, repo, *args, **opts):
1780 1783 '''set or print guards for a patch
1781 1784
1782 1785 Guards control whether a patch can be pushed. A patch with no
1783 1786 guards is always pushed. A patch with a positive guard ("+foo") is
1784 1787 pushed only if the qselect command has activated it. A patch with
1785 1788 a negative guard ("-foo") is never pushed if the qselect command
1786 1789 has activated it.
1787 1790
1788 1791 With no arguments, print the currently active guards.
1789 1792 With arguments, set guards for the named patch.
1790 1793
1791 1794 To set a negative guard "-foo" on topmost patch ("--" is needed so
1792 1795 hg will not interpret "-foo" as an option):
1793 1796 hg qguard -- -foo
1794 1797
1795 1798 To set guards on another patch:
1796 1799 hg qguard other.patch +2.6.17 -stable
1797 1800 '''
1798 1801 def status(idx):
1799 1802 guards = q.series_guards[idx] or ['unguarded']
1800 1803 ui.write('%s: %s\n' % (q.series[idx], ' '.join(guards)))
1801 1804 q = repo.mq
1802 1805 patch = None
1803 1806 args = list(args)
1804 1807 if opts['list']:
1805 1808 if args or opts['none']:
1806 1809 raise util.Abort(_('cannot mix -l/--list with options or arguments'))
1807 1810 for i in xrange(len(q.series)):
1808 1811 status(i)
1809 1812 return
1810 1813 if not args or args[0][0:1] in '-+':
1811 1814 if not q.applied:
1812 1815 raise util.Abort(_('no patches applied'))
1813 1816 patch = q.applied[-1].name
1814 1817 if patch is None and args[0][0:1] not in '-+':
1815 1818 patch = args.pop(0)
1816 1819 if patch is None:
1817 1820 raise util.Abort(_('no patch to work with'))
1818 1821 if args or opts['none']:
1819 1822 idx = q.find_series(patch)
1820 1823 if idx is None:
1821 1824 raise util.Abort(_('no patch named %s') % patch)
1822 1825 q.set_guards(idx, args)
1823 1826 q.save_dirty()
1824 1827 else:
1825 1828 status(q.series.index(q.lookup(patch)))
1826 1829
1827 1830 def header(ui, repo, patch=None):
1828 1831 """Print the header of the topmost or specified patch"""
1829 1832 q = repo.mq
1830 1833
1831 1834 if patch:
1832 1835 patch = q.lookup(patch)
1833 1836 else:
1834 1837 if not q.applied:
1835 1838 ui.write('No patches applied\n')
1836 1839 return 1
1837 1840 patch = q.lookup('qtip')
1838 1841 message = repo.mq.readheaders(patch)[0]
1839 1842
1840 1843 ui.write('\n'.join(message) + '\n')
1841 1844
1842 1845 def lastsavename(path):
1843 1846 (directory, base) = os.path.split(path)
1844 1847 names = os.listdir(directory)
1845 1848 namere = re.compile("%s.([0-9]+)" % base)
1846 1849 maxindex = None
1847 1850 maxname = None
1848 1851 for f in names:
1849 1852 m = namere.match(f)
1850 1853 if m:
1851 1854 index = int(m.group(1))
1852 1855 if maxindex == None or index > maxindex:
1853 1856 maxindex = index
1854 1857 maxname = f
1855 1858 if maxname:
1856 1859 return (os.path.join(directory, maxname), maxindex)
1857 1860 return (None, None)
1858 1861
1859 1862 def savename(path):
1860 1863 (last, index) = lastsavename(path)
1861 1864 if last is None:
1862 1865 index = 0
1863 1866 newpath = path + ".%d" % (index + 1)
1864 1867 return newpath
1865 1868
1866 1869 def push(ui, repo, patch=None, **opts):
1867 1870 """push the next patch onto the stack"""
1868 1871 q = repo.mq
1869 1872 mergeq = None
1870 1873
1871 1874 if opts['all']:
1872 1875 if not q.series:
1873 1876 ui.warn(_('no patches in series\n'))
1874 1877 return 0
1875 1878 patch = q.series[-1]
1876 1879 if opts['merge']:
1877 1880 if opts['name']:
1878 1881 newpath = opts['name']
1879 1882 else:
1880 1883 newpath, i = lastsavename(q.path)
1881 1884 if not newpath:
1882 1885 ui.warn("no saved queues found, please use -n\n")
1883 1886 return 1
1884 1887 mergeq = queue(ui, repo.join(""), newpath)
1885 1888 ui.warn("merging with queue at: %s\n" % mergeq.path)
1886 1889 ret = q.push(repo, patch, force=opts['force'], list=opts['list'],
1887 1890 mergeq=mergeq)
1888 1891 return ret
1889 1892
1890 1893 def pop(ui, repo, patch=None, **opts):
1891 1894 """pop the current patch off the stack"""
1892 1895 localupdate = True
1893 1896 if opts['name']:
1894 1897 q = queue(ui, repo.join(""), repo.join(opts['name']))
1895 1898 ui.warn('using patch queue: %s\n' % q.path)
1896 1899 localupdate = False
1897 1900 else:
1898 1901 q = repo.mq
1899 1902 ret = q.pop(repo, patch, force=opts['force'], update=localupdate,
1900 1903 all=opts['all'])
1901 1904 q.save_dirty()
1902 1905 return ret
1903 1906
1904 1907 def rename(ui, repo, patch, name=None, **opts):
1905 1908 """rename a patch
1906 1909
1907 1910 With one argument, renames the current patch to PATCH1.
1908 1911 With two arguments, renames PATCH1 to PATCH2."""
1909 1912
1910 1913 q = repo.mq
1911 1914
1912 1915 if not name:
1913 1916 name = patch
1914 1917 patch = None
1915 1918
1916 1919 if patch:
1917 1920 patch = q.lookup(patch)
1918 1921 else:
1919 1922 if not q.applied:
1920 1923 ui.write(_('No patches applied\n'))
1921 1924 return
1922 1925 patch = q.lookup('qtip')
1923 1926 absdest = q.join(name)
1924 1927 if os.path.isdir(absdest):
1925 1928 name = normname(os.path.join(name, os.path.basename(patch)))
1926 1929 absdest = q.join(name)
1927 1930 if os.path.exists(absdest):
1928 1931 raise util.Abort(_('%s already exists') % absdest)
1929 1932
1930 1933 if name in q.series:
1931 1934 raise util.Abort(_('A patch named %s already exists in the series file') % name)
1932 1935
1933 1936 if ui.verbose:
1934 1937 ui.write('Renaming %s to %s\n' % (patch, name))
1935 1938 i = q.find_series(patch)
1936 1939 guards = q.guard_re.findall(q.full_series[i])
1937 1940 q.full_series[i] = name + ''.join([' #' + g for g in guards])
1938 1941 q.parse_series()
1939 1942 q.series_dirty = 1
1940 1943
1941 1944 info = q.isapplied(patch)
1942 1945 if info:
1943 1946 q.applied[info[0]] = statusentry(info[1], name)
1944 1947 q.applied_dirty = 1
1945 1948
1946 1949 util.rename(q.join(patch), absdest)
1947 1950 r = q.qrepo()
1948 1951 if r:
1949 1952 wlock = r.wlock()
1950 1953 if r.dirstate.state(name) == 'r':
1951 1954 r.undelete([name], wlock)
1952 1955 r.copy(patch, name, wlock)
1953 1956 r.remove([patch], False, wlock)
1954 1957
1955 1958 q.save_dirty()
1956 1959
1957 1960 def restore(ui, repo, rev, **opts):
1958 1961 """restore the queue state saved by a rev"""
1959 1962 rev = repo.lookup(rev)
1960 1963 q = repo.mq
1961 1964 q.restore(repo, rev, delete=opts['delete'],
1962 1965 qupdate=opts['update'])
1963 1966 q.save_dirty()
1964 1967 return 0
1965 1968
1966 1969 def save(ui, repo, **opts):
1967 1970 """save current queue state"""
1968 1971 q = repo.mq
1969 1972 message = cmdutil.logmessage(opts)
1970 1973 ret = q.save(repo, msg=message)
1971 1974 if ret:
1972 1975 return ret
1973 1976 q.save_dirty()
1974 1977 if opts['copy']:
1975 1978 path = q.path
1976 1979 if opts['name']:
1977 1980 newpath = os.path.join(q.basepath, opts['name'])
1978 1981 if os.path.exists(newpath):
1979 1982 if not os.path.isdir(newpath):
1980 1983 raise util.Abort(_('destination %s exists and is not '
1981 1984 'a directory') % newpath)
1982 1985 if not opts['force']:
1983 1986 raise util.Abort(_('destination %s exists, '
1984 1987 'use -f to force') % newpath)
1985 1988 else:
1986 1989 newpath = savename(path)
1987 1990 ui.warn("copy %s to %s\n" % (path, newpath))
1988 1991 util.copyfiles(path, newpath)
1989 1992 if opts['empty']:
1990 1993 try:
1991 1994 os.unlink(q.join(q.status_path))
1992 1995 except:
1993 1996 pass
1994 1997 return 0
1995 1998
1996 1999 def strip(ui, repo, rev, **opts):
1997 2000 """strip a revision and all later revs on the same branch"""
1998 2001 rev = repo.lookup(rev)
1999 2002 backup = 'all'
2000 2003 if opts['backup']:
2001 2004 backup = 'strip'
2002 2005 elif opts['nobackup']:
2003 2006 backup = 'none'
2004 2007 update = repo.dirstate.parents()[0] != revlog.nullid
2005 2008 repo.mq.strip(repo, rev, backup=backup, update=update)
2006 2009 return 0
2007 2010
2008 2011 def select(ui, repo, *args, **opts):
2009 2012 '''set or print guarded patches to push
2010 2013
2011 2014 Use the qguard command to set or print guards on patch, then use
2012 2015 qselect to tell mq which guards to use. A patch will be pushed if it
2013 2016 has no guards or any positive guards match the currently selected guard,
2014 2017 but will not be pushed if any negative guards match the current guard.
2015 2018 For example:
2016 2019
2017 2020 qguard foo.patch -stable (negative guard)
2018 2021 qguard bar.patch +stable (positive guard)
2019 2022 qselect stable
2020 2023
2021 2024 This activates the "stable" guard. mq will skip foo.patch (because
2022 2025 it has a negative match) but push bar.patch (because it
2023 2026 has a positive match).
2024 2027
2025 2028 With no arguments, prints the currently active guards.
2026 2029 With one argument, sets the active guard.
2027 2030
2028 2031 Use -n/--none to deactivate guards (no other arguments needed).
2029 2032 When no guards are active, patches with positive guards are skipped
2030 2033 and patches with negative guards are pushed.
2031 2034
2032 2035 qselect can change the guards on applied patches. It does not pop
2033 2036 guarded patches by default. Use --pop to pop back to the last applied
2034 2037 patch that is not guarded. Use --reapply (which implies --pop) to push
2035 2038 back to the current patch afterwards, but skip guarded patches.
2036 2039
2037 2040 Use -s/--series to print a list of all guards in the series file (no
2038 2041 other arguments needed). Use -v for more information.'''
2039 2042
2040 2043 q = repo.mq
2041 2044 guards = q.active()
2042 2045 if args or opts['none']:
2043 2046 old_unapplied = q.unapplied(repo)
2044 2047 old_guarded = [i for i in xrange(len(q.applied)) if
2045 2048 not q.pushable(i)[0]]
2046 2049 q.set_active(args)
2047 2050 q.save_dirty()
2048 2051 if not args:
2049 2052 ui.status(_('guards deactivated\n'))
2050 2053 if not opts['pop'] and not opts['reapply']:
2051 2054 unapplied = q.unapplied(repo)
2052 2055 guarded = [i for i in xrange(len(q.applied))
2053 2056 if not q.pushable(i)[0]]
2054 2057 if len(unapplied) != len(old_unapplied):
2055 2058 ui.status(_('number of unguarded, unapplied patches has '
2056 2059 'changed from %d to %d\n') %
2057 2060 (len(old_unapplied), len(unapplied)))
2058 2061 if len(guarded) != len(old_guarded):
2059 2062 ui.status(_('number of guarded, applied patches has changed '
2060 2063 'from %d to %d\n') %
2061 2064 (len(old_guarded), len(guarded)))
2062 2065 elif opts['series']:
2063 2066 guards = {}
2064 2067 noguards = 0
2065 2068 for gs in q.series_guards:
2066 2069 if not gs:
2067 2070 noguards += 1
2068 2071 for g in gs:
2069 2072 guards.setdefault(g, 0)
2070 2073 guards[g] += 1
2071 2074 if ui.verbose:
2072 2075 guards['NONE'] = noguards
2073 2076 guards = guards.items()
2074 2077 guards.sort(lambda a, b: cmp(a[0][1:], b[0][1:]))
2075 2078 if guards:
2076 2079 ui.note(_('guards in series file:\n'))
2077 2080 for guard, count in guards:
2078 2081 ui.note('%2d ' % count)
2079 2082 ui.write(guard, '\n')
2080 2083 else:
2081 2084 ui.note(_('no guards in series file\n'))
2082 2085 else:
2083 2086 if guards:
2084 2087 ui.note(_('active guards:\n'))
2085 2088 for g in guards:
2086 2089 ui.write(g, '\n')
2087 2090 else:
2088 2091 ui.write(_('no active guards\n'))
2089 2092 reapply = opts['reapply'] and q.applied and q.appliedname(-1)
2090 2093 popped = False
2091 2094 if opts['pop'] or opts['reapply']:
2092 2095 for i in xrange(len(q.applied)):
2093 2096 pushable, reason = q.pushable(i)
2094 2097 if not pushable:
2095 2098 ui.status(_('popping guarded patches\n'))
2096 2099 popped = True
2097 2100 if i == 0:
2098 2101 q.pop(repo, all=True)
2099 2102 else:
2100 2103 q.pop(repo, i-1)
2101 2104 break
2102 2105 if popped:
2103 2106 try:
2104 2107 if reapply:
2105 2108 ui.status(_('reapplying unguarded patches\n'))
2106 2109 q.push(repo, reapply)
2107 2110 finally:
2108 2111 q.save_dirty()
2109 2112
2110 2113 def reposetup(ui, repo):
2111 2114 class mqrepo(repo.__class__):
2112 2115 def abort_if_wdir_patched(self, errmsg, force=False):
2113 2116 if self.mq.applied and not force:
2114 2117 parent = revlog.hex(self.dirstate.parents()[0])
2115 2118 if parent in [s.rev for s in self.mq.applied]:
2116 2119 raise util.Abort(errmsg)
2117 2120
2118 2121 def commit(self, *args, **opts):
2119 2122 if len(args) >= 6:
2120 2123 force = args[5]
2121 2124 else:
2122 2125 force = opts.get('force')
2123 2126 self.abort_if_wdir_patched(
2124 2127 _('cannot commit over an applied mq patch'),
2125 2128 force)
2126 2129
2127 2130 return super(mqrepo, self).commit(*args, **opts)
2128 2131
2129 2132 def push(self, remote, force=False, revs=None):
2130 2133 if self.mq.applied and not force and not revs:
2131 2134 raise util.Abort(_('source has mq patches applied'))
2132 2135 return super(mqrepo, self).push(remote, force, revs)
2133 2136
2134 2137 def tags(self):
2135 2138 if self.tagscache:
2136 2139 return self.tagscache
2137 2140
2138 2141 tagscache = super(mqrepo, self).tags()
2139 2142
2140 2143 q = self.mq
2141 2144 if not q.applied:
2142 2145 return tagscache
2143 2146
2144 2147 mqtags = [(revlog.bin(patch.rev), patch.name) for patch in q.applied]
2145 2148 mqtags.append((mqtags[-1][0], 'qtip'))
2146 2149 mqtags.append((mqtags[0][0], 'qbase'))
2147 2150 mqtags.append((self.changelog.parents(mqtags[0][0])[0], 'qparent'))
2148 2151 for patch in mqtags:
2149 2152 if patch[1] in tagscache:
2150 2153 self.ui.warn('Tag %s overrides mq patch of the same name\n' % patch[1])
2151 2154 else:
2152 2155 tagscache[patch[1]] = patch[0]
2153 2156
2154 2157 return tagscache
2155 2158
2156 2159 def _branchtags(self):
2157 2160 q = self.mq
2158 2161 if not q.applied:
2159 2162 return super(mqrepo, self)._branchtags()
2160 2163
2161 2164 self.branchcache = {} # avoid recursion in changectx
2162 2165 cl = self.changelog
2163 2166 partial, last, lrev = self._readbranchcache()
2164 2167
2165 2168 qbase = cl.rev(revlog.bin(q.applied[0].rev))
2166 2169 start = lrev + 1
2167 2170 if start < qbase:
2168 2171 # update the cache (excluding the patches) and save it
2169 2172 self._updatebranchcache(partial, lrev+1, qbase)
2170 2173 self._writebranchcache(partial, cl.node(qbase-1), qbase-1)
2171 2174 start = qbase
2172 2175 # if start = qbase, the cache is as updated as it should be.
2173 2176 # if start > qbase, the cache includes (part of) the patches.
2174 2177 # we might as well use it, but we won't save it.
2175 2178
2176 2179 # update the cache up to the tip
2177 2180 self._updatebranchcache(partial, start, cl.count())
2178 2181
2179 2182 return partial
2180 2183
2181 2184 if repo.local():
2182 2185 repo.__class__ = mqrepo
2183 2186 repo.mq = queue(ui, repo.join(""))
2184 2187
2185 2188 seriesopts = [('s', 'summary', None, _('print first line of patch header'))]
2186 2189
2187 2190 cmdtable = {
2188 2191 "qapplied": (applied, [] + seriesopts, 'hg qapplied [-s] [PATCH]'),
2189 2192 "qclone": (clone,
2190 2193 [('', 'pull', None, _('use pull protocol to copy metadata')),
2191 2194 ('U', 'noupdate', None, _('do not update the new working directories')),
2192 2195 ('', 'uncompressed', None,
2193 2196 _('use uncompressed transfer (fast over LAN)')),
2194 2197 ('e', 'ssh', '', _('specify ssh command to use')),
2195 2198 ('p', 'patches', '', _('location of source patch repo')),
2196 2199 ('', 'remotecmd', '',
2197 2200 _('specify hg command to run on the remote side'))],
2198 2201 'hg qclone [OPTION]... SOURCE [DEST]'),
2199 2202 "qcommit|qci":
2200 2203 (commit,
2201 2204 commands.table["^commit|ci"][1],
2202 2205 'hg qcommit [OPTION]... [FILE]...'),
2203 2206 "^qdiff": (diff,
2204 2207 [('g', 'git', None, _('use git extended diff format')),
2205 2208 ('I', 'include', [], _('include names matching the given patterns')),
2206 2209 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2207 2210 'hg qdiff [-I] [-X] [FILE]...'),
2208 2211 "qdelete|qremove|qrm":
2209 2212 (delete,
2210 2213 [('k', 'keep', None, _('keep patch file')),
2211 2214 ('r', 'rev', [], _('stop managing a revision'))],
2212 2215 'hg qdelete [-k] [-r REV]... PATCH...'),
2213 2216 'qfold':
2214 2217 (fold,
2215 2218 [('e', 'edit', None, _('edit patch header')),
2216 2219 ('k', 'keep', None, _('keep folded patch files'))
2217 2220 ] + commands.commitopts,
2218 2221 'hg qfold [-e] [-m <text>] [-l <file] PATCH...'),
2219 2222 'qgoto': (goto, [('f', 'force', None, _('overwrite any local changes'))],
2220 2223 'hg qgoto [OPT]... PATCH'),
2221 2224 'qguard': (guard, [('l', 'list', None, _('list all patches and guards')),
2222 2225 ('n', 'none', None, _('drop all guards'))],
2223 2226 'hg qguard [PATCH] [+GUARD]... [-GUARD]...'),
2224 2227 'qheader': (header, [],
2225 2228 _('hg qheader [PATCH]')),
2226 2229 "^qimport":
2227 2230 (qimport,
2228 2231 [('e', 'existing', None, 'import file in patch dir'),
2229 2232 ('n', 'name', '', 'patch file name'),
2230 2233 ('f', 'force', None, 'overwrite existing files'),
2231 2234 ('r', 'rev', [], 'place existing revisions under mq control'),
2232 2235 ('g', 'git', None, _('use git extended diff format'))],
2233 2236 'hg qimport [-e] [-n NAME] [-f] [-g] [-r REV]... FILE...'),
2234 2237 "^qinit":
2235 2238 (init,
2236 2239 [('c', 'create-repo', None, 'create queue repository')],
2237 2240 'hg qinit [-c]'),
2238 2241 "qnew":
2239 2242 (new,
2240 2243 [('e', 'edit', None, _('edit commit message')),
2241 2244 ('f', 'force', None, _('import uncommitted changes into patch'))
2242 2245 ] + commands.commitopts,
2243 2246 'hg qnew [-e] [-m TEXT] [-l FILE] [-f] PATCH'),
2244 2247 "qnext": (next, [] + seriesopts, 'hg qnext [-s]'),
2245 2248 "qprev": (prev, [] + seriesopts, 'hg qprev [-s]'),
2246 2249 "^qpop":
2247 2250 (pop,
2248 2251 [('a', 'all', None, 'pop all patches'),
2249 2252 ('n', 'name', '', 'queue name to pop'),
2250 2253 ('f', 'force', None, 'forget any local changes')],
2251 2254 'hg qpop [-a] [-n NAME] [-f] [PATCH | INDEX]'),
2252 2255 "^qpush":
2253 2256 (push,
2254 2257 [('f', 'force', None, 'apply if the patch has rejects'),
2255 2258 ('l', 'list', None, 'list patch name in commit text'),
2256 2259 ('a', 'all', None, 'apply all patches'),
2257 2260 ('m', 'merge', None, 'merge from another queue'),
2258 2261 ('n', 'name', '', 'merge queue name')],
2259 2262 'hg qpush [-f] [-l] [-a] [-m] [-n NAME] [PATCH | INDEX]'),
2260 2263 "^qrefresh":
2261 2264 (refresh,
2262 2265 [('e', 'edit', None, _('edit commit message')),
2263 2266 ('g', 'git', None, _('use git extended diff format')),
2264 2267 ('s', 'short', None, 'refresh only files already in the patch'),
2265 2268 ('I', 'include', [], _('include names matching the given patterns')),
2266 2269 ('X', 'exclude', [], _('exclude names matching the given patterns'))
2267 2270 ] + commands.commitopts,
2268 2271 'hg qrefresh [-I] [-X] [-e] [-m TEXT] [-l FILE] [-s] [FILE]...'),
2269 2272 'qrename|qmv':
2270 2273 (rename, [], 'hg qrename PATCH1 [PATCH2]'),
2271 2274 "qrestore":
2272 2275 (restore,
2273 2276 [('d', 'delete', None, 'delete save entry'),
2274 2277 ('u', 'update', None, 'update queue working dir')],
2275 2278 'hg qrestore [-d] [-u] REV'),
2276 2279 "qsave":
2277 2280 (save,
2278 2281 [('c', 'copy', None, 'copy patch directory'),
2279 2282 ('n', 'name', '', 'copy directory name'),
2280 2283 ('e', 'empty', None, 'clear queue status file'),
2281 2284 ('f', 'force', None, 'force copy')] + commands.commitopts,
2282 2285 'hg qsave [-m TEXT] [-l FILE] [-c] [-n NAME] [-e] [-f]'),
2283 2286 "qselect": (select,
2284 2287 [('n', 'none', None, _('disable all guards')),
2285 2288 ('s', 'series', None, _('list all guards in series file')),
2286 2289 ('', 'pop', None,
2287 2290 _('pop to before first guarded applied patch')),
2288 2291 ('', 'reapply', None, _('pop, then reapply patches'))],
2289 2292 'hg qselect [OPTION]... [GUARD]...'),
2290 2293 "qseries":
2291 2294 (series,
2292 2295 [('m', 'missing', None, 'print patches not in series')] + seriesopts,
2293 2296 'hg qseries [-ms]'),
2294 2297 "^strip":
2295 2298 (strip,
2296 2299 [('f', 'force', None, 'force multi-head removal'),
2297 2300 ('b', 'backup', None, 'bundle unrelated changesets'),
2298 2301 ('n', 'nobackup', None, 'no backups')],
2299 2302 'hg strip [-f] [-b] [-n] REV'),
2300 2303 "qtop": (top, [] + seriesopts, 'hg qtop [-s]'),
2301 2304 "qunapplied": (unapplied, [] + seriesopts, 'hg qunapplied [-s] [PATCH]'),
2302 2305 }
General Comments 0
You need to be logged in to leave comments. Login now