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