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