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