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