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