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