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