##// END OF EJS Templates
qrefresh: fix handling of added files (including copies) in the slow path
Alexis S. L. Carvalho -
r3699:4bafcf7a default
parent child Browse files
Show More
@@ -1,2177 +1,2193 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 try: os.unlink(repo.wjoin(f))
919 except: raise
918 try:
919 os.unlink(repo.wjoin(f))
920 except OSError, e:
921 if e.errno != errno.ENOENT:
922 raise
920 923 try: os.removedirs(os.path.dirname(repo.wjoin(f)))
921 924 except: pass
922 925 if a:
923 926 repo.dirstate.forget(a)
924 927 repo.dirstate.setparents(qp, revlog.nullid)
925 928 self.strip(repo, rev, update=False, backup='strip', wlock=wlock)
926 929 del self.applied[start:end]
927 930 if len(self.applied):
928 931 self.ui.write("Now at: %s\n" % self.applied[-1].name)
929 932 else:
930 933 self.ui.write("Patch queue now empty\n")
931 934
932 935 def diff(self, repo, pats, opts):
933 936 top = self.check_toppatch(repo)
934 937 if not top:
935 938 self.ui.write("No patches applied\n")
936 939 return
937 940 qp = self.qparents(repo, top)
938 941 if opts.get('git'):
939 942 self.diffopts().git = True
940 943 self.printdiff(repo, qp, files=pats, opts=opts)
941 944
942 945 def refresh(self, repo, pats=None, **opts):
943 946 if len(self.applied) == 0:
944 947 self.ui.write("No patches applied\n")
945 948 return 1
946 949 wlock = repo.wlock()
947 950 self.check_toppatch(repo)
948 951 (top, patchfn) = (self.applied[-1].rev, self.applied[-1].name)
949 952 top = revlog.bin(top)
950 953 cparents = repo.changelog.parents(top)
951 954 patchparent = self.qparents(repo, top)
952 955 message, comments, user, date, patchfound = self.readheaders(patchfn)
953 956
954 957 patchf = self.opener(patchfn, "w")
955 958 msg = opts.get('msg', '').rstrip()
956 959 if msg:
957 960 if comments:
958 961 # Remove existing message.
959 962 ci = 0
960 963 for mi in xrange(len(message)):
961 964 while message[mi] != comments[ci]:
962 965 ci += 1
963 966 del comments[ci]
964 967 comments.append(msg)
965 968 if comments:
966 969 comments = "\n".join(comments) + '\n\n'
967 970 patchf.write(comments)
968 971
969 972 if opts.get('git'):
970 973 self.diffopts().git = True
971 974 fns, matchfn, anypats = cmdutil.matchpats(repo, pats, opts)
972 975 tip = repo.changelog.tip()
973 976 if top == tip:
974 977 # if the top of our patch queue is also the tip, there is an
975 978 # optimization here. We update the dirstate in place and strip
976 979 # off the tip commit. Then just commit the current directory
977 980 # tree. We can also send repo.commit the list of files
978 981 # changed to speed up the diff
979 982 #
980 983 # in short mode, we only diff the files included in the
981 984 # patch already
982 985 #
983 986 # this should really read:
984 987 # mm, dd, aa, aa2, uu = repo.status(tip, patchparent)[:5]
985 988 # but we do it backwards to take advantage of manifest/chlog
986 989 # caching against the next repo.status call
987 990 #
988 991 mm, aa, dd, aa2, uu = repo.status(patchparent, tip)[:5]
989 992 if opts.get('short'):
990 993 filelist = mm + aa + dd
991 994 else:
992 995 filelist = None
993 996 m, a, r, d, u = repo.status(files=filelist)[:5]
994 997
995 998 # we might end up with files that were added between tip and
996 999 # the dirstate parent, but then changed in the local dirstate.
997 1000 # in this case, we want them to only show up in the added section
998 1001 for x in m:
999 1002 if x not in aa:
1000 1003 mm.append(x)
1001 1004 # we might end up with files added by the local dirstate that
1002 1005 # were deleted by the patch. In this case, they should only
1003 1006 # show up in the changed section.
1004 1007 for x in a:
1005 1008 if x in dd:
1006 1009 del dd[dd.index(x)]
1007 1010 mm.append(x)
1008 1011 else:
1009 1012 aa.append(x)
1010 1013 # make sure any files deleted in the local dirstate
1011 1014 # are not in the add or change column of the patch
1012 1015 forget = []
1013 1016 for x in d + r:
1014 1017 if x in aa:
1015 1018 del aa[aa.index(x)]
1016 1019 forget.append(x)
1017 1020 continue
1018 1021 elif x in mm:
1019 1022 del mm[mm.index(x)]
1020 1023 dd.append(x)
1021 1024
1022 1025 m = util.unique(mm)
1023 1026 r = util.unique(dd)
1024 1027 a = util.unique(aa)
1025 1028 filelist = filter(matchfn, util.unique(m + r + a))
1026 1029 patch.diff(repo, patchparent, files=filelist, match=matchfn,
1027 1030 fp=patchf, changes=(m, a, r, [], u),
1028 1031 opts=self.diffopts())
1029 1032 patchf.close()
1030 1033
1031 1034 changes = repo.changelog.read(tip)
1032 1035 repo.dirstate.setparents(*cparents)
1033 1036 copies = [(f, repo.dirstate.copied(f)) for f in a]
1034 1037 repo.dirstate.update(a, 'a')
1035 1038 for dst, src in copies:
1036 1039 repo.dirstate.copy(src, dst)
1037 1040 repo.dirstate.update(r, 'r')
1038 1041 # if the patch excludes a modified file, mark that file with mtime=0
1039 1042 # so status can see it.
1040 1043 mm = []
1041 1044 for i in xrange(len(m)-1, -1, -1):
1042 1045 if not matchfn(m[i]):
1043 1046 mm.append(m[i])
1044 1047 del m[i]
1045 1048 repo.dirstate.update(m, 'n')
1046 1049 repo.dirstate.update(mm, 'n', st_mtime=0)
1047 1050 repo.dirstate.forget(forget)
1048 1051
1049 1052 if not msg:
1050 1053 if not message:
1051 1054 message = "patch queue: %s\n" % patchfn
1052 1055 else:
1053 1056 message = "\n".join(message)
1054 1057 else:
1055 1058 message = msg
1056 1059
1057 1060 self.strip(repo, top, update=False, backup='strip', wlock=wlock)
1058 1061 n = repo.commit(filelist, message, changes[1], force=1, wlock=wlock)
1059 1062 self.applied[-1] = statusentry(revlog.hex(n), patchfn)
1060 1063 self.applied_dirty = 1
1061 1064 else:
1062 1065 self.printdiff(repo, patchparent, fp=patchf)
1063 1066 patchf.close()
1067 added = repo.status()[1]
1068 for a in added:
1069 f = repo.wjoin(a)
1070 try:
1071 os.unlink(f)
1072 except OSError, e:
1073 if e.errno != errno.ENOENT:
1074 raise
1075 try: os.removedirs(os.path.dirname(f))
1076 except: pass
1077 # forget the file copies in the dirstate
1078 # push should readd the files later on
1079 repo.dirstate.forget(added)
1064 1080 self.pop(repo, force=True, wlock=wlock)
1065 1081 self.push(repo, force=True, wlock=wlock)
1066 1082
1067 1083 def init(self, repo, create=False):
1068 1084 if os.path.isdir(self.path):
1069 1085 raise util.Abort(_("patch queue directory already exists"))
1070 1086 os.mkdir(self.path)
1071 1087 if create:
1072 1088 return self.qrepo(create=True)
1073 1089
1074 1090 def unapplied(self, repo, patch=None):
1075 1091 if patch and patch not in self.series:
1076 1092 raise util.Abort(_("patch %s is not in series file") % patch)
1077 1093 if not patch:
1078 1094 start = self.series_end()
1079 1095 else:
1080 1096 start = self.series.index(patch) + 1
1081 1097 unapplied = []
1082 1098 for i in xrange(start, len(self.series)):
1083 1099 pushable, reason = self.pushable(i)
1084 1100 if pushable:
1085 1101 unapplied.append((i, self.series[i]))
1086 1102 self.explain_pushable(i)
1087 1103 return unapplied
1088 1104
1089 1105 def qseries(self, repo, missing=None, start=0, length=0, status=None,
1090 1106 summary=False):
1091 1107 def displayname(patchname):
1092 1108 if summary:
1093 1109 msg = self.readheaders(patchname)[0]
1094 1110 msg = msg and ': ' + msg[0] or ': '
1095 1111 else:
1096 1112 msg = ''
1097 1113 return '%s%s' % (patchname, msg)
1098 1114
1099 1115 def pname(i):
1100 1116 if status == 'A':
1101 1117 return self.applied[i].name
1102 1118 else:
1103 1119 return self.series[i]
1104 1120
1105 1121 unapplied = self.series_end(all_patches=True)
1106 1122 if not length:
1107 1123 length = len(self.series) - start
1108 1124 if not missing:
1109 1125 for i in xrange(start, start+length):
1110 1126 pfx = ''
1111 1127 if self.ui.verbose:
1112 1128 if i < unapplied:
1113 1129 status = 'A'
1114 1130 elif self.pushable(i)[0]:
1115 1131 status = 'U'
1116 1132 else:
1117 1133 status = 'G'
1118 1134 pfx = '%d %s ' % (i, status)
1119 1135 patch = pname(i)
1120 1136 self.ui.write('%s%s\n' % (pfx, displayname(patch)))
1121 1137 else:
1122 1138 msng_list = []
1123 1139 for root, dirs, files in os.walk(self.path):
1124 1140 d = root[len(self.path) + 1:]
1125 1141 for f in files:
1126 1142 fl = os.path.join(d, f)
1127 1143 if (fl not in self.series and
1128 1144 fl not in (self.status_path, self.series_path)
1129 1145 and not fl.startswith('.')):
1130 1146 msng_list.append(fl)
1131 1147 msng_list.sort()
1132 1148 for x in msng_list:
1133 1149 pfx = self.ui.verbose and ('D ') or ''
1134 1150 self.ui.write("%s%s\n" % (pfx, displayname(x)))
1135 1151
1136 1152 def issaveline(self, l):
1137 1153 if l.name == '.hg.patches.save.line':
1138 1154 return True
1139 1155
1140 1156 def qrepo(self, create=False):
1141 1157 if create or os.path.isdir(self.join(".hg")):
1142 1158 return hg.repository(self.ui, path=self.path, create=create)
1143 1159
1144 1160 def restore(self, repo, rev, delete=None, qupdate=None):
1145 1161 c = repo.changelog.read(rev)
1146 1162 desc = c[4].strip()
1147 1163 lines = desc.splitlines()
1148 1164 i = 0
1149 1165 datastart = None
1150 1166 series = []
1151 1167 applied = []
1152 1168 qpp = None
1153 1169 for i in xrange(0, len(lines)):
1154 1170 if lines[i] == 'Patch Data:':
1155 1171 datastart = i + 1
1156 1172 elif lines[i].startswith('Dirstate:'):
1157 1173 l = lines[i].rstrip()
1158 1174 l = l[10:].split(' ')
1159 1175 qpp = [ hg.bin(x) for x in l ]
1160 1176 elif datastart != None:
1161 1177 l = lines[i].rstrip()
1162 1178 se = statusentry(l)
1163 1179 file_ = se.name
1164 1180 if se.rev:
1165 1181 applied.append(se)
1166 1182 else:
1167 1183 series.append(file_)
1168 1184 if datastart == None:
1169 1185 self.ui.warn("No saved patch data found\n")
1170 1186 return 1
1171 1187 self.ui.warn("restoring status: %s\n" % lines[0])
1172 1188 self.full_series = series
1173 1189 self.applied = applied
1174 1190 self.parse_series()
1175 1191 self.series_dirty = 1
1176 1192 self.applied_dirty = 1
1177 1193 heads = repo.changelog.heads()
1178 1194 if delete:
1179 1195 if rev not in heads:
1180 1196 self.ui.warn("save entry has children, leaving it alone\n")
1181 1197 else:
1182 1198 self.ui.warn("removing save entry %s\n" % hg.short(rev))
1183 1199 pp = repo.dirstate.parents()
1184 1200 if rev in pp:
1185 1201 update = True
1186 1202 else:
1187 1203 update = False
1188 1204 self.strip(repo, rev, update=update, backup='strip')
1189 1205 if qpp:
1190 1206 self.ui.warn("saved queue repository parents: %s %s\n" %
1191 1207 (hg.short(qpp[0]), hg.short(qpp[1])))
1192 1208 if qupdate:
1193 1209 print "queue directory updating"
1194 1210 r = self.qrepo()
1195 1211 if not r:
1196 1212 self.ui.warn("Unable to load queue repository\n")
1197 1213 return 1
1198 1214 hg.clean(r, qpp[0])
1199 1215
1200 1216 def save(self, repo, msg=None):
1201 1217 if len(self.applied) == 0:
1202 1218 self.ui.warn("save: no patches applied, exiting\n")
1203 1219 return 1
1204 1220 if self.issaveline(self.applied[-1]):
1205 1221 self.ui.warn("status is already saved\n")
1206 1222 return 1
1207 1223
1208 1224 ar = [ ':' + x for x in self.full_series ]
1209 1225 if not msg:
1210 1226 msg = "hg patches saved state"
1211 1227 else:
1212 1228 msg = "hg patches: " + msg.rstrip('\r\n')
1213 1229 r = self.qrepo()
1214 1230 if r:
1215 1231 pp = r.dirstate.parents()
1216 1232 msg += "\nDirstate: %s %s" % (hg.hex(pp[0]), hg.hex(pp[1]))
1217 1233 msg += "\n\nPatch Data:\n"
1218 1234 text = msg + "\n".join([str(x) for x in self.applied]) + '\n' + (ar and
1219 1235 "\n".join(ar) + '\n' or "")
1220 1236 n = repo.commit(None, text, user=None, force=1)
1221 1237 if not n:
1222 1238 self.ui.warn("repo commit failed\n")
1223 1239 return 1
1224 1240 self.applied.append(statusentry(revlog.hex(n),'.hg.patches.save.line'))
1225 1241 self.applied_dirty = 1
1226 1242
1227 1243 def full_series_end(self):
1228 1244 if len(self.applied) > 0:
1229 1245 p = self.applied[-1].name
1230 1246 end = self.find_series(p)
1231 1247 if end == None:
1232 1248 return len(self.full_series)
1233 1249 return end + 1
1234 1250 return 0
1235 1251
1236 1252 def series_end(self, all_patches=False):
1237 1253 end = 0
1238 1254 def next(start):
1239 1255 if all_patches:
1240 1256 return start
1241 1257 i = start
1242 1258 while i < len(self.series):
1243 1259 p, reason = self.pushable(i)
1244 1260 if p:
1245 1261 break
1246 1262 self.explain_pushable(i)
1247 1263 i += 1
1248 1264 return i
1249 1265 if len(self.applied) > 0:
1250 1266 p = self.applied[-1].name
1251 1267 try:
1252 1268 end = self.series.index(p)
1253 1269 except ValueError:
1254 1270 return 0
1255 1271 return next(end + 1)
1256 1272 return next(end)
1257 1273
1258 1274 def appliedname(self, index):
1259 1275 pname = self.applied[index].name
1260 1276 if not self.ui.verbose:
1261 1277 p = pname
1262 1278 else:
1263 1279 p = str(self.series.index(pname)) + " " + pname
1264 1280 return p
1265 1281
1266 1282 def qimport(self, repo, files, patchname=None, rev=None, existing=None,
1267 1283 force=None, git=False):
1268 1284 def checkseries(patchname):
1269 1285 if patchname in self.series:
1270 1286 raise util.Abort(_('patch %s is already in the series file')
1271 1287 % patchname)
1272 1288 def checkfile(patchname):
1273 1289 if not force and os.path.exists(self.join(patchname)):
1274 1290 raise util.Abort(_('patch "%s" already exists')
1275 1291 % patchname)
1276 1292
1277 1293 if rev:
1278 1294 if files:
1279 1295 raise util.Abort(_('option "-r" not valid when importing '
1280 1296 'files'))
1281 1297 rev = cmdutil.revrange(self.ui, repo, rev)
1282 1298 rev.sort(lambda x, y: cmp(y, x))
1283 1299 if (len(files) > 1 or len(rev) > 1) and patchname:
1284 1300 raise util.Abort(_('option "-n" not valid when importing multiple '
1285 1301 'patches'))
1286 1302 i = 0
1287 1303 added = []
1288 1304 if rev:
1289 1305 # If mq patches are applied, we can only import revisions
1290 1306 # that form a linear path to qbase.
1291 1307 # Otherwise, they should form a linear path to a head.
1292 1308 heads = repo.changelog.heads(repo.changelog.node(rev[-1]))
1293 1309 if len(heads) > 1:
1294 1310 raise util.Abort(_('revision %d is the root of more than one '
1295 1311 'branch') % rev[-1])
1296 1312 if self.applied:
1297 1313 base = revlog.hex(repo.changelog.node(rev[0]))
1298 1314 if base in [n.rev for n in self.applied]:
1299 1315 raise util.Abort(_('revision %d is already managed')
1300 1316 % rev[0])
1301 1317 if heads != [revlog.bin(self.applied[-1].rev)]:
1302 1318 raise util.Abort(_('revision %d is not the parent of '
1303 1319 'the queue') % rev[0])
1304 1320 base = repo.changelog.rev(revlog.bin(self.applied[0].rev))
1305 1321 lastparent = repo.changelog.parentrevs(base)[0]
1306 1322 else:
1307 1323 if heads != [repo.changelog.node(rev[0])]:
1308 1324 raise util.Abort(_('revision %d has unmanaged children')
1309 1325 % rev[0])
1310 1326 lastparent = None
1311 1327
1312 1328 if git:
1313 1329 self.diffopts().git = True
1314 1330
1315 1331 for r in rev:
1316 1332 p1, p2 = repo.changelog.parentrevs(r)
1317 1333 n = repo.changelog.node(r)
1318 1334 if p2 != revlog.nullrev:
1319 1335 raise util.Abort(_('cannot import merge revision %d') % r)
1320 1336 if lastparent and lastparent != r:
1321 1337 raise util.Abort(_('revision %d is not the parent of %d')
1322 1338 % (r, lastparent))
1323 1339 lastparent = p1
1324 1340
1325 1341 if not patchname:
1326 1342 patchname = '%d.diff' % r
1327 1343 checkseries(patchname)
1328 1344 checkfile(patchname)
1329 1345 self.full_series.insert(0, patchname)
1330 1346
1331 1347 patchf = self.opener(patchname, "w")
1332 1348 patch.export(repo, [n], fp=patchf, opts=self.diffopts())
1333 1349 patchf.close()
1334 1350
1335 1351 se = statusentry(revlog.hex(n), patchname)
1336 1352 self.applied.insert(0, se)
1337 1353
1338 1354 added.append(patchname)
1339 1355 patchname = None
1340 1356 self.parse_series()
1341 1357 self.applied_dirty = 1
1342 1358
1343 1359 for filename in files:
1344 1360 if existing:
1345 1361 if filename == '-':
1346 1362 raise util.Abort(_('-e is incompatible with import from -'))
1347 1363 if not patchname:
1348 1364 patchname = filename
1349 1365 if not os.path.isfile(self.join(patchname)):
1350 1366 raise util.Abort(_("patch %s does not exist") % patchname)
1351 1367 else:
1352 1368 try:
1353 1369 if filename == '-':
1354 1370 if not patchname:
1355 1371 raise util.Abort(_('need --name to import a patch from -'))
1356 1372 text = sys.stdin.read()
1357 1373 else:
1358 1374 text = file(filename).read()
1359 1375 except IOError:
1360 1376 raise util.Abort(_("unable to read %s") % patchname)
1361 1377 if not patchname:
1362 1378 patchname = os.path.basename(filename)
1363 1379 checkfile(patchname)
1364 1380 patchf = self.opener(patchname, "w")
1365 1381 patchf.write(text)
1366 1382 checkseries(patchname)
1367 1383 index = self.full_series_end() + i
1368 1384 self.full_series[index:index] = [patchname]
1369 1385 self.parse_series()
1370 1386 self.ui.warn("adding %s to series file\n" % patchname)
1371 1387 i += 1
1372 1388 added.append(patchname)
1373 1389 patchname = None
1374 1390 self.series_dirty = 1
1375 1391 qrepo = self.qrepo()
1376 1392 if qrepo:
1377 1393 qrepo.add(added)
1378 1394
1379 1395 def delete(ui, repo, *patches, **opts):
1380 1396 """remove patches from queue
1381 1397
1382 1398 With --rev, mq will stop managing the named revisions. The
1383 1399 patches must be applied and at the base of the stack. This option
1384 1400 is useful when the patches have been applied upstream.
1385 1401
1386 1402 Otherwise, the patches must not be applied.
1387 1403
1388 1404 With --keep, the patch files are preserved in the patch directory."""
1389 1405 q = repo.mq
1390 1406 q.delete(repo, patches, opts)
1391 1407 q.save_dirty()
1392 1408 return 0
1393 1409
1394 1410 def applied(ui, repo, patch=None, **opts):
1395 1411 """print the patches already applied"""
1396 1412 q = repo.mq
1397 1413 if patch:
1398 1414 if patch not in q.series:
1399 1415 raise util.Abort(_("patch %s is not in series file") % patch)
1400 1416 end = q.series.index(patch) + 1
1401 1417 else:
1402 1418 end = len(q.applied)
1403 1419 if not end:
1404 1420 return
1405 1421
1406 1422 return q.qseries(repo, length=end, status='A', summary=opts.get('summary'))
1407 1423
1408 1424 def unapplied(ui, repo, patch=None, **opts):
1409 1425 """print the patches not yet applied"""
1410 1426 q = repo.mq
1411 1427 if patch:
1412 1428 if patch not in q.series:
1413 1429 raise util.Abort(_("patch %s is not in series file") % patch)
1414 1430 start = q.series.index(patch) + 1
1415 1431 else:
1416 1432 start = q.series_end()
1417 1433 q.qseries(repo, start=start, summary=opts.get('summary'))
1418 1434
1419 1435 def qimport(ui, repo, *filename, **opts):
1420 1436 """import a patch
1421 1437
1422 1438 The patch will have the same name as its source file unless you
1423 1439 give it a new one with --name.
1424 1440
1425 1441 You can register an existing patch inside the patch directory
1426 1442 with the --existing flag.
1427 1443
1428 1444 With --force, an existing patch of the same name will be overwritten.
1429 1445
1430 1446 An existing changeset may be placed under mq control with --rev
1431 1447 (e.g. qimport --rev tip -n patch will place tip under mq control).
1432 1448 With --git, patches imported with --rev will use the git diff
1433 1449 format.
1434 1450 """
1435 1451 q = repo.mq
1436 1452 q.qimport(repo, filename, patchname=opts['name'],
1437 1453 existing=opts['existing'], force=opts['force'], rev=opts['rev'],
1438 1454 git=opts['git'])
1439 1455 q.save_dirty()
1440 1456 return 0
1441 1457
1442 1458 def init(ui, repo, **opts):
1443 1459 """init a new queue repository
1444 1460
1445 1461 The queue repository is unversioned by default. If -c is
1446 1462 specified, qinit will create a separate nested repository
1447 1463 for patches. Use qcommit to commit changes to this queue
1448 1464 repository."""
1449 1465 q = repo.mq
1450 1466 r = q.init(repo, create=opts['create_repo'])
1451 1467 q.save_dirty()
1452 1468 if r:
1453 1469 fp = r.wopener('.hgignore', 'w')
1454 1470 print >> fp, 'syntax: glob'
1455 1471 print >> fp, 'status'
1456 1472 fp.close()
1457 1473 r.wopener('series', 'w').close()
1458 1474 r.add(['.hgignore', 'series'])
1459 1475 return 0
1460 1476
1461 1477 def clone(ui, source, dest=None, **opts):
1462 1478 '''clone main and patch repository at same time
1463 1479
1464 1480 If source is local, destination will have no patches applied. If
1465 1481 source is remote, this command can not check if patches are
1466 1482 applied in source, so cannot guarantee that patches are not
1467 1483 applied in destination. If you clone remote repository, be sure
1468 1484 before that it has no patches applied.
1469 1485
1470 1486 Source patch repository is looked for in <src>/.hg/patches by
1471 1487 default. Use -p <url> to change.
1472 1488 '''
1473 1489 commands.setremoteconfig(ui, opts)
1474 1490 if dest is None:
1475 1491 dest = hg.defaultdest(source)
1476 1492 sr = hg.repository(ui, ui.expandpath(source))
1477 1493 qbase, destrev = None, None
1478 1494 if sr.local():
1479 1495 reposetup(ui, sr)
1480 1496 if sr.mq.applied:
1481 1497 qbase = revlog.bin(sr.mq.applied[0].rev)
1482 1498 if not hg.islocal(dest):
1483 1499 destrev = sr.parents(qbase)[0]
1484 1500 ui.note(_('cloning main repo\n'))
1485 1501 sr, dr = hg.clone(ui, sr, dest,
1486 1502 pull=opts['pull'],
1487 1503 rev=destrev,
1488 1504 update=False,
1489 1505 stream=opts['uncompressed'])
1490 1506 ui.note(_('cloning patch repo\n'))
1491 1507 spr, dpr = hg.clone(ui, opts['patches'] or (sr.url() + '/.hg/patches'),
1492 1508 dr.url() + '/.hg/patches',
1493 1509 pull=opts['pull'],
1494 1510 update=not opts['noupdate'],
1495 1511 stream=opts['uncompressed'])
1496 1512 if dr.local():
1497 1513 if qbase:
1498 1514 ui.note(_('stripping applied patches from destination repo\n'))
1499 1515 reposetup(ui, dr)
1500 1516 dr.mq.strip(dr, qbase, update=False, backup=None)
1501 1517 if not opts['noupdate']:
1502 1518 ui.note(_('updating destination repo\n'))
1503 1519 hg.update(dr, dr.changelog.tip())
1504 1520
1505 1521 def commit(ui, repo, *pats, **opts):
1506 1522 """commit changes in the queue repository"""
1507 1523 q = repo.mq
1508 1524 r = q.qrepo()
1509 1525 if not r: raise util.Abort('no queue repository')
1510 1526 commands.commit(r.ui, r, *pats, **opts)
1511 1527
1512 1528 def series(ui, repo, **opts):
1513 1529 """print the entire series file"""
1514 1530 repo.mq.qseries(repo, missing=opts['missing'], summary=opts['summary'])
1515 1531 return 0
1516 1532
1517 1533 def top(ui, repo, **opts):
1518 1534 """print the name of the current patch"""
1519 1535 q = repo.mq
1520 1536 t = len(q.applied)
1521 1537 if t:
1522 1538 return q.qseries(repo, start=t-1, length=1, status='A',
1523 1539 summary=opts.get('summary'))
1524 1540 else:
1525 1541 ui.write("No patches applied\n")
1526 1542 return 1
1527 1543
1528 1544 def next(ui, repo, **opts):
1529 1545 """print the name of the next patch"""
1530 1546 q = repo.mq
1531 1547 end = q.series_end()
1532 1548 if end == len(q.series):
1533 1549 ui.write("All patches applied\n")
1534 1550 return 1
1535 1551 return q.qseries(repo, start=end, length=1, summary=opts.get('summary'))
1536 1552
1537 1553 def prev(ui, repo, **opts):
1538 1554 """print the name of the previous patch"""
1539 1555 q = repo.mq
1540 1556 l = len(q.applied)
1541 1557 if l == 1:
1542 1558 ui.write("Only one patch applied\n")
1543 1559 return 1
1544 1560 if not l:
1545 1561 ui.write("No patches applied\n")
1546 1562 return 1
1547 1563 return q.qseries(repo, start=l-2, length=1, status='A',
1548 1564 summary=opts.get('summary'))
1549 1565
1550 1566 def new(ui, repo, patch, **opts):
1551 1567 """create a new patch
1552 1568
1553 1569 qnew creates a new patch on top of the currently-applied patch
1554 1570 (if any). It will refuse to run if there are any outstanding
1555 1571 changes unless -f is specified, in which case the patch will
1556 1572 be initialised with them.
1557 1573
1558 1574 -e, -m or -l set the patch header as well as the commit message.
1559 1575 If none is specified, the patch header is empty and the
1560 1576 commit message is 'New patch: PATCH'"""
1561 1577 q = repo.mq
1562 1578 message = commands.logmessage(opts)
1563 1579 if opts['edit']:
1564 1580 message = ui.edit(message, ui.username())
1565 1581 q.new(repo, patch, msg=message, force=opts['force'])
1566 1582 q.save_dirty()
1567 1583 return 0
1568 1584
1569 1585 def refresh(ui, repo, *pats, **opts):
1570 1586 """update the current patch
1571 1587
1572 1588 If any file patterns are provided, the refreshed patch will contain only
1573 1589 the modifications that match those patterns; the remaining modifications
1574 1590 will remain in the working directory.
1575 1591 """
1576 1592 q = repo.mq
1577 1593 message = commands.logmessage(opts)
1578 1594 if opts['edit']:
1579 1595 if message:
1580 1596 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
1581 1597 patch = q.applied[-1].name
1582 1598 (message, comment, user, date, hasdiff) = q.readheaders(patch)
1583 1599 message = ui.edit('\n'.join(message), user or ui.username())
1584 1600 ret = q.refresh(repo, pats, msg=message, **opts)
1585 1601 q.save_dirty()
1586 1602 return ret
1587 1603
1588 1604 def diff(ui, repo, *pats, **opts):
1589 1605 """diff of the current patch"""
1590 1606 repo.mq.diff(repo, pats, opts)
1591 1607 return 0
1592 1608
1593 1609 def fold(ui, repo, *files, **opts):
1594 1610 """fold the named patches into the current patch
1595 1611
1596 1612 Patches must not yet be applied. Each patch will be successively
1597 1613 applied to the current patch in the order given. If all the
1598 1614 patches apply successfully, the current patch will be refreshed
1599 1615 with the new cumulative patch, and the folded patches will
1600 1616 be deleted. With -k/--keep, the folded patch files will not
1601 1617 be removed afterwards.
1602 1618
1603 1619 The header for each folded patch will be concatenated with
1604 1620 the current patch header, separated by a line of '* * *'."""
1605 1621
1606 1622 q = repo.mq
1607 1623
1608 1624 if not files:
1609 1625 raise util.Abort(_('qfold requires at least one patch name'))
1610 1626 if not q.check_toppatch(repo):
1611 1627 raise util.Abort(_('No patches applied'))
1612 1628
1613 1629 message = commands.logmessage(opts)
1614 1630 if opts['edit']:
1615 1631 if message:
1616 1632 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
1617 1633
1618 1634 parent = q.lookup('qtip')
1619 1635 patches = []
1620 1636 messages = []
1621 1637 for f in files:
1622 1638 p = q.lookup(f)
1623 1639 if p in patches or p == parent:
1624 1640 ui.warn(_('Skipping already folded patch %s') % p)
1625 1641 if q.isapplied(p):
1626 1642 raise util.Abort(_('qfold cannot fold already applied patch %s') % p)
1627 1643 patches.append(p)
1628 1644
1629 1645 for p in patches:
1630 1646 if not message:
1631 1647 messages.append(q.readheaders(p)[0])
1632 1648 pf = q.join(p)
1633 1649 (patchsuccess, files, fuzz) = q.patch(repo, pf)
1634 1650 if not patchsuccess:
1635 1651 raise util.Abort(_('Error folding patch %s') % p)
1636 1652 patch.updatedir(ui, repo, files)
1637 1653
1638 1654 if not message:
1639 1655 message, comments, user = q.readheaders(parent)[0:3]
1640 1656 for msg in messages:
1641 1657 message.append('* * *')
1642 1658 message.extend(msg)
1643 1659 message = '\n'.join(message)
1644 1660
1645 1661 if opts['edit']:
1646 1662 message = ui.edit(message, user or ui.username())
1647 1663
1648 1664 q.refresh(repo, msg=message)
1649 1665 q.delete(repo, patches, opts)
1650 1666 q.save_dirty()
1651 1667
1652 1668 def guard(ui, repo, *args, **opts):
1653 1669 '''set or print guards for a patch
1654 1670
1655 1671 Guards control whether a patch can be pushed. A patch with no
1656 1672 guards is always pushed. A patch with a positive guard ("+foo") is
1657 1673 pushed only if the qselect command has activated it. A patch with
1658 1674 a negative guard ("-foo") is never pushed if the qselect command
1659 1675 has activated it.
1660 1676
1661 1677 With no arguments, print the currently active guards.
1662 1678 With arguments, set guards for the named patch.
1663 1679
1664 1680 To set a negative guard "-foo" on topmost patch ("--" is needed so
1665 1681 hg will not interpret "-foo" as an option):
1666 1682 hg qguard -- -foo
1667 1683
1668 1684 To set guards on another patch:
1669 1685 hg qguard other.patch +2.6.17 -stable
1670 1686 '''
1671 1687 def status(idx):
1672 1688 guards = q.series_guards[idx] or ['unguarded']
1673 1689 ui.write('%s: %s\n' % (q.series[idx], ' '.join(guards)))
1674 1690 q = repo.mq
1675 1691 patch = None
1676 1692 args = list(args)
1677 1693 if opts['list']:
1678 1694 if args or opts['none']:
1679 1695 raise util.Abort(_('cannot mix -l/--list with options or arguments'))
1680 1696 for i in xrange(len(q.series)):
1681 1697 status(i)
1682 1698 return
1683 1699 if not args or args[0][0:1] in '-+':
1684 1700 if not q.applied:
1685 1701 raise util.Abort(_('no patches applied'))
1686 1702 patch = q.applied[-1].name
1687 1703 if patch is None and args[0][0:1] not in '-+':
1688 1704 patch = args.pop(0)
1689 1705 if patch is None:
1690 1706 raise util.Abort(_('no patch to work with'))
1691 1707 if args or opts['none']:
1692 1708 q.set_guards(q.find_series(patch), args)
1693 1709 q.save_dirty()
1694 1710 else:
1695 1711 status(q.series.index(q.lookup(patch)))
1696 1712
1697 1713 def header(ui, repo, patch=None):
1698 1714 """Print the header of the topmost or specified patch"""
1699 1715 q = repo.mq
1700 1716
1701 1717 if patch:
1702 1718 patch = q.lookup(patch)
1703 1719 else:
1704 1720 if not q.applied:
1705 1721 ui.write('No patches applied\n')
1706 1722 return 1
1707 1723 patch = q.lookup('qtip')
1708 1724 message = repo.mq.readheaders(patch)[0]
1709 1725
1710 1726 ui.write('\n'.join(message) + '\n')
1711 1727
1712 1728 def lastsavename(path):
1713 1729 (directory, base) = os.path.split(path)
1714 1730 names = os.listdir(directory)
1715 1731 namere = re.compile("%s.([0-9]+)" % base)
1716 1732 maxindex = None
1717 1733 maxname = None
1718 1734 for f in names:
1719 1735 m = namere.match(f)
1720 1736 if m:
1721 1737 index = int(m.group(1))
1722 1738 if maxindex == None or index > maxindex:
1723 1739 maxindex = index
1724 1740 maxname = f
1725 1741 if maxname:
1726 1742 return (os.path.join(directory, maxname), maxindex)
1727 1743 return (None, None)
1728 1744
1729 1745 def savename(path):
1730 1746 (last, index) = lastsavename(path)
1731 1747 if last is None:
1732 1748 index = 0
1733 1749 newpath = path + ".%d" % (index + 1)
1734 1750 return newpath
1735 1751
1736 1752 def push(ui, repo, patch=None, **opts):
1737 1753 """push the next patch onto the stack"""
1738 1754 q = repo.mq
1739 1755 mergeq = None
1740 1756
1741 1757 if opts['all']:
1742 1758 if not q.series:
1743 1759 raise util.Abort(_('no patches in series'))
1744 1760 patch = q.series[-1]
1745 1761 if opts['merge']:
1746 1762 if opts['name']:
1747 1763 newpath = opts['name']
1748 1764 else:
1749 1765 newpath, i = lastsavename(q.path)
1750 1766 if not newpath:
1751 1767 ui.warn("no saved queues found, please use -n\n")
1752 1768 return 1
1753 1769 mergeq = queue(ui, repo.join(""), newpath)
1754 1770 ui.warn("merging with queue at: %s\n" % mergeq.path)
1755 1771 ret = q.push(repo, patch, force=opts['force'], list=opts['list'],
1756 1772 mergeq=mergeq)
1757 1773 q.save_dirty()
1758 1774 return ret
1759 1775
1760 1776 def pop(ui, repo, patch=None, **opts):
1761 1777 """pop the current patch off the stack"""
1762 1778 localupdate = True
1763 1779 if opts['name']:
1764 1780 q = queue(ui, repo.join(""), repo.join(opts['name']))
1765 1781 ui.warn('using patch queue: %s\n' % q.path)
1766 1782 localupdate = False
1767 1783 else:
1768 1784 q = repo.mq
1769 1785 q.pop(repo, patch, force=opts['force'], update=localupdate, all=opts['all'])
1770 1786 q.save_dirty()
1771 1787 return 0
1772 1788
1773 1789 def rename(ui, repo, patch, name=None, **opts):
1774 1790 """rename a patch
1775 1791
1776 1792 With one argument, renames the current patch to PATCH1.
1777 1793 With two arguments, renames PATCH1 to PATCH2."""
1778 1794
1779 1795 q = repo.mq
1780 1796
1781 1797 if not name:
1782 1798 name = patch
1783 1799 patch = None
1784 1800
1785 1801 if patch:
1786 1802 patch = q.lookup(patch)
1787 1803 else:
1788 1804 if not q.applied:
1789 1805 ui.write(_('No patches applied\n'))
1790 1806 return
1791 1807 patch = q.lookup('qtip')
1792 1808 absdest = q.join(name)
1793 1809 if os.path.isdir(absdest):
1794 1810 name = os.path.join(name, os.path.basename(patch))
1795 1811 absdest = q.join(name)
1796 1812 if os.path.exists(absdest):
1797 1813 raise util.Abort(_('%s already exists') % absdest)
1798 1814
1799 1815 if name in q.series:
1800 1816 raise util.Abort(_('A patch named %s already exists in the series file') % name)
1801 1817
1802 1818 if ui.verbose:
1803 1819 ui.write('Renaming %s to %s\n' % (patch, name))
1804 1820 i = q.find_series(patch)
1805 1821 guards = q.guard_re.findall(q.full_series[i])
1806 1822 q.full_series[i] = name + ''.join([' #' + g for g in guards])
1807 1823 q.parse_series()
1808 1824 q.series_dirty = 1
1809 1825
1810 1826 info = q.isapplied(patch)
1811 1827 if info:
1812 1828 q.applied[info[0]] = statusentry(info[1], name)
1813 1829 q.applied_dirty = 1
1814 1830
1815 1831 util.rename(q.join(patch), absdest)
1816 1832 r = q.qrepo()
1817 1833 if r:
1818 1834 wlock = r.wlock()
1819 1835 if r.dirstate.state(name) == 'r':
1820 1836 r.undelete([name], wlock)
1821 1837 r.copy(patch, name, wlock)
1822 1838 r.remove([patch], False, wlock)
1823 1839
1824 1840 q.save_dirty()
1825 1841
1826 1842 def restore(ui, repo, rev, **opts):
1827 1843 """restore the queue state saved by a rev"""
1828 1844 rev = repo.lookup(rev)
1829 1845 q = repo.mq
1830 1846 q.restore(repo, rev, delete=opts['delete'],
1831 1847 qupdate=opts['update'])
1832 1848 q.save_dirty()
1833 1849 return 0
1834 1850
1835 1851 def save(ui, repo, **opts):
1836 1852 """save current queue state"""
1837 1853 q = repo.mq
1838 1854 message = commands.logmessage(opts)
1839 1855 ret = q.save(repo, msg=message)
1840 1856 if ret:
1841 1857 return ret
1842 1858 q.save_dirty()
1843 1859 if opts['copy']:
1844 1860 path = q.path
1845 1861 if opts['name']:
1846 1862 newpath = os.path.join(q.basepath, opts['name'])
1847 1863 if os.path.exists(newpath):
1848 1864 if not os.path.isdir(newpath):
1849 1865 raise util.Abort(_('destination %s exists and is not '
1850 1866 'a directory') % newpath)
1851 1867 if not opts['force']:
1852 1868 raise util.Abort(_('destination %s exists, '
1853 1869 'use -f to force') % newpath)
1854 1870 else:
1855 1871 newpath = savename(path)
1856 1872 ui.warn("copy %s to %s\n" % (path, newpath))
1857 1873 util.copyfiles(path, newpath)
1858 1874 if opts['empty']:
1859 1875 try:
1860 1876 os.unlink(q.join(q.status_path))
1861 1877 except:
1862 1878 pass
1863 1879 return 0
1864 1880
1865 1881 def strip(ui, repo, rev, **opts):
1866 1882 """strip a revision and all later revs on the same branch"""
1867 1883 rev = repo.lookup(rev)
1868 1884 backup = 'all'
1869 1885 if opts['backup']:
1870 1886 backup = 'strip'
1871 1887 elif opts['nobackup']:
1872 1888 backup = 'none'
1873 1889 update = repo.dirstate.parents()[0] != revlog.nullid
1874 1890 repo.mq.strip(repo, rev, backup=backup, update=update)
1875 1891 return 0
1876 1892
1877 1893 def select(ui, repo, *args, **opts):
1878 1894 '''set or print guarded patches to push
1879 1895
1880 1896 Use the qguard command to set or print guards on patch, then use
1881 1897 qselect to tell mq which guards to use. A patch will be pushed if it
1882 1898 has no guards or any positive guards match the currently selected guard,
1883 1899 but will not be pushed if any negative guards match the current guard.
1884 1900 For example:
1885 1901
1886 1902 qguard foo.patch -stable (negative guard)
1887 1903 qguard bar.patch +stable (positive guard)
1888 1904 qselect stable
1889 1905
1890 1906 This activates the "stable" guard. mq will skip foo.patch (because
1891 1907 it has a negative match) but push bar.patch (because it
1892 1908 has a positive match).
1893 1909
1894 1910 With no arguments, prints the currently active guards.
1895 1911 With one argument, sets the active guard.
1896 1912
1897 1913 Use -n/--none to deactivate guards (no other arguments needed).
1898 1914 When no guards are active, patches with positive guards are skipped
1899 1915 and patches with negative guards are pushed.
1900 1916
1901 1917 qselect can change the guards on applied patches. It does not pop
1902 1918 guarded patches by default. Use --pop to pop back to the last applied
1903 1919 patch that is not guarded. Use --reapply (which implies --pop) to push
1904 1920 back to the current patch afterwards, but skip guarded patches.
1905 1921
1906 1922 Use -s/--series to print a list of all guards in the series file (no
1907 1923 other arguments needed). Use -v for more information.'''
1908 1924
1909 1925 q = repo.mq
1910 1926 guards = q.active()
1911 1927 if args or opts['none']:
1912 1928 old_unapplied = q.unapplied(repo)
1913 1929 old_guarded = [i for i in xrange(len(q.applied)) if
1914 1930 not q.pushable(i)[0]]
1915 1931 q.set_active(args)
1916 1932 q.save_dirty()
1917 1933 if not args:
1918 1934 ui.status(_('guards deactivated\n'))
1919 1935 if not opts['pop'] and not opts['reapply']:
1920 1936 unapplied = q.unapplied(repo)
1921 1937 guarded = [i for i in xrange(len(q.applied))
1922 1938 if not q.pushable(i)[0]]
1923 1939 if len(unapplied) != len(old_unapplied):
1924 1940 ui.status(_('number of unguarded, unapplied patches has '
1925 1941 'changed from %d to %d\n') %
1926 1942 (len(old_unapplied), len(unapplied)))
1927 1943 if len(guarded) != len(old_guarded):
1928 1944 ui.status(_('number of guarded, applied patches has changed '
1929 1945 'from %d to %d\n') %
1930 1946 (len(old_guarded), len(guarded)))
1931 1947 elif opts['series']:
1932 1948 guards = {}
1933 1949 noguards = 0
1934 1950 for gs in q.series_guards:
1935 1951 if not gs:
1936 1952 noguards += 1
1937 1953 for g in gs:
1938 1954 guards.setdefault(g, 0)
1939 1955 guards[g] += 1
1940 1956 if ui.verbose:
1941 1957 guards['NONE'] = noguards
1942 1958 guards = guards.items()
1943 1959 guards.sort(lambda a, b: cmp(a[0][1:], b[0][1:]))
1944 1960 if guards:
1945 1961 ui.note(_('guards in series file:\n'))
1946 1962 for guard, count in guards:
1947 1963 ui.note('%2d ' % count)
1948 1964 ui.write(guard, '\n')
1949 1965 else:
1950 1966 ui.note(_('no guards in series file\n'))
1951 1967 else:
1952 1968 if guards:
1953 1969 ui.note(_('active guards:\n'))
1954 1970 for g in guards:
1955 1971 ui.write(g, '\n')
1956 1972 else:
1957 1973 ui.write(_('no active guards\n'))
1958 1974 reapply = opts['reapply'] and q.applied and q.appliedname(-1)
1959 1975 popped = False
1960 1976 if opts['pop'] or opts['reapply']:
1961 1977 for i in xrange(len(q.applied)):
1962 1978 pushable, reason = q.pushable(i)
1963 1979 if not pushable:
1964 1980 ui.status(_('popping guarded patches\n'))
1965 1981 popped = True
1966 1982 if i == 0:
1967 1983 q.pop(repo, all=True)
1968 1984 else:
1969 1985 q.pop(repo, i-1)
1970 1986 break
1971 1987 if popped:
1972 1988 try:
1973 1989 if reapply:
1974 1990 ui.status(_('reapplying unguarded patches\n'))
1975 1991 q.push(repo, reapply)
1976 1992 finally:
1977 1993 q.save_dirty()
1978 1994
1979 1995 def reposetup(ui, repo):
1980 1996 class mqrepo(repo.__class__):
1981 1997 def abort_if_wdir_patched(self, errmsg, force=False):
1982 1998 if self.mq.applied and not force:
1983 1999 parent = revlog.hex(self.dirstate.parents()[0])
1984 2000 if parent in [s.rev for s in self.mq.applied]:
1985 2001 raise util.Abort(errmsg)
1986 2002
1987 2003 def commit(self, *args, **opts):
1988 2004 if len(args) >= 6:
1989 2005 force = args[5]
1990 2006 else:
1991 2007 force = opts.get('force')
1992 2008 self.abort_if_wdir_patched(
1993 2009 _('cannot commit over an applied mq patch'),
1994 2010 force)
1995 2011
1996 2012 return super(mqrepo, self).commit(*args, **opts)
1997 2013
1998 2014 def push(self, remote, force=False, revs=None):
1999 2015 if self.mq.applied and not force:
2000 2016 raise util.Abort(_('source has mq patches applied'))
2001 2017 return super(mqrepo, self).push(remote, force, revs)
2002 2018
2003 2019 def tags(self):
2004 2020 if self.tagscache:
2005 2021 return self.tagscache
2006 2022
2007 2023 tagscache = super(mqrepo, self).tags()
2008 2024
2009 2025 q = self.mq
2010 2026 if not q.applied:
2011 2027 return tagscache
2012 2028
2013 2029 mqtags = [(patch.rev, patch.name) for patch in q.applied]
2014 2030 mqtags.append((mqtags[-1][0], 'qtip'))
2015 2031 mqtags.append((mqtags[0][0], 'qbase'))
2016 2032 for patch in mqtags:
2017 2033 if patch[1] in tagscache:
2018 2034 self.ui.warn('Tag %s overrides mq patch of the same name\n' % patch[1])
2019 2035 else:
2020 2036 tagscache[patch[1]] = revlog.bin(patch[0])
2021 2037
2022 2038 return tagscache
2023 2039
2024 2040 def branchtags(self):
2025 2041 if self.branchcache != None:
2026 2042 return self.branchcache
2027 2043
2028 2044 q = self.mq
2029 2045 if not q.applied:
2030 2046 return super(mqrepo, self).branchtags()
2031 2047
2032 2048 self.branchcache = {} # avoid recursion in changectx
2033 2049 cl = self.changelog
2034 2050 partial, last, lrev = self._readbranchcache()
2035 2051
2036 2052 qbase = cl.rev(revlog.bin(q.applied[0].rev))
2037 2053 start = lrev + 1
2038 2054 if start < qbase:
2039 2055 # update the cache (excluding the patches) and save it
2040 2056 self._updatebranchcache(partial, lrev+1, qbase)
2041 2057 self._writebranchcache(partial, cl.node(qbase-1), qbase-1)
2042 2058 start = qbase
2043 2059 # if start = qbase, the cache is as updated as it should be.
2044 2060 # if start > qbase, the cache includes (part of) the patches.
2045 2061 # we might as well use it, but we won't save it.
2046 2062
2047 2063 # update the cache up to the tip
2048 2064 self._updatebranchcache(partial, start, cl.count())
2049 2065
2050 2066 self.branchcache = partial
2051 2067 return self.branchcache
2052 2068
2053 2069 if repo.local():
2054 2070 repo.__class__ = mqrepo
2055 2071 repo.mq = queue(ui, repo.join(""))
2056 2072
2057 2073 seriesopts = [('s', 'summary', None, _('print first line of patch header'))]
2058 2074
2059 2075 cmdtable = {
2060 2076 "qapplied": (applied, [] + seriesopts, 'hg qapplied [-s] [PATCH]'),
2061 2077 "qclone": (clone,
2062 2078 [('', 'pull', None, _('use pull protocol to copy metadata')),
2063 2079 ('U', 'noupdate', None, _('do not update the new working directories')),
2064 2080 ('', 'uncompressed', None,
2065 2081 _('use uncompressed transfer (fast over LAN)')),
2066 2082 ('e', 'ssh', '', _('specify ssh command to use')),
2067 2083 ('p', 'patches', '', _('location of source patch repo')),
2068 2084 ('', 'remotecmd', '',
2069 2085 _('specify hg command to run on the remote side'))],
2070 2086 'hg qclone [OPTION]... SOURCE [DEST]'),
2071 2087 "qcommit|qci":
2072 2088 (commit,
2073 2089 commands.table["^commit|ci"][1],
2074 2090 'hg qcommit [OPTION]... [FILE]...'),
2075 2091 "^qdiff": (diff,
2076 2092 [('g', 'git', None, _('use git extended diff format')),
2077 2093 ('I', 'include', [], _('include names matching the given patterns')),
2078 2094 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2079 2095 'hg qdiff [-I] [-X] [FILE]...'),
2080 2096 "qdelete|qremove|qrm":
2081 2097 (delete,
2082 2098 [('k', 'keep', None, _('keep patch file')),
2083 2099 ('r', 'rev', [], _('stop managing a revision'))],
2084 2100 'hg qdelete [-k] [-r REV]... PATCH...'),
2085 2101 'qfold':
2086 2102 (fold,
2087 2103 [('e', 'edit', None, _('edit patch header')),
2088 2104 ('k', 'keep', None, _('keep folded patch files')),
2089 2105 ('m', 'message', '', _('set patch header to <text>')),
2090 2106 ('l', 'logfile', '', _('set patch header to contents of <file>'))],
2091 2107 'hg qfold [-e] [-m <text>] [-l <file] PATCH...'),
2092 2108 'qguard': (guard, [('l', 'list', None, _('list all patches and guards')),
2093 2109 ('n', 'none', None, _('drop all guards'))],
2094 2110 'hg qguard [PATCH] [+GUARD...] [-GUARD...]'),
2095 2111 'qheader': (header, [],
2096 2112 _('hg qheader [PATCH]')),
2097 2113 "^qimport":
2098 2114 (qimport,
2099 2115 [('e', 'existing', None, 'import file in patch dir'),
2100 2116 ('n', 'name', '', 'patch file name'),
2101 2117 ('f', 'force', None, 'overwrite existing files'),
2102 2118 ('r', 'rev', [], 'place existing revisions under mq control'),
2103 2119 ('g', 'git', None, _('use git extended diff format'))],
2104 2120 'hg qimport [-e] [-n NAME] [-f] [-g] [-r REV]... FILE...'),
2105 2121 "^qinit":
2106 2122 (init,
2107 2123 [('c', 'create-repo', None, 'create queue repository')],
2108 2124 'hg qinit [-c]'),
2109 2125 "qnew":
2110 2126 (new,
2111 2127 [('e', 'edit', None, _('edit commit message')),
2112 2128 ('m', 'message', '', _('use <text> as commit message')),
2113 2129 ('l', 'logfile', '', _('read the commit message from <file>')),
2114 2130 ('f', 'force', None, _('import uncommitted changes into patch'))],
2115 2131 'hg qnew [-e] [-m TEXT] [-l FILE] [-f] PATCH'),
2116 2132 "qnext": (next, [] + seriesopts, 'hg qnext [-s]'),
2117 2133 "qprev": (prev, [] + seriesopts, 'hg qprev [-s]'),
2118 2134 "^qpop":
2119 2135 (pop,
2120 2136 [('a', 'all', None, 'pop all patches'),
2121 2137 ('n', 'name', '', 'queue name to pop'),
2122 2138 ('f', 'force', None, 'forget any local changes')],
2123 2139 'hg qpop [-a] [-n NAME] [-f] [PATCH | INDEX]'),
2124 2140 "^qpush":
2125 2141 (push,
2126 2142 [('f', 'force', None, 'apply if the patch has rejects'),
2127 2143 ('l', 'list', None, 'list patch name in commit text'),
2128 2144 ('a', 'all', None, 'apply all patches'),
2129 2145 ('m', 'merge', None, 'merge from another queue'),
2130 2146 ('n', 'name', '', 'merge queue name')],
2131 2147 'hg qpush [-f] [-l] [-a] [-m] [-n NAME] [PATCH | INDEX]'),
2132 2148 "^qrefresh":
2133 2149 (refresh,
2134 2150 [('e', 'edit', None, _('edit commit message')),
2135 2151 ('m', 'message', '', _('change commit message to <text>')),
2136 2152 ('l', 'logfile', '', _('change commit message to <file> content')),
2137 2153 ('g', 'git', None, _('use git extended diff format')),
2138 2154 ('s', 'short', None, 'refresh only files already in the patch'),
2139 2155 ('I', 'include', [], _('include names matching the given patterns')),
2140 2156 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2141 2157 'hg qrefresh [-I] [-X] [-e] [-m TEXT] [-l FILE] [-s] FILES...'),
2142 2158 'qrename|qmv':
2143 2159 (rename, [], 'hg qrename PATCH1 [PATCH2]'),
2144 2160 "qrestore":
2145 2161 (restore,
2146 2162 [('d', 'delete', None, 'delete save entry'),
2147 2163 ('u', 'update', None, 'update queue working dir')],
2148 2164 'hg qrestore [-d] [-u] REV'),
2149 2165 "qsave":
2150 2166 (save,
2151 2167 [('m', 'message', '', _('use <text> as commit message')),
2152 2168 ('l', 'logfile', '', _('read the commit message from <file>')),
2153 2169 ('c', 'copy', None, 'copy patch directory'),
2154 2170 ('n', 'name', '', 'copy directory name'),
2155 2171 ('e', 'empty', None, 'clear queue status file'),
2156 2172 ('f', 'force', None, 'force copy')],
2157 2173 'hg qsave [-m TEXT] [-l FILE] [-c] [-n NAME] [-e] [-f]'),
2158 2174 "qselect": (select,
2159 2175 [('n', 'none', None, _('disable all guards')),
2160 2176 ('s', 'series', None, _('list all guards in series file')),
2161 2177 ('', 'pop', None,
2162 2178 _('pop to before first guarded applied patch')),
2163 2179 ('', 'reapply', None, _('pop, then reapply patches'))],
2164 2180 'hg qselect [OPTION...] [GUARD...]'),
2165 2181 "qseries":
2166 2182 (series,
2167 2183 [('m', 'missing', None, 'print patches not in series')] + seriesopts,
2168 2184 'hg qseries [-ms]'),
2169 2185 "^strip":
2170 2186 (strip,
2171 2187 [('f', 'force', None, 'force multi-head removal'),
2172 2188 ('b', 'backup', None, 'bundle unrelated changesets'),
2173 2189 ('n', 'nobackup', None, 'no backups')],
2174 2190 'hg strip [-f] [-b] [-n] REV'),
2175 2191 "qtop": (top, [] + seriesopts, 'hg qtop [-s]'),
2176 2192 "qunapplied": (unapplied, [] + seriesopts, 'hg qunapplied [-s] [PATCH]'),
2177 2193 }
@@ -1,209 +1,240 b''
1 1 #!/bin/sh
2 2
3 3 echo "[extensions]" >> $HGRCPATH
4 4 echo "mq=" >> $HGRCPATH
5 5
6 6 echo % help
7 7 hg help mq
8 8
9 9 hg init a
10 10 cd a
11 11 echo a > a
12 12 hg ci -Ama
13 13
14 14 hg clone . ../k
15 15
16 16 mkdir b
17 17 echo z > b/z
18 18 hg ci -Ama
19 19
20 20 echo % qinit
21 21
22 22 hg qinit
23 23
24 24 cd ..
25 25 hg init b
26 26
27 27 echo % -R qinit
28 28
29 29 hg -R b qinit
30 30
31 31 hg init c
32 32
33 33 echo % qinit -c
34 34
35 35 hg --cwd c qinit -c
36 36 hg -R c/.hg/patches st
37 37
38 38 echo % qnew implies add
39 39
40 40 hg -R c qnew test.patch
41 41 hg -R c/.hg/patches st
42 42
43 43 cd a
44 44
45 45 echo % qnew -m
46 46
47 47 hg qnew -m 'foo bar' test.patch
48 48 cat .hg/patches/test.patch
49 49
50 50 echo % qrefresh
51 51
52 52 echo a >> a
53 53 hg qrefresh
54 54 sed -e "s/^\(diff -r \)\([a-f0-9]* \)/\1 x/" \
55 55 -e "s/\(+++ [a-zA-Z0-9_/.-]*\).*/\1/" \
56 56 -e "s/\(--- [a-zA-Z0-9_/.-]*\).*/\1/" .hg/patches/test.patch
57 57
58 58 echo % qpop
59 59
60 60 hg qpop
61 61
62 62 echo % qpush
63 63
64 64 hg qpush
65 65
66 66 cd ..
67 67
68 68 echo % pop/push outside repo
69 69
70 70 hg -R a qpop
71 71 hg -R a qpush
72 72
73 73 cd a
74 74 hg qnew test2.patch
75 75
76 76 echo % qrefresh in subdir
77 77
78 78 cd b
79 79 echo a > a
80 80 hg add a
81 81 hg qrefresh
82 82
83 83 echo % pop/push -a in subdir
84 84
85 85 hg qpop -a
86 86 hg --traceback qpush -a
87 87
88 88 echo % qseries
89 89 hg qseries
90 90 hg qpop
91 91 hg qseries -vs
92 92 hg qpush
93 93
94 94 echo % qapplied
95 95 hg qapplied
96 96
97 97 echo % qtop
98 98 hg qtop
99 99
100 100 echo % qprev
101 101 hg qprev
102 102
103 103 echo % qnext
104 104 hg qnext
105 105
106 106 echo % pop, qnext, qprev, qapplied
107 107 hg qpop
108 108 hg qnext
109 109 hg qprev
110 110 hg qapplied
111 111
112 112 echo % commit should fail
113 113 hg commit
114 114
115 115 echo % push should fail
116 116 hg push ../../k
117 117
118 118 echo % qunapplied
119 119 hg qunapplied
120 120
121 121 echo % qpush/qpop with index
122 122 hg qnew test1b.patch
123 123 echo 1b > 1b
124 124 hg add 1b
125 125 hg qrefresh
126 126 hg qpush 2
127 127 hg qpop 0
128 128 hg qpush test.patch+1
129 129 hg qpush test.patch+2
130 130 hg qpop test2.patch-1
131 131 hg qpop test2.patch-2
132 132 hg qpush test1b.patch+1
133 133
134 134 echo % push should succeed
135 135 hg qpop -a
136 136 hg push ../../k
137 137
138 138 echo % strip
139 139 cd ../../b
140 140 echo x>x
141 141 hg ci -Ama
142 142 hg strip tip 2>&1 | sed 's/\(saving bundle to \).*/\1/'
143 143 hg unbundle .hg/strip-backup/*
144 144
145 145 echo '% cd b; hg qrefresh'
146 146 hg init refresh
147 147 cd refresh
148 148 echo a > a
149 149 hg ci -Ama -d'0 0'
150 150 hg qnew -mfoo foo
151 151 echo a >> a
152 152 hg qrefresh
153 153 mkdir b
154 154 cd b
155 155 echo f > f
156 156 hg add f
157 157 hg qrefresh
158 158 sed -e "s/\(+++ [a-zA-Z0-9_/.-]*\).*/\1/" \
159 159 -e "s/\(--- [a-zA-Z0-9_/.-]*\).*/\1/" ../.hg/patches/foo
160 160 echo % hg qrefresh .
161 161 hg qrefresh .
162 162 sed -e "s/\(+++ [a-zA-Z0-9_/.-]*\).*/\1/" \
163 163 -e "s/\(--- [a-zA-Z0-9_/.-]*\).*/\1/" ../.hg/patches/foo
164 164 hg status
165 165
166 166 echo % qpush failure
167 167 cd ..
168 168 hg qrefresh
169 169 hg qnew -mbar bar
170 170 echo foo > foo
171 171 echo bar > bar
172 172 hg add foo bar
173 173 hg qrefresh
174 174 hg qpop -a
175 175 echo bar > foo
176 176 hg qpush -a
177 177 hg st
178 178
179 179 cat >>$HGRCPATH <<EOF
180 180 [diff]
181 181 git = True
182 182 EOF
183 183 cd ..
184 184 hg init git
185 185 cd git
186 186 hg qinit
187 187
188 188 hg qnew -m'new file' new
189 189 echo foo > new
190 190 chmod +x new
191 191 hg add new
192 192 hg qrefresh
193 193 sed -e "s/\(+++ [a-zA-Z0-9_/.-]*\).*/\1/" \
194 194 -e "s/\(--- [a-zA-Z0-9_/.-]*\).*/\1/" .hg/patches/new
195 195
196 196 hg qnew -m'copy file' copy
197 197 hg cp new copy
198 198 hg qrefresh
199 199 sed -e "s/\(+++ [a-zA-Z0-9_/.-]*\).*/\1/" \
200 200 -e "s/\(--- [a-zA-Z0-9_/.-]*\).*/\1/" .hg/patches/copy
201 201
202 202 hg qpop
203 203 hg qpush
204 204 hg qdiff
205 205 cat >>$HGRCPATH <<EOF
206 206 [diff]
207 207 git = False
208 208 EOF
209 209 hg qdiff --git
210
211 cd ..
212 hg init slow
213 cd slow
214 hg qinit
215 echo foo > foo
216 hg add foo
217 hg ci -m 'add foo'
218 hg qnew bar
219 echo bar > bar
220 hg add bar
221 hg mv foo baz
222 hg qrefresh --git
223 hg up -C 0
224 echo >> foo
225 hg ci -m 'change foo'
226 hg up -C 1
227 hg qrefresh --git 2>&1 | grep -v 'saving bundle'
228 cat .hg/patches/bar
229 hg log -vC --template '{rev} {file_copies%filecopy}\n' -r .
230
231 echo
232 hg up -C 1
233 echo >> foo
234 hg ci -m 'change foo again'
235 hg up -C 2
236 hg mv bar quux
237 hg mv baz bleh
238 hg qrefresh --git 2>&1 | grep -v 'saving bundle'
239 cat .hg/patches/bar
240 hg log -vC --template '{rev} {file_copies%filecopy}\n' -r .
@@ -1,203 +1,246 b''
1 1 % help
2 2 mq extension - patch management and development
3 3
4 4 This extension lets you work with a stack of patches in a Mercurial
5 5 repository. It manages two stacks of patches - all known patches, and
6 6 applied patches (subset of known patches).
7 7
8 8 Known patches are represented as patch files in the .hg/patches
9 9 directory. Applied patches are both patch files and changesets.
10 10
11 11 Common tasks (use "hg help command" for more details):
12 12
13 13 prepare repository to work with patches qinit
14 14 create new patch qnew
15 15 import existing patch qimport
16 16
17 17 print patch series qseries
18 18 print applied patches qapplied
19 19 print name of top applied patch qtop
20 20
21 21 add known patch to applied stack qpush
22 22 remove patch from applied stack qpop
23 23 refresh contents of top applied patch qrefresh
24 24
25 25 list of commands (use "hg help -v mq" to show aliases and global options):
26 26
27 27 qapplied print the patches already applied
28 28 qclone clone main and patch repository at same time
29 29 qcommit commit changes in the queue repository
30 30 qdelete remove patches from queue
31 31 qdiff diff of the current patch
32 32 qfold fold the named patches into the current patch
33 33 qguard set or print guards for a patch
34 34 qheader Print the header of the topmost or specified patch
35 35 qimport import a patch
36 36 qinit init a new queue repository
37 37 qnew create a new patch
38 38 qnext print the name of the next patch
39 39 qpop pop the current patch off the stack
40 40 qprev print the name of the previous patch
41 41 qpush push the next patch onto the stack
42 42 qrefresh update the current patch
43 43 qrename rename a patch
44 44 qrestore restore the queue state saved by a rev
45 45 qsave save current queue state
46 46 qselect set or print guarded patches to push
47 47 qseries print the entire series file
48 48 qtop print the name of the current patch
49 49 qunapplied print the patches not yet applied
50 50 strip strip a revision and all later revs on the same branch
51 51 adding a
52 52 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
53 53 adding b/z
54 54 % qinit
55 55 % -R qinit
56 56 % qinit -c
57 57 A .hgignore
58 58 A series
59 59 % qnew implies add
60 60 A .hgignore
61 61 A series
62 62 A test.patch
63 63 % qnew -m
64 64 foo bar
65 65 % qrefresh
66 66 foo bar
67 67
68 68 diff -r xa
69 69 --- a/a
70 70 +++ b/a
71 71 @@ -1,1 +1,2 @@ a
72 72 a
73 73 +a
74 74 % qpop
75 75 Patch queue now empty
76 76 % qpush
77 77 applying test.patch
78 78 Now at: test.patch
79 79 % pop/push outside repo
80 80 Patch queue now empty
81 81 applying test.patch
82 82 Now at: test.patch
83 83 % qrefresh in subdir
84 84 % pop/push -a in subdir
85 85 Patch queue now empty
86 86 applying test.patch
87 87 applying test2.patch
88 88 Now at: test2.patch
89 89 % qseries
90 90 test.patch
91 91 test2.patch
92 92 Now at: test.patch
93 93 0 A test.patch: foo bar
94 94 1 U test2.patch:
95 95 applying test2.patch
96 96 Now at: test2.patch
97 97 % qapplied
98 98 test.patch
99 99 test2.patch
100 100 % qtop
101 101 test2.patch
102 102 % qprev
103 103 test.patch
104 104 % qnext
105 105 All patches applied
106 106 % pop, qnext, qprev, qapplied
107 107 Now at: test.patch
108 108 test2.patch
109 109 Only one patch applied
110 110 test.patch
111 111 % commit should fail
112 112 abort: cannot commit over an applied mq patch
113 113 % push should fail
114 114 pushing to ../../k
115 115 abort: source has mq patches applied
116 116 % qunapplied
117 117 test2.patch
118 118 % qpush/qpop with index
119 119 applying test2.patch
120 120 Now at: test2.patch
121 121 Now at: test.patch
122 122 applying test1b.patch
123 123 Now at: test1b.patch
124 124 applying test2.patch
125 125 Now at: test2.patch
126 126 Now at: test1b.patch
127 127 Now at: test.patch
128 128 applying test1b.patch
129 129 applying test2.patch
130 130 Now at: test2.patch
131 131 % push should succeed
132 132 Patch queue now empty
133 133 pushing to ../../k
134 134 searching for changes
135 135 adding changesets
136 136 adding manifests
137 137 adding file changes
138 138 added 1 changesets with 1 changes to 1 files
139 139 % strip
140 140 adding x
141 141 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
142 142 saving bundle to
143 143 adding changesets
144 144 adding manifests
145 145 adding file changes
146 146 added 1 changesets with 1 changes to 1 files
147 147 (run 'hg update' to get a working copy)
148 148 % cd b; hg qrefresh
149 149 adding a
150 150 foo
151 151
152 152 diff -r cb9a9f314b8b a
153 153 --- a/a
154 154 +++ b/a
155 155 @@ -1,1 +1,2 @@ a
156 156 a
157 157 +a
158 158 diff -r cb9a9f314b8b b/f
159 159 --- /dev/null
160 160 +++ b/b/f
161 161 @@ -0,0 +1,1 @@
162 162 +f
163 163 % hg qrefresh .
164 164 foo
165 165
166 166 diff -r cb9a9f314b8b b/f
167 167 --- /dev/null
168 168 +++ b/b/f
169 169 @@ -0,0 +1,1 @@
170 170 +f
171 171 M a
172 172 % qpush failure
173 173 Patch queue now empty
174 174 applying foo
175 175 applying bar
176 176 1 out of 1 hunk ignored -- saving rejects to file foo.rej
177 177 patch failed, unable to continue (try -v)
178 178 patch failed, rejects left in working dir
179 179 Errors during apply, please fix and refresh bar
180 180 ? foo
181 181 ? foo.rej
182 182 new file
183 183
184 184 diff --git a/new b/new
185 185 new file mode 100755
186 186 --- /dev/null
187 187 +++ b/new
188 188 @@ -0,0 +1,1 @@
189 189 +foo
190 190 copy file
191 191
192 192 diff --git a/new b/copy
193 193 copy from new
194 194 copy to copy
195 195 Now at: new
196 196 applying copy
197 197 Now at: copy
198 198 diff --git a/new b/copy
199 199 copy from new
200 200 copy to copy
201 201 diff --git a/new b/copy
202 202 copy from new
203 203 copy to copy
204 1 files updated, 0 files merged, 2 files removed, 0 files unresolved
205 2 files updated, 0 files merged, 1 files removed, 0 files unresolved
206 adding branch
207 adding changesets
208 adding manifests
209 adding file changes
210 added 1 changesets with 1 changes to 1 files
211 (run 'hg update' to get a working copy)
212 Patch queue now empty
213 applying bar
214 Now at: bar
215 diff --git a/bar b/bar
216 new file mode 100644
217 --- /dev/null
218 +++ b/bar
219 @@ -0,0 +1,1 @@
220 +bar
221 diff --git a/foo b/baz
222 rename from foo
223 rename to baz
224 2 baz (foo)
225
226 1 files updated, 0 files merged, 2 files removed, 0 files unresolved
227 2 files updated, 0 files merged, 1 files removed, 0 files unresolved
228 adding branch
229 adding changesets
230 adding manifests
231 adding file changes
232 added 1 changesets with 1 changes to 1 files
233 (run 'hg update' to get a working copy)
234 Patch queue now empty
235 applying bar
236 Now at: bar
237 diff --git a/foo b/bleh
238 rename from foo
239 rename to bleh
240 diff --git a/quux b/quux
241 new file mode 100644
242 --- /dev/null
243 +++ b/quux
244 @@ -0,0 +1,1 @@
245 +bar
246 3 bleh (foo)
General Comments 0
You need to be logged in to leave comments. Login now