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