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