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