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