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