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