##// END OF EJS Templates
mq: strip should not blow away local changes...
Chris Mason -
r2699:f8bcaf56 default
parent child Browse files
Show More
@@ -1,1461 +1,1464 b''
1 1 # queue.py - patch queues for mercurial
2 2 #
3 3 # Copyright 2005 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.demandload import *
33 33 demandload(globals(), "os sys re struct traceback errno bz2")
34 34 from mercurial.i18n import gettext as _
35 35 from mercurial import ui, hg, revlog, commands, util
36 36
37 37 versionstr = "0.45"
38 38
39 39 repomap = {}
40 40
41 41 commands.norepo += " qversion"
42 42 class queue:
43 43 def __init__(self, ui, path, patchdir=None):
44 44 self.basepath = path
45 45 if patchdir:
46 46 self.path = patchdir
47 47 else:
48 48 self.path = os.path.join(path, "patches")
49 49 self.opener = util.opener(self.path)
50 50 self.ui = ui
51 51 self.applied = []
52 52 self.full_series = []
53 53 self.applied_dirty = 0
54 54 self.series_dirty = 0
55 55 self.series_path = "series"
56 56 self.status_path = "status"
57 57
58 58 if os.path.exists(os.path.join(self.path, self.series_path)):
59 59 self.full_series = self.opener(self.series_path).read().splitlines()
60 60 self.read_series(self.full_series)
61 61
62 62 if os.path.exists(os.path.join(self.path, self.status_path)):
63 63 self.applied = self.opener(self.status_path).read().splitlines()
64 64
65 65 def find_series(self, patch):
66 66 pre = re.compile("(\s*)([^#]+)")
67 67 index = 0
68 68 for l in self.full_series:
69 69 m = pre.match(l)
70 70 if m:
71 71 s = m.group(2)
72 72 s = s.rstrip()
73 73 if s == patch:
74 74 return index
75 75 index += 1
76 76 return None
77 77
78 78 def read_series(self, list):
79 79 def matcher(list):
80 80 pre = re.compile("(\s*)([^#]+)")
81 81 for l in list:
82 82 m = pre.match(l)
83 83 if m:
84 84 s = m.group(2)
85 85 s = s.rstrip()
86 86 if len(s) > 0:
87 87 yield s
88 88 self.series = []
89 89 self.series = [ x for x in matcher(list) ]
90 90
91 91 def save_dirty(self):
92 92 if self.applied_dirty:
93 93 if len(self.applied) > 0:
94 94 nl = "\n"
95 95 else:
96 96 nl = ""
97 97 f = self.opener(self.status_path, "w")
98 98 f.write("\n".join(self.applied) + nl)
99 99 if self.series_dirty:
100 100 if len(self.full_series) > 0:
101 101 nl = "\n"
102 102 else:
103 103 nl = ""
104 104 f = self.opener(self.series_path, "w")
105 105 f.write("\n".join(self.full_series) + nl)
106 106
107 107 def readheaders(self, patch):
108 108 def eatdiff(lines):
109 109 while lines:
110 110 l = lines[-1]
111 111 if (l.startswith("diff -") or
112 112 l.startswith("Index:") or
113 113 l.startswith("===========")):
114 114 del lines[-1]
115 115 else:
116 116 break
117 117 def eatempty(lines):
118 118 while lines:
119 119 l = lines[-1]
120 120 if re.match('\s*$', l):
121 121 del lines[-1]
122 122 else:
123 123 break
124 124
125 125 pf = os.path.join(self.path, patch)
126 126 message = []
127 127 comments = []
128 128 user = None
129 129 date = None
130 130 format = None
131 131 subject = None
132 132 diffstart = 0
133 133
134 134 for line in file(pf):
135 135 line = line.rstrip()
136 136 if diffstart:
137 137 if line.startswith('+++ '):
138 138 diffstart = 2
139 139 break
140 140 if line.startswith("--- "):
141 141 diffstart = 1
142 142 continue
143 143 elif format == "hgpatch":
144 144 # parse values when importing the result of an hg export
145 145 if line.startswith("# User "):
146 146 user = line[7:]
147 147 elif line.startswith("# Date "):
148 148 date = line[7:]
149 149 elif not line.startswith("# ") and line:
150 150 message.append(line)
151 151 format = None
152 152 elif line == '# HG changeset patch':
153 153 format = "hgpatch"
154 154 elif (format != "tagdone" and (line.startswith("Subject: ") or
155 155 line.startswith("subject: "))):
156 156 subject = line[9:]
157 157 format = "tag"
158 158 elif (format != "tagdone" and (line.startswith("From: ") or
159 159 line.startswith("from: "))):
160 160 user = line[6:]
161 161 format = "tag"
162 162 elif format == "tag" and line == "":
163 163 # when looking for tags (subject: from: etc) they
164 164 # end once you find a blank line in the source
165 165 format = "tagdone"
166 166 elif message or line:
167 167 message.append(line)
168 168 comments.append(line)
169 169
170 170 eatdiff(message)
171 171 eatdiff(comments)
172 172 eatempty(message)
173 173 eatempty(comments)
174 174
175 175 # make sure message isn't empty
176 176 if format and format.startswith("tag") and subject:
177 177 message.insert(0, "")
178 178 message.insert(0, subject)
179 179 return (message, comments, user, date, diffstart > 1)
180 180
181 181 def mergeone(self, repo, mergeq, head, patch, rev, wlock):
182 182 # first try just applying the patch
183 183 (err, n) = self.apply(repo, [ patch ], update_status=False,
184 184 strict=True, merge=rev, wlock=wlock)
185 185
186 186 if err == 0:
187 187 return (err, n)
188 188
189 189 if n is None:
190 190 self.ui.warn("apply failed for patch %s\n" % patch)
191 191 sys.exit(1)
192 192
193 193 self.ui.warn("patch didn't work out, merging %s\n" % patch)
194 194
195 195 # apply failed, strip away that rev and merge.
196 196 repo.update(head, allow=False, force=True, wlock=wlock)
197 197 self.strip(repo, n, update=False, backup='strip', wlock=wlock)
198 198
199 199 c = repo.changelog.read(rev)
200 200 ret = repo.update(rev, allow=True, wlock=wlock)
201 201 if ret:
202 202 self.ui.warn("update returned %d\n" % ret)
203 203 sys.exit(1)
204 204 n = repo.commit(None, c[4], c[1], force=1, wlock=wlock)
205 205 if n == None:
206 206 self.ui.warn("repo commit failed\n")
207 207 sys.exit(1)
208 208 try:
209 209 message, comments, user, date, patchfound = mergeq.readheaders(patch)
210 210 except:
211 211 self.ui.warn("Unable to read %s\n" % patch)
212 212 sys.exit(1)
213 213
214 214 patchf = self.opener(patch, "w")
215 215 if comments:
216 216 comments = "\n".join(comments) + '\n\n'
217 217 patchf.write(comments)
218 218 commands.dodiff(patchf, self.ui, repo, head, n)
219 219 patchf.close()
220 220 return (0, n)
221 221
222 222 def qparents(self, repo, rev=None):
223 223 if rev is None:
224 224 (p1, p2) = repo.dirstate.parents()
225 225 if p2 == revlog.nullid:
226 226 return p1
227 227 if len(self.applied) == 0:
228 228 return None
229 229 (top, patch) = self.applied[-1].split(':')
230 230 top = revlog.bin(top)
231 231 return top
232 232 pp = repo.changelog.parents(rev)
233 233 if pp[1] != revlog.nullid:
234 234 arevs = [ x.split(':')[0] for x in self.applied ]
235 235 p0 = revlog.hex(pp[0])
236 236 p1 = revlog.hex(pp[1])
237 237 if p0 in arevs:
238 238 return pp[0]
239 239 if p1 in arevs:
240 240 return pp[1]
241 241 return pp[0]
242 242
243 243 def mergepatch(self, repo, mergeq, series, wlock):
244 244 if len(self.applied) == 0:
245 245 # each of the patches merged in will have two parents. This
246 246 # can confuse the qrefresh, qdiff, and strip code because it
247 247 # needs to know which parent is actually in the patch queue.
248 248 # so, we insert a merge marker with only one parent. This way
249 249 # the first patch in the queue is never a merge patch
250 250 #
251 251 pname = ".hg.patches.merge.marker"
252 252 n = repo.commit(None, '[mq]: merge marker', user=None, force=1,
253 253 wlock=wlock)
254 254 self.applied.append(revlog.hex(n) + ":" + pname)
255 255 self.applied_dirty = 1
256 256
257 257 head = self.qparents(repo)
258 258
259 259 for patch in series:
260 260 patch = mergeq.lookup(patch, strict=True)
261 261 if not patch:
262 262 self.ui.warn("patch %s does not exist\n" % patch)
263 263 return (1, None)
264 264
265 265 info = mergeq.isapplied(patch)
266 266 if not info:
267 267 self.ui.warn("patch %s is not applied\n" % patch)
268 268 return (1, None)
269 269 rev = revlog.bin(info[1])
270 270 (err, head) = self.mergeone(repo, mergeq, head, patch, rev, wlock)
271 271 if head:
272 272 self.applied.append(revlog.hex(head) + ":" + patch)
273 273 self.applied_dirty = 1
274 274 if err:
275 275 return (err, head)
276 276 return (0, head)
277 277
278 278 def apply(self, repo, series, list=False, update_status=True,
279 279 strict=False, patchdir=None, merge=None, wlock=None):
280 280 # TODO unify with commands.py
281 281 if not patchdir:
282 282 patchdir = self.path
283 283 pwd = os.getcwd()
284 284 os.chdir(repo.root)
285 285 err = 0
286 286 if not wlock:
287 287 wlock = repo.wlock()
288 288 lock = repo.lock()
289 289 tr = repo.transaction()
290 290 n = None
291 291 for patch in series:
292 292 self.ui.warn("applying %s\n" % patch)
293 293 pf = os.path.join(patchdir, patch)
294 294
295 295 try:
296 296 message, comments, user, date, patchfound = self.readheaders(patch)
297 297 except:
298 298 self.ui.warn("Unable to read %s\n" % pf)
299 299 err = 1
300 300 break
301 301
302 302 if not message:
303 303 message = "imported patch %s\n" % patch
304 304 else:
305 305 if list:
306 306 message.append("\nimported patch %s" % patch)
307 307 message = '\n'.join(message)
308 308
309 309 try:
310 310 pp = util.find_in_path('gpatch', os.environ.get('PATH', ''), 'patch')
311 311 f = os.popen("%s -p1 --no-backup-if-mismatch < '%s'" % (pp, pf))
312 312 except:
313 313 self.ui.warn("patch failed, unable to continue (try -v)\n")
314 314 err = 1
315 315 break
316 316 files = []
317 317 fuzz = False
318 318 for l in f:
319 319 l = l.rstrip('\r\n');
320 320 if self.ui.verbose:
321 321 self.ui.warn(l + "\n")
322 322 if l[:14] == 'patching file ':
323 323 pf = os.path.normpath(l[14:])
324 324 # when patch finds a space in the file name, it puts
325 325 # single quotes around the filename. strip them off
326 326 if pf[0] == "'" and pf[-1] == "'":
327 327 pf = pf[1:-1]
328 328 if pf not in files:
329 329 files.append(pf)
330 330 printed_file = False
331 331 file_str = l
332 332 elif l.find('with fuzz') >= 0:
333 333 if not printed_file:
334 334 self.ui.warn(file_str + '\n')
335 335 printed_file = True
336 336 self.ui.warn(l + '\n')
337 337 fuzz = True
338 338 elif l.find('saving rejects to file') >= 0:
339 339 self.ui.warn(l + '\n')
340 340 elif l.find('FAILED') >= 0:
341 341 if not printed_file:
342 342 self.ui.warn(file_str + '\n')
343 343 printed_file = True
344 344 self.ui.warn(l + '\n')
345 345 patcherr = f.close()
346 346
347 347 if merge and len(files) > 0:
348 348 # Mark as merged and update dirstate parent info
349 349 repo.dirstate.update(repo.dirstate.filterfiles(files), 'm')
350 350 p1, p2 = repo.dirstate.parents()
351 351 repo.dirstate.setparents(p1, merge)
352 352 if len(files) > 0:
353 353 commands.addremove_lock(self.ui, repo, files,
354 354 opts={}, wlock=wlock)
355 355 n = repo.commit(files, message, user, date, force=1, lock=lock,
356 356 wlock=wlock)
357 357
358 358 if n == None:
359 359 self.ui.warn("repo commit failed\n")
360 360 sys.exit(1)
361 361
362 362 if update_status:
363 363 self.applied.append(revlog.hex(n) + ":" + patch)
364 364
365 365 if patcherr:
366 366 if not patchfound:
367 367 self.ui.warn("patch %s is empty\n" % patch)
368 368 err = 0
369 369 else:
370 370 self.ui.warn("patch failed, rejects left in working dir\n")
371 371 err = 1
372 372 break
373 373
374 374 if fuzz and strict:
375 375 self.ui.warn("fuzz found when applying patch, stopping\n")
376 376 err = 1
377 377 break
378 378 tr.close()
379 379 os.chdir(pwd)
380 380 return (err, n)
381 381
382 382 def delete(self, repo, patch):
383 383 patch = self.lookup(patch, strict=True)
384 384 info = self.isapplied(patch)
385 385 if info:
386 386 self.ui.warn("cannot delete applied patch %s\n" % patch)
387 387 sys.exit(1)
388 388 if patch not in self.series:
389 389 self.ui.warn("patch %s not in series file\n" % patch)
390 390 sys.exit(1)
391 391 i = self.find_series(patch)
392 392 del self.full_series[i]
393 393 self.read_series(self.full_series)
394 394 self.series_dirty = 1
395 395
396 396 def check_toppatch(self, repo):
397 397 if len(self.applied) > 0:
398 398 (top, patch) = self.applied[-1].split(':')
399 399 top = revlog.bin(top)
400 400 pp = repo.dirstate.parents()
401 401 if top not in pp:
402 402 self.ui.warn("queue top not at dirstate parents. top %s dirstate %s %s\n" %( revlog.short(top), revlog.short(pp[0]), revlog.short(pp[1])))
403 403 sys.exit(1)
404 404 return top
405 405 return None
406 406 def check_localchanges(self, repo):
407 407 (c, a, r, d, u) = repo.changes(None, None)
408 408 if c or a or d or r:
409 409 self.ui.write("Local changes found, refresh first\n")
410 410 sys.exit(1)
411 411 def new(self, repo, patch, msg=None, force=None):
412 412 commitfiles = []
413 413 (c, a, r, d, u) = repo.changes(None, None)
414 414 if c or a or d or r:
415 415 if not force:
416 416 raise util.Abort(_("Local changes found, refresh first"))
417 417 else:
418 418 commitfiles = c + a + r
419 419 self.check_toppatch(repo)
420 420 wlock = repo.wlock()
421 421 insert = self.full_series_end()
422 422 if msg:
423 423 n = repo.commit(commitfiles, "[mq]: %s" % msg, force=True,
424 424 wlock=wlock)
425 425 else:
426 426 n = repo.commit(commitfiles,
427 427 "New patch: %s" % patch, force=True, wlock=wlock)
428 428 if n == None:
429 429 self.ui.warn("repo commit failed\n")
430 430 sys.exit(1)
431 431 self.full_series[insert:insert] = [patch]
432 432 self.applied.append(revlog.hex(n) + ":" + patch)
433 433 self.read_series(self.full_series)
434 434 self.series_dirty = 1
435 435 self.applied_dirty = 1
436 436 p = self.opener(patch, "w")
437 437 if msg:
438 438 msg = msg + "\n"
439 439 p.write(msg)
440 440 p.close()
441 441 wlock = None
442 442 r = self.qrepo()
443 443 if r: r.add([patch])
444 444 if commitfiles:
445 445 self.refresh(repo, msg=None, short=True)
446 446
447 447 def strip(self, repo, rev, update=True, backup="all", wlock=None):
448 448 def limitheads(chlog, stop):
449 449 """return the list of all nodes that have no children"""
450 450 p = {}
451 451 h = []
452 452 stoprev = 0
453 453 if stop in chlog.nodemap:
454 454 stoprev = chlog.rev(stop)
455 455
456 456 for r in range(chlog.count() - 1, -1, -1):
457 457 n = chlog.node(r)
458 458 if n not in p:
459 459 h.append(n)
460 460 if n == stop:
461 461 break
462 462 if r < stoprev:
463 463 break
464 464 for pn in chlog.parents(n):
465 465 p[pn] = 1
466 466 return h
467 467
468 468 def bundle(cg):
469 469 backupdir = repo.join("strip-backup")
470 470 if not os.path.isdir(backupdir):
471 471 os.mkdir(backupdir)
472 472 name = os.path.join(backupdir, "%s" % revlog.short(rev))
473 473 name = savename(name)
474 474 self.ui.warn("saving bundle to %s\n" % name)
475 475 # TODO, exclusive open
476 476 f = open(name, "wb")
477 477 try:
478 478 f.write("HG10")
479 479 z = bz2.BZ2Compressor(9)
480 480 while 1:
481 481 chunk = cg.read(4096)
482 482 if not chunk:
483 483 break
484 484 f.write(z.compress(chunk))
485 485 f.write(z.flush())
486 486 except:
487 487 os.unlink(name)
488 488 raise
489 489 f.close()
490 490 return name
491 491
492 492 def stripall(rev, revnum):
493 493 cl = repo.changelog
494 494 c = cl.read(rev)
495 495 mm = repo.manifest.read(c[0])
496 496 seen = {}
497 497
498 498 for x in xrange(revnum, cl.count()):
499 499 c = cl.read(cl.node(x))
500 500 for f in c[3]:
501 501 if f in seen:
502 502 continue
503 503 seen[f] = 1
504 504 if f in mm:
505 505 filerev = mm[f]
506 506 else:
507 507 filerev = 0
508 508 seen[f] = filerev
509 509 # we go in two steps here so the strip loop happens in a
510 510 # sensible order. When stripping many files, this helps keep
511 511 # our disk access patterns under control.
512 512 list = seen.keys()
513 513 list.sort()
514 514 for f in list:
515 515 ff = repo.file(f)
516 516 filerev = seen[f]
517 517 if filerev != 0:
518 518 if filerev in ff.nodemap:
519 519 filerev = ff.rev(filerev)
520 520 else:
521 521 filerev = 0
522 522 ff.strip(filerev, revnum)
523 523
524 524 if not wlock:
525 525 wlock = repo.wlock()
526 526 lock = repo.lock()
527 527 chlog = repo.changelog
528 528 # TODO delete the undo files, and handle undo of merge sets
529 529 pp = chlog.parents(rev)
530 530 revnum = chlog.rev(rev)
531 531
532 532 if update:
533 (c, a, r, d, u) = repo.changes(None, None)
534 if c or a or d or r:
535 raise util.Abort(_("Local changes found"))
533 536 urev = self.qparents(repo, rev)
534 537 repo.update(urev, allow=False, force=True, wlock=wlock)
535 538 repo.dirstate.write()
536 539
537 540 # save is a list of all the branches we are truncating away
538 541 # that we actually want to keep. changegroup will be used
539 542 # to preserve them and add them back after the truncate
540 543 saveheads = []
541 544 savebases = {}
542 545
543 546 tip = chlog.tip()
544 547 heads = limitheads(chlog, rev)
545 548 seen = {}
546 549
547 550 # search through all the heads, finding those where the revision
548 551 # we want to strip away is an ancestor. Also look for merges
549 552 # that might be turned into new heads by the strip.
550 553 while heads:
551 554 h = heads.pop()
552 555 n = h
553 556 while True:
554 557 seen[n] = 1
555 558 pp = chlog.parents(n)
556 559 if pp[1] != revlog.nullid and chlog.rev(pp[1]) > revnum:
557 560 if pp[1] not in seen:
558 561 heads.append(pp[1])
559 562 if pp[0] == revlog.nullid:
560 563 break
561 564 if chlog.rev(pp[0]) < revnum:
562 565 break
563 566 n = pp[0]
564 567 if n == rev:
565 568 break
566 569 r = chlog.reachable(h, rev)
567 570 if rev not in r:
568 571 saveheads.append(h)
569 572 for x in r:
570 573 if chlog.rev(x) > revnum:
571 574 savebases[x] = 1
572 575
573 576 # create a changegroup for all the branches we need to keep
574 577 if backup is "all":
575 578 backupch = repo.changegroupsubset([rev], chlog.heads(), 'strip')
576 579 bundle(backupch)
577 580 if saveheads:
578 581 backupch = repo.changegroupsubset(savebases.keys(), saveheads, 'strip')
579 582 chgrpfile = bundle(backupch)
580 583
581 584 stripall(rev, revnum)
582 585
583 586 change = chlog.read(rev)
584 587 repo.manifest.strip(repo.manifest.rev(change[0]), revnum)
585 588 chlog.strip(revnum, revnum)
586 589 if saveheads:
587 590 self.ui.status("adding branch\n")
588 591 commands.unbundle(self.ui, repo, chgrpfile, update=False)
589 592 if backup is not "strip":
590 593 os.unlink(chgrpfile)
591 594
592 595 def isapplied(self, patch):
593 596 """returns (index, rev, patch)"""
594 597 for i in xrange(len(self.applied)):
595 598 p = self.applied[i]
596 599 a = p.split(':')
597 600 if a[1] == patch:
598 601 return (i, a[0], a[1])
599 602 return None
600 603
601 604 # if the exact patch name does not exist, we try a few
602 605 # variations. If strict is passed, we try only #1
603 606 #
604 607 # 1) a number to indicate an offset in the series file
605 608 # 2) a unique substring of the patch name was given
606 609 # 3) patchname[-+]num to indicate an offset in the series file
607 610 def lookup(self, patch, strict=False):
608 611 def partial_name(s):
609 612 count = 0
610 613 if s in self.series:
611 614 return s
612 615 for x in self.series:
613 616 if s in x:
614 617 count += 1
615 618 last = x
616 619 if count > 1:
617 620 return None
618 621 if count:
619 622 return last
620 623 if len(self.series) > 0 and len(self.applied) > 0:
621 624 if s == 'qtip':
622 625 return self.series[self.series_end()-1]
623 626 if s == 'qbase':
624 627 return self.series[0]
625 628 return None
626 629 if patch == None:
627 630 return None
628 631
629 632 # we don't want to return a partial match until we make
630 633 # sure the file name passed in does not exist (checked below)
631 634 res = partial_name(patch)
632 635 if res and res == patch:
633 636 return res
634 637
635 638 if not os.path.isfile(os.path.join(self.path, patch)):
636 639 try:
637 640 sno = int(patch)
638 641 except(ValueError, OverflowError):
639 642 pass
640 643 else:
641 644 if sno < len(self.series):
642 645 patch = self.series[sno]
643 646 return patch
644 647 if not strict:
645 648 # return any partial match made above
646 649 if res:
647 650 return res
648 651 minus = patch.rsplit('-', 1)
649 652 if len(minus) > 1:
650 653 res = partial_name(minus[0])
651 654 if res:
652 655 i = self.series.index(res)
653 656 try:
654 657 off = int(minus[1] or 1)
655 658 except(ValueError, OverflowError):
656 659 pass
657 660 else:
658 661 if i - off >= 0:
659 662 return self.series[i - off]
660 663 plus = patch.rsplit('+', 1)
661 664 if len(plus) > 1:
662 665 res = partial_name(plus[0])
663 666 if res:
664 667 i = self.series.index(res)
665 668 try:
666 669 off = int(plus[1] or 1)
667 670 except(ValueError, OverflowError):
668 671 pass
669 672 else:
670 673 if i + off < len(self.series):
671 674 return self.series[i + off]
672 675 self.ui.warn("patch %s not in series\n" % patch)
673 676 sys.exit(1)
674 677
675 678 def push(self, repo, patch=None, force=False, list=False,
676 679 mergeq=None, wlock=None):
677 680 if not wlock:
678 681 wlock = repo.wlock()
679 682 patch = self.lookup(patch)
680 683 if patch and self.isapplied(patch):
681 684 self.ui.warn("patch %s is already applied\n" % patch)
682 685 sys.exit(1)
683 686 if self.series_end() == len(self.series):
684 687 self.ui.warn("File series fully applied\n")
685 688 sys.exit(1)
686 689 if not force:
687 690 self.check_localchanges(repo)
688 691
689 692 self.applied_dirty = 1;
690 693 start = self.series_end()
691 694 if start > 0:
692 695 self.check_toppatch(repo)
693 696 if not patch:
694 697 patch = self.series[start]
695 698 end = start + 1
696 699 else:
697 700 end = self.series.index(patch, start) + 1
698 701 s = self.series[start:end]
699 702 if mergeq:
700 703 ret = self.mergepatch(repo, mergeq, s, wlock)
701 704 else:
702 705 ret = self.apply(repo, s, list, wlock=wlock)
703 706 top = self.applied[-1].split(':')[1]
704 707 if ret[0]:
705 708 self.ui.write("Errors during apply, please fix and refresh %s\n" %
706 709 top)
707 710 else:
708 711 self.ui.write("Now at: %s\n" % top)
709 712 return ret[0]
710 713
711 714 def pop(self, repo, patch=None, force=False, update=True, all=False,
712 715 wlock=None):
713 716 def getfile(f, rev):
714 717 t = repo.file(f).read(rev)
715 718 try:
716 719 repo.wfile(f, "w").write(t)
717 720 except IOError:
718 721 try:
719 722 os.makedirs(os.path.dirname(repo.wjoin(f)))
720 723 except OSError, err:
721 724 if err.errno != errno.EEXIST: raise
722 725 repo.wfile(f, "w").write(t)
723 726
724 727 if not wlock:
725 728 wlock = repo.wlock()
726 729 if patch:
727 730 # index, rev, patch
728 731 info = self.isapplied(patch)
729 732 if not info:
730 733 patch = self.lookup(patch)
731 734 info = self.isapplied(patch)
732 735 if not info:
733 736 self.ui.warn("patch %s is not applied\n" % patch)
734 737 sys.exit(1)
735 738 if len(self.applied) == 0:
736 739 self.ui.warn("No patches applied\n")
737 740 sys.exit(1)
738 741
739 742 if not update:
740 743 parents = repo.dirstate.parents()
741 744 rr = [ revlog.bin(x.split(':')[0]) for x in self.applied ]
742 745 for p in parents:
743 746 if p in rr:
744 747 self.ui.warn("qpop: forcing dirstate update\n")
745 748 update = True
746 749
747 750 if not force and update:
748 751 self.check_localchanges(repo)
749 752
750 753 self.applied_dirty = 1;
751 754 end = len(self.applied)
752 755 if not patch:
753 756 if all:
754 757 popi = 0
755 758 else:
756 759 popi = len(self.applied) - 1
757 760 else:
758 761 popi = info[0] + 1
759 762 if popi >= end:
760 763 self.ui.warn("qpop: %s is already at the top\n" % patch)
761 764 return
762 765 info = [ popi ] + self.applied[popi].split(':')
763 766
764 767 start = info[0]
765 768 rev = revlog.bin(info[1])
766 769
767 770 # we know there are no local changes, so we can make a simplified
768 771 # form of hg.update.
769 772 if update:
770 773 top = self.check_toppatch(repo)
771 774 qp = self.qparents(repo, rev)
772 775 changes = repo.changelog.read(qp)
773 776 mf1 = repo.manifest.readflags(changes[0])
774 777 mmap = repo.manifest.read(changes[0])
775 778 (c, a, r, d, u) = repo.changes(qp, top)
776 779 if d:
777 780 raise util.Abort("deletions found between repo revs")
778 781 for f in c:
779 782 getfile(f, mmap[f])
780 783 for f in r:
781 784 getfile(f, mmap[f])
782 785 util.set_exec(repo.wjoin(f), mf1[f])
783 786 repo.dirstate.update(c + r, 'n')
784 787 for f in a:
785 788 try: os.unlink(repo.wjoin(f))
786 789 except: raise
787 790 try: os.removedirs(os.path.dirname(repo.wjoin(f)))
788 791 except: pass
789 792 if a:
790 793 repo.dirstate.forget(a)
791 794 repo.dirstate.setparents(qp, revlog.nullid)
792 795 self.strip(repo, rev, update=False, backup='strip', wlock=wlock)
793 796 del self.applied[start:end]
794 797 if len(self.applied):
795 798 self.ui.write("Now at: %s\n" % self.applied[-1].split(':')[1])
796 799 else:
797 800 self.ui.write("Patch queue now empty\n")
798 801
799 802 def diff(self, repo, files):
800 803 top = self.check_toppatch(repo)
801 804 if not top:
802 805 self.ui.write("No patches applied\n")
803 806 return
804 807 qp = self.qparents(repo, top)
805 808 commands.dodiff(sys.stdout, self.ui, repo, qp, None, files)
806 809
807 810 def refresh(self, repo, msg=None, short=False):
808 811 if len(self.applied) == 0:
809 812 self.ui.write("No patches applied\n")
810 813 return
811 814 wlock = repo.wlock()
812 815 self.check_toppatch(repo)
813 816 qp = self.qparents(repo)
814 817 (top, patch) = self.applied[-1].split(':')
815 818 top = revlog.bin(top)
816 819 cparents = repo.changelog.parents(top)
817 820 patchparent = self.qparents(repo, top)
818 821 message, comments, user, date, patchfound = self.readheaders(patch)
819 822
820 823 patchf = self.opener(patch, "w")
821 824 if comments:
822 825 comments = "\n".join(comments) + '\n\n'
823 826 patchf.write(comments)
824 827
825 828 tip = repo.changelog.tip()
826 829 if top == tip:
827 830 # if the top of our patch queue is also the tip, there is an
828 831 # optimization here. We update the dirstate in place and strip
829 832 # off the tip commit. Then just commit the current directory
830 833 # tree. We can also send repo.commit the list of files
831 834 # changed to speed up the diff
832 835 #
833 836 # in short mode, we only diff the files included in the
834 837 # patch already
835 838 #
836 839 # this should really read:
837 840 #(cc, dd, aa, aa2, uu) = repo.changes(tip, patchparent)
838 841 # but we do it backwards to take advantage of manifest/chlog
839 842 # caching against the next repo.changes call
840 843 #
841 844 (cc, aa, dd, aa2, uu) = repo.changes(patchparent, tip)
842 845 if short:
843 846 filelist = cc + aa + dd
844 847 else:
845 848 filelist = None
846 849 (c, a, r, d, u) = repo.changes(None, None, filelist)
847 850
848 851 # we might end up with files that were added between tip and
849 852 # the dirstate parent, but then changed in the local dirstate.
850 853 # in this case, we want them to only show up in the added section
851 854 for x in c:
852 855 if x not in aa:
853 856 cc.append(x)
854 857 # we might end up with files added by the local dirstate that
855 858 # were deleted by the patch. In this case, they should only
856 859 # show up in the changed section.
857 860 for x in a:
858 861 if x in dd:
859 862 del dd[dd.index(x)]
860 863 cc.append(x)
861 864 else:
862 865 aa.append(x)
863 866 # make sure any files deleted in the local dirstate
864 867 # are not in the add or change column of the patch
865 868 forget = []
866 869 for x in d + r:
867 870 if x in aa:
868 871 del aa[aa.index(x)]
869 872 forget.append(x)
870 873 continue
871 874 elif x in cc:
872 875 del cc[cc.index(x)]
873 876 dd.append(x)
874 877
875 878 c = list(util.unique(cc))
876 879 r = list(util.unique(dd))
877 880 a = list(util.unique(aa))
878 881 filelist = list(util.unique(c + r + a ))
879 882 commands.dodiff(patchf, self.ui, repo, patchparent, None,
880 883 filelist, changes=(c, a, r, [], u))
881 884 patchf.close()
882 885
883 886 changes = repo.changelog.read(tip)
884 887 repo.dirstate.setparents(*cparents)
885 888 repo.dirstate.update(a, 'a')
886 889 repo.dirstate.update(r, 'r')
887 890 repo.dirstate.update(c, 'n')
888 891 repo.dirstate.forget(forget)
889 892
890 893 if not msg:
891 894 if not message:
892 895 message = "patch queue: %s\n" % patch
893 896 else:
894 897 message = "\n".join(message)
895 898 else:
896 899 message = msg
897 900
898 901 self.strip(repo, top, update=False, backup='strip', wlock=wlock)
899 902 n = repo.commit(filelist, message, changes[1], force=1, wlock=wlock)
900 903 self.applied[-1] = revlog.hex(n) + ':' + patch
901 904 self.applied_dirty = 1
902 905 else:
903 906 commands.dodiff(patchf, self.ui, repo, patchparent, None)
904 907 patchf.close()
905 908 self.pop(repo, force=True, wlock=wlock)
906 909 self.push(repo, force=True, wlock=wlock)
907 910
908 911 def init(self, repo, create=False):
909 912 if os.path.isdir(self.path):
910 913 raise util.Abort("patch queue directory already exists")
911 914 os.mkdir(self.path)
912 915 if create:
913 916 return self.qrepo(create=True)
914 917
915 918 def unapplied(self, repo, patch=None):
916 919 if patch and patch not in self.series:
917 920 self.ui.warn("%s not in the series file\n" % patch)
918 921 sys.exit(1)
919 922 if not patch:
920 923 start = self.series_end()
921 924 else:
922 925 start = self.series.index(patch) + 1
923 926 for p in self.series[start:]:
924 927 if self.ui.verbose:
925 928 self.ui.write("%d " % self.series.index(p))
926 929 self.ui.write("%s\n" % p)
927 930
928 931 def qseries(self, repo, missing=None):
929 932 start = self.series_end()
930 933 if not missing:
931 934 for p in self.series[:start]:
932 935 if self.ui.verbose:
933 936 self.ui.write("%d A " % self.series.index(p))
934 937 self.ui.write("%s\n" % p)
935 938 for p in self.series[start:]:
936 939 if self.ui.verbose:
937 940 self.ui.write("%d U " % self.series.index(p))
938 941 self.ui.write("%s\n" % p)
939 942 else:
940 943 list = []
941 944 for root, dirs, files in os.walk(self.path):
942 945 d = root[len(self.path) + 1:]
943 946 for f in files:
944 947 fl = os.path.join(d, f)
945 948 if (fl not in self.series and
946 949 fl not in (self.status_path, self.series_path)
947 950 and not fl.startswith('.')):
948 951 list.append(fl)
949 952 list.sort()
950 953 if list:
951 954 for x in list:
952 955 if self.ui.verbose:
953 956 self.ui.write("D ")
954 957 self.ui.write("%s\n" % x)
955 958
956 959 def issaveline(self, l):
957 960 name = l.split(':')[1]
958 961 if name == '.hg.patches.save.line':
959 962 return True
960 963
961 964 def qrepo(self, create=False):
962 965 if create or os.path.isdir(os.path.join(self.path, ".hg")):
963 966 return hg.repository(self.ui, path=self.path, create=create)
964 967
965 968 def restore(self, repo, rev, delete=None, qupdate=None):
966 969 c = repo.changelog.read(rev)
967 970 desc = c[4].strip()
968 971 lines = desc.splitlines()
969 972 i = 0
970 973 datastart = None
971 974 series = []
972 975 applied = []
973 976 qpp = None
974 977 for i in xrange(0, len(lines)):
975 978 if lines[i] == 'Patch Data:':
976 979 datastart = i + 1
977 980 elif lines[i].startswith('Dirstate:'):
978 981 l = lines[i].rstrip()
979 982 l = l[10:].split(' ')
980 983 qpp = [ hg.bin(x) for x in l ]
981 984 elif datastart != None:
982 985 l = lines[i].rstrip()
983 986 index = l.index(':')
984 987 id = l[:index]
985 988 file = l[index + 1:]
986 989 if id:
987 990 applied.append(l)
988 991 series.append(file)
989 992 if datastart == None:
990 993 self.ui.warn("No saved patch data found\n")
991 994 return 1
992 995 self.ui.warn("restoring status: %s\n" % lines[0])
993 996 self.full_series = series
994 997 self.applied = applied
995 998 self.read_series(self.full_series)
996 999 self.series_dirty = 1
997 1000 self.applied_dirty = 1
998 1001 heads = repo.changelog.heads()
999 1002 if delete:
1000 1003 if rev not in heads:
1001 1004 self.ui.warn("save entry has children, leaving it alone\n")
1002 1005 else:
1003 1006 self.ui.warn("removing save entry %s\n" % hg.short(rev))
1004 1007 pp = repo.dirstate.parents()
1005 1008 if rev in pp:
1006 1009 update = True
1007 1010 else:
1008 1011 update = False
1009 1012 self.strip(repo, rev, update=update, backup='strip')
1010 1013 if qpp:
1011 1014 self.ui.warn("saved queue repository parents: %s %s\n" %
1012 1015 (hg.short(qpp[0]), hg.short(qpp[1])))
1013 1016 if qupdate:
1014 1017 print "queue directory updating"
1015 1018 r = self.qrepo()
1016 1019 if not r:
1017 1020 self.ui.warn("Unable to load queue repository\n")
1018 1021 return 1
1019 1022 r.update(qpp[0], allow=False, force=True)
1020 1023
1021 1024 def save(self, repo, msg=None):
1022 1025 if len(self.applied) == 0:
1023 1026 self.ui.warn("save: no patches applied, exiting\n")
1024 1027 return 1
1025 1028 if self.issaveline(self.applied[-1]):
1026 1029 self.ui.warn("status is already saved\n")
1027 1030 return 1
1028 1031
1029 1032 ar = [ ':' + x for x in self.full_series ]
1030 1033 if not msg:
1031 1034 msg = "hg patches saved state"
1032 1035 else:
1033 1036 msg = "hg patches: " + msg.rstrip('\r\n')
1034 1037 r = self.qrepo()
1035 1038 if r:
1036 1039 pp = r.dirstate.parents()
1037 1040 msg += "\nDirstate: %s %s" % (hg.hex(pp[0]), hg.hex(pp[1]))
1038 1041 msg += "\n\nPatch Data:\n"
1039 1042 text = msg + "\n".join(self.applied) + '\n' + (ar and "\n".join(ar)
1040 1043 + '\n' or "")
1041 1044 n = repo.commit(None, text, user=None, force=1)
1042 1045 if not n:
1043 1046 self.ui.warn("repo commit failed\n")
1044 1047 return 1
1045 1048 self.applied.append(revlog.hex(n) + ":" + '.hg.patches.save.line')
1046 1049 self.applied_dirty = 1
1047 1050
1048 1051 def full_series_end(self):
1049 1052 if len(self.applied) > 0:
1050 1053 (top, p) = self.applied[-1].split(':')
1051 1054 end = self.find_series(p)
1052 1055 if end == None:
1053 1056 return len(self.full_series)
1054 1057 return end + 1
1055 1058 return 0
1056 1059
1057 1060 def series_end(self):
1058 1061 end = 0
1059 1062 if len(self.applied) > 0:
1060 1063 (top, p) = self.applied[-1].split(':')
1061 1064 try:
1062 1065 end = self.series.index(p)
1063 1066 except ValueError:
1064 1067 return 0
1065 1068 return end + 1
1066 1069 return end
1067 1070
1068 1071 def qapplied(self, repo, patch=None):
1069 1072 if patch and patch not in self.series:
1070 1073 self.ui.warn("%s not in the series file\n" % patch)
1071 1074 sys.exit(1)
1072 1075 if not patch:
1073 1076 end = len(self.applied)
1074 1077 else:
1075 1078 end = self.series.index(patch) + 1
1076 1079 for x in xrange(end):
1077 1080 p = self.appliedname(x)
1078 1081 self.ui.write("%s\n" % p)
1079 1082
1080 1083 def appliedname(self, index):
1081 1084 p = self.applied[index]
1082 1085 pname = p.split(':')[1]
1083 1086 if not self.ui.verbose:
1084 1087 p = pname
1085 1088 else:
1086 1089 p = str(self.series.index(pname)) + " " + p
1087 1090 return p
1088 1091
1089 1092 def top(self, repo):
1090 1093 if len(self.applied):
1091 1094 p = self.appliedname(-1)
1092 1095 self.ui.write(p + '\n')
1093 1096 else:
1094 1097 self.ui.write("No patches applied\n")
1095 1098
1096 1099 def next(self, repo):
1097 1100 end = self.series_end()
1098 1101 if end == len(self.series):
1099 1102 self.ui.write("All patches applied\n")
1100 1103 else:
1101 1104 p = self.series[end]
1102 1105 if self.ui.verbose:
1103 1106 self.ui.write("%d " % self.series.index(p))
1104 1107 self.ui.write(p + '\n')
1105 1108
1106 1109 def prev(self, repo):
1107 1110 if len(self.applied) > 1:
1108 1111 p = self.appliedname(-2)
1109 1112 self.ui.write(p + '\n')
1110 1113 elif len(self.applied) == 1:
1111 1114 self.ui.write("Only one patch applied\n")
1112 1115 else:
1113 1116 self.ui.write("No patches applied\n")
1114 1117
1115 1118 def qimport(self, repo, files, patch=None, existing=None, force=None):
1116 1119 if len(files) > 1 and patch:
1117 1120 self.ui.warn("-n option not valid when importing multiple files\n")
1118 1121 sys.exit(1)
1119 1122 i = 0
1120 1123 added = []
1121 1124 for filename in files:
1122 1125 if existing:
1123 1126 if not patch:
1124 1127 patch = filename
1125 1128 if not os.path.isfile(os.path.join(self.path, patch)):
1126 1129 self.ui.warn("patch %s does not exist\n" % patch)
1127 1130 sys.exit(1)
1128 1131 else:
1129 1132 try:
1130 1133 text = file(filename).read()
1131 1134 except IOError:
1132 1135 self.ui.warn("Unable to read %s\n" % patch)
1133 1136 sys.exit(1)
1134 1137 if not patch:
1135 1138 patch = os.path.split(filename)[1]
1136 1139 if not force and os.path.isfile(os.path.join(self.path, patch)):
1137 1140 self.ui.warn("patch %s already exists\n" % patch)
1138 1141 sys.exit(1)
1139 1142 patchf = self.opener(patch, "w")
1140 1143 patchf.write(text)
1141 1144 if patch in self.series:
1142 1145 self.ui.warn("patch %s is already in the series file\n" % patch)
1143 1146 sys.exit(1)
1144 1147 index = self.full_series_end() + i
1145 1148 self.full_series[index:index] = [patch]
1146 1149 self.read_series(self.full_series)
1147 1150 self.ui.warn("adding %s to series file\n" % patch)
1148 1151 i += 1
1149 1152 added.append(patch)
1150 1153 patch = None
1151 1154 self.series_dirty = 1
1152 1155 qrepo = self.qrepo()
1153 1156 if qrepo:
1154 1157 qrepo.add(added)
1155 1158
1156 1159 def delete(ui, repo, patch, **opts):
1157 1160 """remove a patch from the series file"""
1158 1161 q = repomap[repo]
1159 1162 q.delete(repo, patch)
1160 1163 q.save_dirty()
1161 1164 return 0
1162 1165
1163 1166 def applied(ui, repo, patch=None, **opts):
1164 1167 """print the patches already applied"""
1165 1168 repomap[repo].qapplied(repo, patch)
1166 1169 return 0
1167 1170
1168 1171 def unapplied(ui, repo, patch=None, **opts):
1169 1172 """print the patches not yet applied"""
1170 1173 repomap[repo].unapplied(repo, patch)
1171 1174 return 0
1172 1175
1173 1176 def qimport(ui, repo, *filename, **opts):
1174 1177 """import a patch"""
1175 1178 q = repomap[repo]
1176 1179 q.qimport(repo, filename, patch=opts['name'],
1177 1180 existing=opts['existing'], force=opts['force'])
1178 1181 q.save_dirty()
1179 1182 return 0
1180 1183
1181 1184 def init(ui, repo, **opts):
1182 1185 """init a new queue repository"""
1183 1186 q = repomap[repo]
1184 1187 r = q.init(repo, create=opts['create_repo'])
1185 1188 q.save_dirty()
1186 1189 if r:
1187 1190 fp = r.wopener('.hgignore', 'w')
1188 1191 print >> fp, 'syntax: glob'
1189 1192 print >> fp, 'status'
1190 1193 fp.close()
1191 1194 r.wopener('series', 'w').close()
1192 1195 r.add(['.hgignore', 'series'])
1193 1196 return 0
1194 1197
1195 1198 def commit(ui, repo, *pats, **opts):
1196 1199 """commit changes in the queue repository"""
1197 1200 q = repomap[repo]
1198 1201 r = q.qrepo()
1199 1202 if not r: raise util.Abort('no queue repository')
1200 1203 commands.commit(r.ui, r, *pats, **opts)
1201 1204
1202 1205 def series(ui, repo, **opts):
1203 1206 """print the entire series file"""
1204 1207 repomap[repo].qseries(repo, missing=opts['missing'])
1205 1208 return 0
1206 1209
1207 1210 def top(ui, repo, **opts):
1208 1211 """print the name of the current patch"""
1209 1212 repomap[repo].top(repo)
1210 1213 return 0
1211 1214
1212 1215 def next(ui, repo, **opts):
1213 1216 """print the name of the next patch"""
1214 1217 repomap[repo].next(repo)
1215 1218 return 0
1216 1219
1217 1220 def prev(ui, repo, **opts):
1218 1221 """print the name of the previous patch"""
1219 1222 repomap[repo].prev(repo)
1220 1223 return 0
1221 1224
1222 1225 def new(ui, repo, patch, **opts):
1223 1226 """create a new patch"""
1224 1227 q = repomap[repo]
1225 1228 message=commands.logmessage(**opts)
1226 1229 q.new(repo, patch, msg=message, force=opts['force'])
1227 1230 q.save_dirty()
1228 1231 return 0
1229 1232
1230 1233 def refresh(ui, repo, **opts):
1231 1234 """update the current patch"""
1232 1235 q = repomap[repo]
1233 1236 message=commands.logmessage(**opts)
1234 1237 q.refresh(repo, msg=message, short=opts['short'])
1235 1238 q.save_dirty()
1236 1239 return 0
1237 1240
1238 1241 def diff(ui, repo, *files, **opts):
1239 1242 """diff of the current patch"""
1240 1243 # deep in the dirstate code, the walkhelper method wants a list, not a tuple
1241 1244 repomap[repo].diff(repo, list(files))
1242 1245 return 0
1243 1246
1244 1247 def lastsavename(path):
1245 1248 (dir, base) = os.path.split(path)
1246 1249 names = os.listdir(dir)
1247 1250 namere = re.compile("%s.([0-9]+)" % base)
1248 1251 max = None
1249 1252 maxname = None
1250 1253 for f in names:
1251 1254 m = namere.match(f)
1252 1255 if m:
1253 1256 index = int(m.group(1))
1254 1257 if max == None or index > max:
1255 1258 max = index
1256 1259 maxname = f
1257 1260 if maxname:
1258 1261 return (os.path.join(dir, maxname), max)
1259 1262 return (None, None)
1260 1263
1261 1264 def savename(path):
1262 1265 (last, index) = lastsavename(path)
1263 1266 if last is None:
1264 1267 index = 0
1265 1268 newpath = path + ".%d" % (index + 1)
1266 1269 return newpath
1267 1270
1268 1271 def push(ui, repo, patch=None, **opts):
1269 1272 """push the next patch onto the stack"""
1270 1273 q = repomap[repo]
1271 1274 mergeq = None
1272 1275
1273 1276 if opts['all']:
1274 1277 patch = q.series[-1]
1275 1278 if opts['merge']:
1276 1279 if opts['name']:
1277 1280 newpath = opts['name']
1278 1281 else:
1279 1282 newpath, i = lastsavename(q.path)
1280 1283 if not newpath:
1281 1284 ui.warn("no saved queues found, please use -n\n")
1282 1285 return 1
1283 1286 mergeq = queue(ui, repo.join(""), newpath)
1284 1287 ui.warn("merging with queue at: %s\n" % mergeq.path)
1285 1288 ret = q.push(repo, patch, force=opts['force'], list=opts['list'],
1286 1289 mergeq=mergeq)
1287 1290 q.save_dirty()
1288 1291 return ret
1289 1292
1290 1293 def pop(ui, repo, patch=None, **opts):
1291 1294 """pop the current patch off the stack"""
1292 1295 localupdate = True
1293 1296 if opts['name']:
1294 1297 q = queue(ui, repo.join(""), repo.join(opts['name']))
1295 1298 ui.warn('using patch queue: %s\n' % q.path)
1296 1299 localupdate = False
1297 1300 else:
1298 1301 q = repomap[repo]
1299 1302 q.pop(repo, patch, force=opts['force'], update=localupdate, all=opts['all'])
1300 1303 q.save_dirty()
1301 1304 return 0
1302 1305
1303 1306 def restore(ui, repo, rev, **opts):
1304 1307 """restore the queue state saved by a rev"""
1305 1308 rev = repo.lookup(rev)
1306 1309 q = repomap[repo]
1307 1310 q.restore(repo, rev, delete=opts['delete'],
1308 1311 qupdate=opts['update'])
1309 1312 q.save_dirty()
1310 1313 return 0
1311 1314
1312 1315 def save(ui, repo, **opts):
1313 1316 """save current queue state"""
1314 1317 q = repomap[repo]
1315 1318 message=commands.logmessage(**opts)
1316 1319 ret = q.save(repo, msg=message)
1317 1320 if ret:
1318 1321 return ret
1319 1322 q.save_dirty()
1320 1323 if opts['copy']:
1321 1324 path = q.path
1322 1325 if opts['name']:
1323 1326 newpath = os.path.join(q.basepath, opts['name'])
1324 1327 if os.path.exists(newpath):
1325 1328 if not os.path.isdir(newpath):
1326 1329 ui.warn("destination %s exists and is not a directory\n" %
1327 1330 newpath)
1328 1331 sys.exit(1)
1329 1332 if not opts['force']:
1330 1333 ui.warn("destination %s exists, use -f to force\n" %
1331 1334 newpath)
1332 1335 sys.exit(1)
1333 1336 else:
1334 1337 newpath = savename(path)
1335 1338 ui.warn("copy %s to %s\n" % (path, newpath))
1336 1339 util.copyfiles(path, newpath)
1337 1340 if opts['empty']:
1338 1341 try:
1339 1342 os.unlink(os.path.join(q.path, q.status_path))
1340 1343 except:
1341 1344 pass
1342 1345 return 0
1343 1346
1344 1347 def strip(ui, repo, rev, **opts):
1345 1348 """strip a revision and all later revs on the same branch"""
1346 1349 rev = repo.lookup(rev)
1347 1350 backup = 'all'
1348 1351 if opts['backup']:
1349 1352 backup = 'strip'
1350 1353 elif opts['nobackup']:
1351 1354 backup = 'none'
1352 1355 repomap[repo].strip(repo, rev, backup=backup)
1353 1356 return 0
1354 1357
1355 1358 def version(ui, q=None):
1356 1359 """print the version number"""
1357 1360 ui.write("mq version %s\n" % versionstr)
1358 1361 return 0
1359 1362
1360 1363 def reposetup(ui, repo):
1361 1364 repomap[repo] = queue(ui, repo.join(""))
1362 1365 oldtags = repo.tags
1363 1366
1364 1367 def qtags():
1365 1368 if repo.tagscache:
1366 1369 return repo.tagscache
1367 1370
1368 1371 tagscache = oldtags()
1369 1372
1370 1373 q = repomap[repo]
1371 1374 if len(q.applied) == 0:
1372 1375 return tagscache
1373 1376
1374 1377 mqtags = [patch.split(':') for patch in q.applied]
1375 1378 mqtags.append((mqtags[-1][0], 'qtip'))
1376 1379 mqtags.append((mqtags[0][0], 'qbase'))
1377 1380 for patch in mqtags:
1378 1381 if patch[1] in tagscache:
1379 1382 repo.ui.warn('Tag %s overrides mq patch of the same name\n' % patch[1])
1380 1383 else:
1381 1384 tagscache[patch[1]] = revlog.bin(patch[0])
1382 1385
1383 1386 return tagscache
1384 1387
1385 1388 repo.tags = qtags
1386 1389
1387 1390 cmdtable = {
1388 1391 "qapplied": (applied, [], 'hg qapplied [PATCH]'),
1389 1392 "qcommit|qci":
1390 1393 (commit,
1391 1394 commands.table["^commit|ci"][1],
1392 1395 'hg qcommit [OPTION]... [FILE]...'),
1393 1396 "^qdiff": (diff, [], 'hg qdiff [FILE]...'),
1394 1397 "qdelete": (delete, [], 'hg qdelete PATCH'),
1395 1398 "^qimport":
1396 1399 (qimport,
1397 1400 [('e', 'existing', None, 'import file in patch dir'),
1398 1401 ('n', 'name', '', 'patch file name'),
1399 1402 ('f', 'force', None, 'overwrite existing files')],
1400 1403 'hg qimport [-e] [-n NAME] [-f] FILE...'),
1401 1404 "^qinit":
1402 1405 (init,
1403 1406 [('c', 'create-repo', None, 'create queue repository')],
1404 1407 'hg qinit [-c]'),
1405 1408 "qnew":
1406 1409 (new,
1407 1410 [('m', 'message', '', _('use <text> as commit message')),
1408 1411 ('l', 'logfile', '', _('read the commit message from <file>')),
1409 1412 ('f', 'force', None, 'force')],
1410 1413 'hg qnew [-m TEXT] [-l FILE] [-f] PATCH'),
1411 1414 "qnext": (next, [], 'hg qnext'),
1412 1415 "qprev": (prev, [], 'hg qprev'),
1413 1416 "^qpop":
1414 1417 (pop,
1415 1418 [('a', 'all', None, 'pop all patches'),
1416 1419 ('n', 'name', '', 'queue name to pop'),
1417 1420 ('f', 'force', None, 'forget any local changes')],
1418 1421 'hg qpop [-a] [-n NAME] [-f] [PATCH | INDEX]'),
1419 1422 "^qpush":
1420 1423 (push,
1421 1424 [('f', 'force', None, 'apply if the patch has rejects'),
1422 1425 ('l', 'list', None, 'list patch name in commit text'),
1423 1426 ('a', 'all', None, 'apply all patches'),
1424 1427 ('m', 'merge', None, 'merge from another queue'),
1425 1428 ('n', 'name', '', 'merge queue name')],
1426 1429 'hg qpush [-f] [-l] [-a] [-m] [-n NAME] [PATCH | INDEX]'),
1427 1430 "^qrefresh":
1428 1431 (refresh,
1429 1432 [('m', 'message', '', _('change commit message with <text>')),
1430 1433 ('l', 'logfile', '', _('change commit message with <file> content')),
1431 1434 ('s', 'short', None, 'short refresh')],
1432 1435 'hg qrefresh [-m TEXT] [-l FILE] [-s]'),
1433 1436 "qrestore":
1434 1437 (restore,
1435 1438 [('d', 'delete', None, 'delete save entry'),
1436 1439 ('u', 'update', None, 'update queue working dir')],
1437 1440 'hg qrestore [-d] [-u] REV'),
1438 1441 "qsave":
1439 1442 (save,
1440 1443 [('m', 'message', '', _('use <text> as commit message')),
1441 1444 ('l', 'logfile', '', _('read the commit message from <file>')),
1442 1445 ('c', 'copy', None, 'copy patch directory'),
1443 1446 ('n', 'name', '', 'copy directory name'),
1444 1447 ('e', 'empty', None, 'clear queue status file'),
1445 1448 ('f', 'force', None, 'force copy')],
1446 1449 'hg qsave [-m TEXT] [-l FILE] [-c] [-n NAME] [-e] [-f]'),
1447 1450 "qseries":
1448 1451 (series,
1449 1452 [('m', 'missing', None, 'print patches not in series')],
1450 1453 'hg qseries [-m]'),
1451 1454 "^strip":
1452 1455 (strip,
1453 1456 [('f', 'force', None, 'force multi-head removal'),
1454 1457 ('b', 'backup', None, 'bundle unrelated changesets'),
1455 1458 ('n', 'nobackup', None, 'no backups')],
1456 1459 'hg strip [-f] [-b] [-n] REV'),
1457 1460 "qtop": (top, [], 'hg qtop'),
1458 1461 "qunapplied": (unapplied, [], 'hg qunapplied [PATCH]'),
1459 1462 "qversion": (version, [], 'hg qversion')
1460 1463 }
1461 1464
General Comments 0
You need to be logged in to leave comments. Login now