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