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