##// END OF EJS Templates
qnew: ignore force option...
Augie Fackler -
r10372:27d542bc default
parent child Browse files
Show More
@@ -1,2766 +1,2762
1 1 # mq.py - patch queues for mercurial
2 2 #
3 3 # Copyright 2005, 2006 Chris Mason <mason@suse.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 '''manage a stack of patches
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
26 26 add known patch to applied stack qpush
27 27 remove patch from applied stack qpop
28 28 refresh contents of top applied patch qrefresh
29 29
30 30 By default, mq will automatically use git patches when required to
31 31 avoid losing file mode changes, copy records, binary files or empty
32 32 files creations or deletions. This behaviour can be configured with::
33 33
34 34 [mq]
35 35 git = auto/keep/yes/no
36 36
37 37 If set to 'keep', mq will obey the [diff] section configuration while
38 38 preserving existing git patches upon qrefresh. If set to 'yes' or
39 39 'no', mq will override the [diff] section and always generate git or
40 40 regular patches, possibly losing data in the second case.
41 41 '''
42 42
43 43 from mercurial.i18n import _
44 44 from mercurial.node import bin, hex, short, nullid, nullrev
45 45 from mercurial.lock import release
46 46 from mercurial import commands, cmdutil, hg, patch, util
47 47 from mercurial import repair, extensions, url, error
48 48 import os, sys, re, errno
49 49
50 50 commands.norepo += " qclone"
51 51
52 52 # Patch names looks like unix-file names.
53 53 # They must be joinable with queue directory and result in the patch path.
54 54 normname = util.normpath
55 55
56 56 class statusentry(object):
57 57 def __init__(self, rev, name=None):
58 58 if not name:
59 59 fields = rev.split(':', 1)
60 60 if len(fields) == 2:
61 61 self.rev, self.name = fields
62 62 else:
63 63 self.rev, self.name = None, None
64 64 else:
65 65 self.rev, self.name = rev, name
66 66
67 67 def __str__(self):
68 68 return self.rev + ':' + self.name
69 69
70 70 class patchheader(object):
71 71 def __init__(self, pf):
72 72 def eatdiff(lines):
73 73 while lines:
74 74 l = lines[-1]
75 75 if (l.startswith("diff -") or
76 76 l.startswith("Index:") or
77 77 l.startswith("===========")):
78 78 del lines[-1]
79 79 else:
80 80 break
81 81 def eatempty(lines):
82 82 while lines:
83 83 l = lines[-1]
84 84 if re.match('\s*$', l):
85 85 del lines[-1]
86 86 else:
87 87 break
88 88
89 89 message = []
90 90 comments = []
91 91 user = None
92 92 date = None
93 93 format = None
94 94 subject = None
95 95 diffstart = 0
96 96
97 97 for line in file(pf):
98 98 line = line.rstrip()
99 99 if line.startswith('diff --git'):
100 100 diffstart = 2
101 101 break
102 102 if diffstart:
103 103 if line.startswith('+++ '):
104 104 diffstart = 2
105 105 break
106 106 if line.startswith("--- "):
107 107 diffstart = 1
108 108 continue
109 109 elif format == "hgpatch":
110 110 # parse values when importing the result of an hg export
111 111 if line.startswith("# User "):
112 112 user = line[7:]
113 113 elif line.startswith("# Date "):
114 114 date = line[7:]
115 115 elif not line.startswith("# ") and line:
116 116 message.append(line)
117 117 format = None
118 118 elif line == '# HG changeset patch':
119 119 message = []
120 120 format = "hgpatch"
121 121 elif (format != "tagdone" and (line.startswith("Subject: ") or
122 122 line.startswith("subject: "))):
123 123 subject = line[9:]
124 124 format = "tag"
125 125 elif (format != "tagdone" and (line.startswith("From: ") or
126 126 line.startswith("from: "))):
127 127 user = line[6:]
128 128 format = "tag"
129 129 elif format == "tag" and line == "":
130 130 # when looking for tags (subject: from: etc) they
131 131 # end once you find a blank line in the source
132 132 format = "tagdone"
133 133 elif message or line:
134 134 message.append(line)
135 135 comments.append(line)
136 136
137 137 eatdiff(message)
138 138 eatdiff(comments)
139 139 eatempty(message)
140 140 eatempty(comments)
141 141
142 142 # make sure message isn't empty
143 143 if format and format.startswith("tag") and subject:
144 144 message.insert(0, "")
145 145 message.insert(0, subject)
146 146
147 147 self.message = message
148 148 self.comments = comments
149 149 self.user = user
150 150 self.date = date
151 151 self.haspatch = diffstart > 1
152 152
153 153 def setuser(self, user):
154 154 if not self.updateheader(['From: ', '# User '], user):
155 155 try:
156 156 patchheaderat = self.comments.index('# HG changeset patch')
157 157 self.comments.insert(patchheaderat + 1, '# User ' + user)
158 158 except ValueError:
159 159 if self._hasheader(['Date: ']):
160 160 self.comments = ['From: ' + user] + self.comments
161 161 else:
162 162 tmp = ['# HG changeset patch', '# User ' + user, '']
163 163 self.comments = tmp + self.comments
164 164 self.user = user
165 165
166 166 def setdate(self, date):
167 167 if not self.updateheader(['Date: ', '# Date '], date):
168 168 try:
169 169 patchheaderat = self.comments.index('# HG changeset patch')
170 170 self.comments.insert(patchheaderat + 1, '# Date ' + date)
171 171 except ValueError:
172 172 if self._hasheader(['From: ']):
173 173 self.comments = ['Date: ' + date] + self.comments
174 174 else:
175 175 tmp = ['# HG changeset patch', '# Date ' + date, '']
176 176 self.comments = tmp + self.comments
177 177 self.date = date
178 178
179 179 def setmessage(self, message):
180 180 if self.comments:
181 181 self._delmsg()
182 182 self.message = [message]
183 183 self.comments += self.message
184 184
185 185 def updateheader(self, prefixes, new):
186 186 '''Update all references to a field in the patch header.
187 187 Return whether the field is present.'''
188 188 res = False
189 189 for prefix in prefixes:
190 190 for i in xrange(len(self.comments)):
191 191 if self.comments[i].startswith(prefix):
192 192 self.comments[i] = prefix + new
193 193 res = True
194 194 break
195 195 return res
196 196
197 197 def _hasheader(self, prefixes):
198 198 '''Check if a header starts with any of the given prefixes.'''
199 199 for prefix in prefixes:
200 200 for comment in self.comments:
201 201 if comment.startswith(prefix):
202 202 return True
203 203 return False
204 204
205 205 def __str__(self):
206 206 if not self.comments:
207 207 return ''
208 208 return '\n'.join(self.comments) + '\n\n'
209 209
210 210 def _delmsg(self):
211 211 '''Remove existing message, keeping the rest of the comments fields.
212 212 If comments contains 'subject: ', message will prepend
213 213 the field and a blank line.'''
214 214 if self.message:
215 215 subj = 'subject: ' + self.message[0].lower()
216 216 for i in xrange(len(self.comments)):
217 217 if subj == self.comments[i].lower():
218 218 del self.comments[i]
219 219 self.message = self.message[2:]
220 220 break
221 221 ci = 0
222 222 for mi in self.message:
223 223 while mi != self.comments[ci]:
224 224 ci += 1
225 225 del self.comments[ci]
226 226
227 227 class queue(object):
228 228 def __init__(self, ui, path, patchdir=None):
229 229 self.basepath = path
230 230 self.path = patchdir or os.path.join(path, "patches")
231 231 self.opener = util.opener(self.path)
232 232 self.ui = ui
233 233 self.applied_dirty = 0
234 234 self.series_dirty = 0
235 235 self.series_path = "series"
236 236 self.status_path = "status"
237 237 self.guards_path = "guards"
238 238 self.active_guards = None
239 239 self.guards_dirty = False
240 240 # Handle mq.git as a bool with extended values
241 241 try:
242 242 gitmode = ui.configbool('mq', 'git', None)
243 243 if gitmode is None:
244 244 raise error.ConfigError()
245 245 self.gitmode = gitmode and 'yes' or 'no'
246 246 except error.ConfigError:
247 247 self.gitmode = ui.config('mq', 'git', 'auto').lower()
248 248
249 249 @util.propertycache
250 250 def applied(self):
251 251 if os.path.exists(self.join(self.status_path)):
252 252 lines = self.opener(self.status_path).read().splitlines()
253 253 return [statusentry(l) for l in lines]
254 254 return []
255 255
256 256 @util.propertycache
257 257 def full_series(self):
258 258 if os.path.exists(self.join(self.series_path)):
259 259 return self.opener(self.series_path).read().splitlines()
260 260 return []
261 261
262 262 @util.propertycache
263 263 def series(self):
264 264 self.parse_series()
265 265 return self.series
266 266
267 267 @util.propertycache
268 268 def series_guards(self):
269 269 self.parse_series()
270 270 return self.series_guards
271 271
272 272 def invalidate(self):
273 273 for a in 'applied full_series series series_guards'.split():
274 274 if a in self.__dict__:
275 275 delattr(self, a)
276 276 self.applied_dirty = 0
277 277 self.series_dirty = 0
278 278 self.guards_dirty = False
279 279 self.active_guards = None
280 280
281 281 def diffopts(self, opts={}, patchfn=None):
282 282 diffopts = patch.diffopts(self.ui, opts)
283 283 if self.gitmode == 'auto':
284 284 diffopts.upgrade = True
285 285 elif self.gitmode == 'keep':
286 286 pass
287 287 elif self.gitmode in ('yes', 'no'):
288 288 diffopts.git = self.gitmode == 'yes'
289 289 else:
290 290 raise util.Abort(_('mq.git option can be auto/keep/yes/no'
291 291 ' got %s') % self.gitmode)
292 292 if patchfn:
293 293 diffopts = self.patchopts(diffopts, patchfn)
294 294 return diffopts
295 295
296 296 def patchopts(self, diffopts, *patches):
297 297 """Return a copy of input diff options with git set to true if
298 298 referenced patch is a git patch and should be preserved as such.
299 299 """
300 300 diffopts = diffopts.copy()
301 301 if not diffopts.git and self.gitmode == 'keep':
302 302 for patchfn in patches:
303 303 patchf = self.opener(patchfn, 'r')
304 304 # if the patch was a git patch, refresh it as a git patch
305 305 for line in patchf:
306 306 if line.startswith('diff --git'):
307 307 diffopts.git = True
308 308 break
309 309 patchf.close()
310 310 return diffopts
311 311
312 312 def join(self, *p):
313 313 return os.path.join(self.path, *p)
314 314
315 315 def find_series(self, patch):
316 316 pre = re.compile("(\s*)([^#]+)")
317 317 index = 0
318 318 for l in self.full_series:
319 319 m = pre.match(l)
320 320 if m:
321 321 s = m.group(2)
322 322 s = s.rstrip()
323 323 if s == patch:
324 324 return index
325 325 index += 1
326 326 return None
327 327
328 328 guard_re = re.compile(r'\s?#([-+][^-+# \t\r\n\f][^# \t\r\n\f]*)')
329 329
330 330 def parse_series(self):
331 331 self.series = []
332 332 self.series_guards = []
333 333 for l in self.full_series:
334 334 h = l.find('#')
335 335 if h == -1:
336 336 patch = l
337 337 comment = ''
338 338 elif h == 0:
339 339 continue
340 340 else:
341 341 patch = l[:h]
342 342 comment = l[h:]
343 343 patch = patch.strip()
344 344 if patch:
345 345 if patch in self.series:
346 346 raise util.Abort(_('%s appears more than once in %s') %
347 347 (patch, self.join(self.series_path)))
348 348 self.series.append(patch)
349 349 self.series_guards.append(self.guard_re.findall(comment))
350 350
351 351 def check_guard(self, guard):
352 352 if not guard:
353 353 return _('guard cannot be an empty string')
354 354 bad_chars = '# \t\r\n\f'
355 355 first = guard[0]
356 356 if first in '-+':
357 357 return (_('guard %r starts with invalid character: %r') %
358 358 (guard, first))
359 359 for c in bad_chars:
360 360 if c in guard:
361 361 return _('invalid character in guard %r: %r') % (guard, c)
362 362
363 363 def set_active(self, guards):
364 364 for guard in guards:
365 365 bad = self.check_guard(guard)
366 366 if bad:
367 367 raise util.Abort(bad)
368 368 guards = sorted(set(guards))
369 369 self.ui.debug('active guards: %s\n' % ' '.join(guards))
370 370 self.active_guards = guards
371 371 self.guards_dirty = True
372 372
373 373 def active(self):
374 374 if self.active_guards is None:
375 375 self.active_guards = []
376 376 try:
377 377 guards = self.opener(self.guards_path).read().split()
378 378 except IOError, err:
379 379 if err.errno != errno.ENOENT:
380 380 raise
381 381 guards = []
382 382 for i, guard in enumerate(guards):
383 383 bad = self.check_guard(guard)
384 384 if bad:
385 385 self.ui.warn('%s:%d: %s\n' %
386 386 (self.join(self.guards_path), i + 1, bad))
387 387 else:
388 388 self.active_guards.append(guard)
389 389 return self.active_guards
390 390
391 391 def set_guards(self, idx, guards):
392 392 for g in guards:
393 393 if len(g) < 2:
394 394 raise util.Abort(_('guard %r too short') % g)
395 395 if g[0] not in '-+':
396 396 raise util.Abort(_('guard %r starts with invalid char') % g)
397 397 bad = self.check_guard(g[1:])
398 398 if bad:
399 399 raise util.Abort(bad)
400 400 drop = self.guard_re.sub('', self.full_series[idx])
401 401 self.full_series[idx] = drop + ''.join([' #' + g for g in guards])
402 402 self.parse_series()
403 403 self.series_dirty = True
404 404
405 405 def pushable(self, idx):
406 406 if isinstance(idx, str):
407 407 idx = self.series.index(idx)
408 408 patchguards = self.series_guards[idx]
409 409 if not patchguards:
410 410 return True, None
411 411 guards = self.active()
412 412 exactneg = [g for g in patchguards if g[0] == '-' and g[1:] in guards]
413 413 if exactneg:
414 414 return False, exactneg[0]
415 415 pos = [g for g in patchguards if g[0] == '+']
416 416 exactpos = [g for g in pos if g[1:] in guards]
417 417 if pos:
418 418 if exactpos:
419 419 return True, exactpos[0]
420 420 return False, pos
421 421 return True, ''
422 422
423 423 def explain_pushable(self, idx, all_patches=False):
424 424 write = all_patches and self.ui.write or self.ui.warn
425 425 if all_patches or self.ui.verbose:
426 426 if isinstance(idx, str):
427 427 idx = self.series.index(idx)
428 428 pushable, why = self.pushable(idx)
429 429 if all_patches and pushable:
430 430 if why is None:
431 431 write(_('allowing %s - no guards in effect\n') %
432 432 self.series[idx])
433 433 else:
434 434 if not why:
435 435 write(_('allowing %s - no matching negative guards\n') %
436 436 self.series[idx])
437 437 else:
438 438 write(_('allowing %s - guarded by %r\n') %
439 439 (self.series[idx], why))
440 440 if not pushable:
441 441 if why:
442 442 write(_('skipping %s - guarded by %r\n') %
443 443 (self.series[idx], why))
444 444 else:
445 445 write(_('skipping %s - no matching guards\n') %
446 446 self.series[idx])
447 447
448 448 def save_dirty(self):
449 449 def write_list(items, path):
450 450 fp = self.opener(path, 'w')
451 451 for i in items:
452 452 fp.write("%s\n" % i)
453 453 fp.close()
454 454 if self.applied_dirty:
455 455 write_list(map(str, self.applied), self.status_path)
456 456 if self.series_dirty:
457 457 write_list(self.full_series, self.series_path)
458 458 if self.guards_dirty:
459 459 write_list(self.active_guards, self.guards_path)
460 460
461 461 def removeundo(self, repo):
462 462 undo = repo.sjoin('undo')
463 463 if not os.path.exists(undo):
464 464 return
465 465 try:
466 466 os.unlink(undo)
467 467 except OSError, inst:
468 468 self.ui.warn(_('error removing undo: %s\n') % str(inst))
469 469
470 470 def printdiff(self, repo, diffopts, node1, node2=None, files=None,
471 471 fp=None, changes=None, opts={}):
472 472 stat = opts.get('stat')
473 473 if stat:
474 474 opts['unified'] = '0'
475 475
476 476 m = cmdutil.match(repo, files, opts)
477 477 chunks = patch.diff(repo, node1, node2, m, changes, diffopts)
478 478 write = fp is None and repo.ui.write or fp.write
479 479 if stat:
480 480 width = self.ui.interactive() and util.termwidth() or 80
481 481 write(patch.diffstat(util.iterlines(chunks), width=width,
482 482 git=diffopts.git))
483 483 else:
484 484 for chunk in chunks:
485 485 write(chunk)
486 486
487 487 def mergeone(self, repo, mergeq, head, patch, rev, diffopts):
488 488 # first try just applying the patch
489 489 (err, n) = self.apply(repo, [patch], update_status=False,
490 490 strict=True, merge=rev)
491 491
492 492 if err == 0:
493 493 return (err, n)
494 494
495 495 if n is None:
496 496 raise util.Abort(_("apply failed for patch %s") % patch)
497 497
498 498 self.ui.warn(_("patch didn't work out, merging %s\n") % patch)
499 499
500 500 # apply failed, strip away that rev and merge.
501 501 hg.clean(repo, head)
502 502 self.strip(repo, n, update=False, backup='strip')
503 503
504 504 ctx = repo[rev]
505 505 ret = hg.merge(repo, rev)
506 506 if ret:
507 507 raise util.Abort(_("update returned %d") % ret)
508 508 n = repo.commit(ctx.description(), ctx.user(), force=True)
509 509 if n is None:
510 510 raise util.Abort(_("repo commit failed"))
511 511 try:
512 512 ph = patchheader(mergeq.join(patch))
513 513 except:
514 514 raise util.Abort(_("unable to read %s") % patch)
515 515
516 516 diffopts = self.patchopts(diffopts, patch)
517 517 patchf = self.opener(patch, "w")
518 518 comments = str(ph)
519 519 if comments:
520 520 patchf.write(comments)
521 521 self.printdiff(repo, diffopts, head, n, fp=patchf)
522 522 patchf.close()
523 523 self.removeundo(repo)
524 524 return (0, n)
525 525
526 526 def qparents(self, repo, rev=None):
527 527 if rev is None:
528 528 (p1, p2) = repo.dirstate.parents()
529 529 if p2 == nullid:
530 530 return p1
531 531 if len(self.applied) == 0:
532 532 return None
533 533 return bin(self.applied[-1].rev)
534 534 pp = repo.changelog.parents(rev)
535 535 if pp[1] != nullid:
536 536 arevs = [x.rev for x in self.applied]
537 537 p0 = hex(pp[0])
538 538 p1 = hex(pp[1])
539 539 if p0 in arevs:
540 540 return pp[0]
541 541 if p1 in arevs:
542 542 return pp[1]
543 543 return pp[0]
544 544
545 545 def mergepatch(self, repo, mergeq, series, diffopts):
546 546 if len(self.applied) == 0:
547 547 # each of the patches merged in will have two parents. This
548 548 # can confuse the qrefresh, qdiff, and strip code because it
549 549 # needs to know which parent is actually in the patch queue.
550 550 # so, we insert a merge marker with only one parent. This way
551 551 # the first patch in the queue is never a merge patch
552 552 #
553 553 pname = ".hg.patches.merge.marker"
554 554 n = repo.commit('[mq]: merge marker', force=True)
555 555 self.removeundo(repo)
556 556 self.applied.append(statusentry(hex(n), pname))
557 557 self.applied_dirty = 1
558 558
559 559 head = self.qparents(repo)
560 560
561 561 for patch in series:
562 562 patch = mergeq.lookup(patch, strict=True)
563 563 if not patch:
564 564 self.ui.warn(_("patch %s does not exist\n") % patch)
565 565 return (1, None)
566 566 pushable, reason = self.pushable(patch)
567 567 if not pushable:
568 568 self.explain_pushable(patch, all_patches=True)
569 569 continue
570 570 info = mergeq.isapplied(patch)
571 571 if not info:
572 572 self.ui.warn(_("patch %s is not applied\n") % patch)
573 573 return (1, None)
574 574 rev = bin(info[1])
575 575 err, head = self.mergeone(repo, mergeq, head, patch, rev, diffopts)
576 576 if head:
577 577 self.applied.append(statusentry(hex(head), patch))
578 578 self.applied_dirty = 1
579 579 if err:
580 580 return (err, head)
581 581 self.save_dirty()
582 582 return (0, head)
583 583
584 584 def patch(self, repo, patchfile):
585 585 '''Apply patchfile to the working directory.
586 586 patchfile: name of patch file'''
587 587 files = {}
588 588 try:
589 589 fuzz = patch.patch(patchfile, self.ui, strip=1, cwd=repo.root,
590 590 files=files, eolmode=None)
591 591 except Exception, inst:
592 592 self.ui.note(str(inst) + '\n')
593 593 if not self.ui.verbose:
594 594 self.ui.warn(_("patch failed, unable to continue (try -v)\n"))
595 595 return (False, files, False)
596 596
597 597 return (True, files, fuzz)
598 598
599 599 def apply(self, repo, series, list=False, update_status=True,
600 600 strict=False, patchdir=None, merge=None, all_files={}):
601 601 wlock = lock = tr = None
602 602 try:
603 603 wlock = repo.wlock()
604 604 lock = repo.lock()
605 605 tr = repo.transaction()
606 606 try:
607 607 ret = self._apply(repo, series, list, update_status,
608 608 strict, patchdir, merge, all_files=all_files)
609 609 tr.close()
610 610 self.save_dirty()
611 611 return ret
612 612 except:
613 613 try:
614 614 tr.abort()
615 615 finally:
616 616 repo.invalidate()
617 617 repo.dirstate.invalidate()
618 618 raise
619 619 finally:
620 620 del tr
621 621 release(lock, wlock)
622 622 self.removeundo(repo)
623 623
624 624 def _apply(self, repo, series, list=False, update_status=True,
625 625 strict=False, patchdir=None, merge=None, all_files={}):
626 626 '''returns (error, hash)
627 627 error = 1 for unable to read, 2 for patch failed, 3 for patch fuzz'''
628 628 # TODO unify with commands.py
629 629 if not patchdir:
630 630 patchdir = self.path
631 631 err = 0
632 632 n = None
633 633 for patchname in series:
634 634 pushable, reason = self.pushable(patchname)
635 635 if not pushable:
636 636 self.explain_pushable(patchname, all_patches=True)
637 637 continue
638 638 self.ui.status(_("applying %s\n") % patchname)
639 639 pf = os.path.join(patchdir, patchname)
640 640
641 641 try:
642 642 ph = patchheader(self.join(patchname))
643 643 except:
644 644 self.ui.warn(_("unable to read %s\n") % patchname)
645 645 err = 1
646 646 break
647 647
648 648 message = ph.message
649 649 if not message:
650 650 message = "imported patch %s\n" % patchname
651 651 else:
652 652 if list:
653 653 message.append("\nimported patch %s" % patchname)
654 654 message = '\n'.join(message)
655 655
656 656 if ph.haspatch:
657 657 (patcherr, files, fuzz) = self.patch(repo, pf)
658 658 all_files.update(files)
659 659 patcherr = not patcherr
660 660 else:
661 661 self.ui.warn(_("patch %s is empty\n") % patchname)
662 662 patcherr, files, fuzz = 0, [], 0
663 663
664 664 if merge and files:
665 665 # Mark as removed/merged and update dirstate parent info
666 666 removed = []
667 667 merged = []
668 668 for f in files:
669 669 if os.path.exists(repo.wjoin(f)):
670 670 merged.append(f)
671 671 else:
672 672 removed.append(f)
673 673 for f in removed:
674 674 repo.dirstate.remove(f)
675 675 for f in merged:
676 676 repo.dirstate.merge(f)
677 677 p1, p2 = repo.dirstate.parents()
678 678 repo.dirstate.setparents(p1, merge)
679 679
680 680 files = patch.updatedir(self.ui, repo, files)
681 681 match = cmdutil.matchfiles(repo, files or [])
682 682 n = repo.commit(message, ph.user, ph.date, match=match, force=True)
683 683
684 684 if n is None:
685 685 raise util.Abort(_("repo commit failed"))
686 686
687 687 if update_status:
688 688 self.applied.append(statusentry(hex(n), patchname))
689 689
690 690 if patcherr:
691 691 self.ui.warn(_("patch failed, rejects left in working dir\n"))
692 692 err = 2
693 693 break
694 694
695 695 if fuzz and strict:
696 696 self.ui.warn(_("fuzz found when applying patch, stopping\n"))
697 697 err = 3
698 698 break
699 699 return (err, n)
700 700
701 701 def _cleanup(self, patches, numrevs, keep=False):
702 702 if not keep:
703 703 r = self.qrepo()
704 704 if r:
705 705 r.remove(patches, True)
706 706 else:
707 707 for p in patches:
708 708 os.unlink(self.join(p))
709 709
710 710 if numrevs:
711 711 del self.applied[:numrevs]
712 712 self.applied_dirty = 1
713 713
714 714 for i in sorted([self.find_series(p) for p in patches], reverse=True):
715 715 del self.full_series[i]
716 716 self.parse_series()
717 717 self.series_dirty = 1
718 718
719 719 def _revpatches(self, repo, revs):
720 720 firstrev = repo[self.applied[0].rev].rev()
721 721 patches = []
722 722 for i, rev in enumerate(revs):
723 723
724 724 if rev < firstrev:
725 725 raise util.Abort(_('revision %d is not managed') % rev)
726 726
727 727 ctx = repo[rev]
728 728 base = bin(self.applied[i].rev)
729 729 if ctx.node() != base:
730 730 msg = _('cannot delete revision %d above applied patches')
731 731 raise util.Abort(msg % rev)
732 732
733 733 patch = self.applied[i].name
734 734 for fmt in ('[mq]: %s', 'imported patch %s'):
735 735 if ctx.description() == fmt % patch:
736 736 msg = _('patch %s finalized without changeset message\n')
737 737 repo.ui.status(msg % patch)
738 738 break
739 739
740 740 patches.append(patch)
741 741 return patches
742 742
743 743 def finish(self, repo, revs):
744 744 patches = self._revpatches(repo, sorted(revs))
745 745 self._cleanup(patches, len(patches))
746 746
747 747 def delete(self, repo, patches, opts):
748 748 if not patches and not opts.get('rev'):
749 749 raise util.Abort(_('qdelete requires at least one revision or '
750 750 'patch name'))
751 751
752 752 realpatches = []
753 753 for patch in patches:
754 754 patch = self.lookup(patch, strict=True)
755 755 info = self.isapplied(patch)
756 756 if info:
757 757 raise util.Abort(_("cannot delete applied patch %s") % patch)
758 758 if patch not in self.series:
759 759 raise util.Abort(_("patch %s not in series file") % patch)
760 760 realpatches.append(patch)
761 761
762 762 numrevs = 0
763 763 if opts.get('rev'):
764 764 if not self.applied:
765 765 raise util.Abort(_('no patches applied'))
766 766 revs = cmdutil.revrange(repo, opts['rev'])
767 767 if len(revs) > 1 and revs[0] > revs[1]:
768 768 revs.reverse()
769 769 revpatches = self._revpatches(repo, revs)
770 770 realpatches += revpatches
771 771 numrevs = len(revpatches)
772 772
773 773 self._cleanup(realpatches, numrevs, opts.get('keep'))
774 774
775 775 def check_toppatch(self, repo):
776 776 if len(self.applied) > 0:
777 777 top = bin(self.applied[-1].rev)
778 778 patch = self.applied[-1].name
779 779 pp = repo.dirstate.parents()
780 780 if top not in pp:
781 781 raise util.Abort(_("working directory revision is not qtip"))
782 782 return top, patch
783 783 return None, None
784 784
785 785 def check_localchanges(self, repo, force=False, refresh=True):
786 786 m, a, r, d = repo.status()[:4]
787 if m or a or r or d:
788 if not force:
787 if (m or a or r or d) and not force:
789 788 if refresh:
790 789 raise util.Abort(_("local changes found, refresh first"))
791 790 else:
792 791 raise util.Abort(_("local changes found"))
793 792 return m, a, r, d
794 793
795 794 _reserved = ('series', 'status', 'guards')
796 795 def check_reserved_name(self, name):
797 796 if (name in self._reserved or name.startswith('.hg')
798 797 or name.startswith('.mq')):
799 798 raise util.Abort(_('"%s" cannot be used as the name of a patch')
800 799 % name)
801 800
802 801 def new(self, repo, patchfn, *pats, **opts):
803 802 """options:
804 803 msg: a string or a no-argument function returning a string
805 804 """
806 805 msg = opts.get('msg')
807 force = opts.get('force')
808 806 user = opts.get('user')
809 807 date = opts.get('date')
810 808 if date:
811 809 date = util.parsedate(date)
812 810 diffopts = self.diffopts({'git': opts.get('git')})
813 811 self.check_reserved_name(patchfn)
814 812 if os.path.exists(self.join(patchfn)):
815 813 raise util.Abort(_('patch "%s" already exists') % patchfn)
816 814 if opts.get('include') or opts.get('exclude') or pats:
817 815 match = cmdutil.match(repo, pats, opts)
818 816 # detect missing files in pats
819 817 def badfn(f, msg):
820 818 raise util.Abort('%s: %s' % (f, msg))
821 819 match.bad = badfn
822 820 m, a, r, d = repo.status(match=match)[:4]
823 821 else:
824 m, a, r, d = self.check_localchanges(repo, force)
822 m, a, r, d = self.check_localchanges(repo, force=True)
825 823 match = cmdutil.matchfiles(repo, m + a + r)
826 if force:
827 p = repo[None].parents()
828 if len(p) > 1:
824 if len(repo[None].parents()) > 1:
829 825 raise util.Abort(_('cannot manage merge changesets'))
830 826 commitfiles = m + a + r
831 827 self.check_toppatch(repo)
832 828 insert = self.full_series_end()
833 829 wlock = repo.wlock()
834 830 try:
835 831 # if patch file write fails, abort early
836 832 p = self.opener(patchfn, "w")
837 833 try:
838 834 if date:
839 835 p.write("# HG changeset patch\n")
840 836 if user:
841 837 p.write("# User " + user + "\n")
842 838 p.write("# Date %d %d\n\n" % date)
843 839 elif user:
844 840 p.write("From: " + user + "\n\n")
845 841
846 842 if hasattr(msg, '__call__'):
847 843 msg = msg()
848 844 commitmsg = msg and msg or ("[mq]: %s" % patchfn)
849 845 n = repo.commit(commitmsg, user, date, match=match, force=True)
850 846 if n is None:
851 847 raise util.Abort(_("repo commit failed"))
852 848 try:
853 849 self.full_series[insert:insert] = [patchfn]
854 850 self.applied.append(statusentry(hex(n), patchfn))
855 851 self.parse_series()
856 852 self.series_dirty = 1
857 853 self.applied_dirty = 1
858 854 if msg:
859 855 msg = msg + "\n\n"
860 856 p.write(msg)
861 857 if commitfiles:
862 858 parent = self.qparents(repo, n)
863 859 chunks = patch.diff(repo, node1=parent, node2=n,
864 860 match=match, opts=diffopts)
865 861 for chunk in chunks:
866 862 p.write(chunk)
867 863 p.close()
868 864 wlock.release()
869 865 wlock = None
870 866 r = self.qrepo()
871 867 if r:
872 868 r.add([patchfn])
873 869 except:
874 870 repo.rollback()
875 871 raise
876 872 except Exception:
877 873 patchpath = self.join(patchfn)
878 874 try:
879 875 os.unlink(patchpath)
880 876 except:
881 877 self.ui.warn(_('error unlinking %s\n') % patchpath)
882 878 raise
883 879 self.removeundo(repo)
884 880 finally:
885 881 release(wlock)
886 882
887 883 def strip(self, repo, rev, update=True, backup="all", force=None):
888 884 wlock = lock = None
889 885 try:
890 886 wlock = repo.wlock()
891 887 lock = repo.lock()
892 888
893 889 if update:
894 890 self.check_localchanges(repo, force=force, refresh=False)
895 891 urev = self.qparents(repo, rev)
896 892 hg.clean(repo, urev)
897 893 repo.dirstate.write()
898 894
899 895 self.removeundo(repo)
900 896 repair.strip(self.ui, repo, rev, backup)
901 897 # strip may have unbundled a set of backed up revisions after
902 898 # the actual strip
903 899 self.removeundo(repo)
904 900 finally:
905 901 release(lock, wlock)
906 902
907 903 def isapplied(self, patch):
908 904 """returns (index, rev, patch)"""
909 905 for i, a in enumerate(self.applied):
910 906 if a.name == patch:
911 907 return (i, a.rev, a.name)
912 908 return None
913 909
914 910 # if the exact patch name does not exist, we try a few
915 911 # variations. If strict is passed, we try only #1
916 912 #
917 913 # 1) a number to indicate an offset in the series file
918 914 # 2) a unique substring of the patch name was given
919 915 # 3) patchname[-+]num to indicate an offset in the series file
920 916 def lookup(self, patch, strict=False):
921 917 patch = patch and str(patch)
922 918
923 919 def partial_name(s):
924 920 if s in self.series:
925 921 return s
926 922 matches = [x for x in self.series if s in x]
927 923 if len(matches) > 1:
928 924 self.ui.warn(_('patch name "%s" is ambiguous:\n') % s)
929 925 for m in matches:
930 926 self.ui.warn(' %s\n' % m)
931 927 return None
932 928 if matches:
933 929 return matches[0]
934 930 if len(self.series) > 0 and len(self.applied) > 0:
935 931 if s == 'qtip':
936 932 return self.series[self.series_end(True)-1]
937 933 if s == 'qbase':
938 934 return self.series[0]
939 935 return None
940 936
941 937 if patch is None:
942 938 return None
943 939 if patch in self.series:
944 940 return patch
945 941
946 942 if not os.path.isfile(self.join(patch)):
947 943 try:
948 944 sno = int(patch)
949 945 except (ValueError, OverflowError):
950 946 pass
951 947 else:
952 948 if -len(self.series) <= sno < len(self.series):
953 949 return self.series[sno]
954 950
955 951 if not strict:
956 952 res = partial_name(patch)
957 953 if res:
958 954 return res
959 955 minus = patch.rfind('-')
960 956 if minus >= 0:
961 957 res = partial_name(patch[:minus])
962 958 if res:
963 959 i = self.series.index(res)
964 960 try:
965 961 off = int(patch[minus + 1:] or 1)
966 962 except (ValueError, OverflowError):
967 963 pass
968 964 else:
969 965 if i - off >= 0:
970 966 return self.series[i - off]
971 967 plus = patch.rfind('+')
972 968 if plus >= 0:
973 969 res = partial_name(patch[:plus])
974 970 if res:
975 971 i = self.series.index(res)
976 972 try:
977 973 off = int(patch[plus + 1:] or 1)
978 974 except (ValueError, OverflowError):
979 975 pass
980 976 else:
981 977 if i + off < len(self.series):
982 978 return self.series[i + off]
983 979 raise util.Abort(_("patch %s not in series") % patch)
984 980
985 981 def push(self, repo, patch=None, force=False, list=False,
986 982 mergeq=None, all=False):
987 983 diffopts = self.diffopts()
988 984 wlock = repo.wlock()
989 985 try:
990 986 heads = []
991 987 for b, ls in repo.branchmap().iteritems():
992 988 heads += ls
993 989 if not heads:
994 990 heads = [nullid]
995 991 if repo.dirstate.parents()[0] not in heads:
996 992 self.ui.status(_("(working directory not at a head)\n"))
997 993
998 994 if not self.series:
999 995 self.ui.warn(_('no patches in series\n'))
1000 996 return 0
1001 997
1002 998 patch = self.lookup(patch)
1003 999 # Suppose our series file is: A B C and the current 'top'
1004 1000 # patch is B. qpush C should be performed (moving forward)
1005 1001 # qpush B is a NOP (no change) qpush A is an error (can't
1006 1002 # go backwards with qpush)
1007 1003 if patch:
1008 1004 info = self.isapplied(patch)
1009 1005 if info:
1010 1006 if info[0] < len(self.applied) - 1:
1011 1007 raise util.Abort(
1012 1008 _("cannot push to a previous patch: %s") % patch)
1013 1009 self.ui.warn(
1014 1010 _('qpush: %s is already at the top\n') % patch)
1015 1011 return
1016 1012 pushable, reason = self.pushable(patch)
1017 1013 if not pushable:
1018 1014 if reason:
1019 1015 reason = _('guarded by %r') % reason
1020 1016 else:
1021 1017 reason = _('no matching guards')
1022 1018 self.ui.warn(_("cannot push '%s' - %s\n") % (patch, reason))
1023 1019 return 1
1024 1020 elif all:
1025 1021 patch = self.series[-1]
1026 1022 if self.isapplied(patch):
1027 1023 self.ui.warn(_('all patches are currently applied\n'))
1028 1024 return 0
1029 1025
1030 1026 # Following the above example, starting at 'top' of B:
1031 1027 # qpush should be performed (pushes C), but a subsequent
1032 1028 # qpush without an argument is an error (nothing to
1033 1029 # apply). This allows a loop of "...while hg qpush..." to
1034 1030 # work as it detects an error when done
1035 1031 start = self.series_end()
1036 1032 if start == len(self.series):
1037 1033 self.ui.warn(_('patch series already fully applied\n'))
1038 1034 return 1
1039 1035 if not force:
1040 1036 self.check_localchanges(repo)
1041 1037
1042 1038 self.applied_dirty = 1
1043 1039 if start > 0:
1044 1040 self.check_toppatch(repo)
1045 1041 if not patch:
1046 1042 patch = self.series[start]
1047 1043 end = start + 1
1048 1044 else:
1049 1045 end = self.series.index(patch, start) + 1
1050 1046
1051 1047 s = self.series[start:end]
1052 1048 all_files = {}
1053 1049 try:
1054 1050 if mergeq:
1055 1051 ret = self.mergepatch(repo, mergeq, s, diffopts)
1056 1052 else:
1057 1053 ret = self.apply(repo, s, list, all_files=all_files)
1058 1054 except:
1059 1055 self.ui.warn(_('cleaning up working directory...'))
1060 1056 node = repo.dirstate.parents()[0]
1061 1057 hg.revert(repo, node, None)
1062 1058 unknown = repo.status(unknown=True)[4]
1063 1059 # only remove unknown files that we know we touched or
1064 1060 # created while patching
1065 1061 for f in unknown:
1066 1062 if f in all_files:
1067 1063 util.unlink(repo.wjoin(f))
1068 1064 self.ui.warn(_('done\n'))
1069 1065 raise
1070 1066
1071 1067 if not self.applied:
1072 1068 return ret[0]
1073 1069 top = self.applied[-1].name
1074 1070 if ret[0] and ret[0] > 1:
1075 1071 msg = _("errors during apply, please fix and refresh %s\n")
1076 1072 self.ui.write(msg % top)
1077 1073 else:
1078 1074 self.ui.write(_("now at: %s\n") % top)
1079 1075 return ret[0]
1080 1076
1081 1077 finally:
1082 1078 wlock.release()
1083 1079
1084 1080 def pop(self, repo, patch=None, force=False, update=True, all=False):
1085 1081 def getfile(f, rev, flags):
1086 1082 t = repo.file(f).read(rev)
1087 1083 repo.wwrite(f, t, flags)
1088 1084
1089 1085 wlock = repo.wlock()
1090 1086 try:
1091 1087 if patch:
1092 1088 # index, rev, patch
1093 1089 info = self.isapplied(patch)
1094 1090 if not info:
1095 1091 patch = self.lookup(patch)
1096 1092 info = self.isapplied(patch)
1097 1093 if not info:
1098 1094 raise util.Abort(_("patch %s is not applied") % patch)
1099 1095
1100 1096 if len(self.applied) == 0:
1101 1097 # Allow qpop -a to work repeatedly,
1102 1098 # but not qpop without an argument
1103 1099 self.ui.warn(_("no patches applied\n"))
1104 1100 return not all
1105 1101
1106 1102 if all:
1107 1103 start = 0
1108 1104 elif patch:
1109 1105 start = info[0] + 1
1110 1106 else:
1111 1107 start = len(self.applied) - 1
1112 1108
1113 1109 if start >= len(self.applied):
1114 1110 self.ui.warn(_("qpop: %s is already at the top\n") % patch)
1115 1111 return
1116 1112
1117 1113 if not update:
1118 1114 parents = repo.dirstate.parents()
1119 1115 rr = [bin(x.rev) for x in self.applied]
1120 1116 for p in parents:
1121 1117 if p in rr:
1122 1118 self.ui.warn(_("qpop: forcing dirstate update\n"))
1123 1119 update = True
1124 1120 else:
1125 1121 parents = [p.hex() for p in repo[None].parents()]
1126 1122 needupdate = False
1127 1123 for entry in self.applied[start:]:
1128 1124 if entry.rev in parents:
1129 1125 needupdate = True
1130 1126 break
1131 1127 update = needupdate
1132 1128
1133 1129 if not force and update:
1134 1130 self.check_localchanges(repo)
1135 1131
1136 1132 self.applied_dirty = 1
1137 1133 end = len(self.applied)
1138 1134 rev = bin(self.applied[start].rev)
1139 1135 if update:
1140 1136 top = self.check_toppatch(repo)[0]
1141 1137
1142 1138 try:
1143 1139 heads = repo.changelog.heads(rev)
1144 1140 except error.LookupError:
1145 1141 node = short(rev)
1146 1142 raise util.Abort(_('trying to pop unknown node %s') % node)
1147 1143
1148 1144 if heads != [bin(self.applied[-1].rev)]:
1149 1145 raise util.Abort(_("popping would remove a revision not "
1150 1146 "managed by this patch queue"))
1151 1147
1152 1148 # we know there are no local changes, so we can make a simplified
1153 1149 # form of hg.update.
1154 1150 if update:
1155 1151 qp = self.qparents(repo, rev)
1156 1152 changes = repo.changelog.read(qp)
1157 1153 mmap = repo.manifest.read(changes[0])
1158 1154 m, a, r, d = repo.status(qp, top)[:4]
1159 1155 if d:
1160 1156 raise util.Abort(_("deletions found between repo revs"))
1161 1157 for f in a:
1162 1158 try:
1163 1159 os.unlink(repo.wjoin(f))
1164 1160 except OSError, e:
1165 1161 if e.errno != errno.ENOENT:
1166 1162 raise
1167 1163 try: os.removedirs(os.path.dirname(repo.wjoin(f)))
1168 1164 except: pass
1169 1165 repo.dirstate.forget(f)
1170 1166 for f in m:
1171 1167 getfile(f, mmap[f], mmap.flags(f))
1172 1168 for f in r:
1173 1169 getfile(f, mmap[f], mmap.flags(f))
1174 1170 for f in m + r:
1175 1171 repo.dirstate.normal(f)
1176 1172 repo.dirstate.setparents(qp, nullid)
1177 1173 for patch in reversed(self.applied[start:end]):
1178 1174 self.ui.status(_("popping %s\n") % patch.name)
1179 1175 del self.applied[start:end]
1180 1176 self.strip(repo, rev, update=False, backup='strip')
1181 1177 if len(self.applied):
1182 1178 self.ui.write(_("now at: %s\n") % self.applied[-1].name)
1183 1179 else:
1184 1180 self.ui.write(_("patch queue now empty\n"))
1185 1181 finally:
1186 1182 wlock.release()
1187 1183
1188 1184 def diff(self, repo, pats, opts):
1189 1185 top, patch = self.check_toppatch(repo)
1190 1186 if not top:
1191 1187 self.ui.write(_("no patches applied\n"))
1192 1188 return
1193 1189 qp = self.qparents(repo, top)
1194 1190 if opts.get('reverse'):
1195 1191 node1, node2 = None, qp
1196 1192 else:
1197 1193 node1, node2 = qp, None
1198 1194 diffopts = self.diffopts(opts, patch)
1199 1195 self.printdiff(repo, diffopts, node1, node2, files=pats, opts=opts)
1200 1196
1201 1197 def refresh(self, repo, pats=None, **opts):
1202 1198 if len(self.applied) == 0:
1203 1199 self.ui.write(_("no patches applied\n"))
1204 1200 return 1
1205 1201 msg = opts.get('msg', '').rstrip()
1206 1202 newuser = opts.get('user')
1207 1203 newdate = opts.get('date')
1208 1204 if newdate:
1209 1205 newdate = '%d %d' % util.parsedate(newdate)
1210 1206 wlock = repo.wlock()
1211 1207
1212 1208 try:
1213 1209 self.check_toppatch(repo)
1214 1210 (top, patchfn) = (self.applied[-1].rev, self.applied[-1].name)
1215 1211 top = bin(top)
1216 1212 if repo.changelog.heads(top) != [top]:
1217 1213 raise util.Abort(_("cannot refresh a revision with children"))
1218 1214
1219 1215 cparents = repo.changelog.parents(top)
1220 1216 patchparent = self.qparents(repo, top)
1221 1217 ph = patchheader(self.join(patchfn))
1222 1218 diffopts = self.diffopts({'git': opts.get('git')}, patchfn)
1223 1219 if msg:
1224 1220 ph.setmessage(msg)
1225 1221 if newuser:
1226 1222 ph.setuser(newuser)
1227 1223 if newdate:
1228 1224 ph.setdate(newdate)
1229 1225
1230 1226 # only commit new patch when write is complete
1231 1227 patchf = self.opener(patchfn, 'w', atomictemp=True)
1232 1228
1233 1229 comments = str(ph)
1234 1230 if comments:
1235 1231 patchf.write(comments)
1236 1232
1237 1233 # update the dirstate in place, strip off the qtip commit
1238 1234 # and then commit.
1239 1235 #
1240 1236 # this should really read:
1241 1237 # mm, dd, aa, aa2 = repo.status(tip, patchparent)[:4]
1242 1238 # but we do it backwards to take advantage of manifest/chlog
1243 1239 # caching against the next repo.status call
1244 1240 mm, aa, dd, aa2 = repo.status(patchparent, top)[:4]
1245 1241 changes = repo.changelog.read(top)
1246 1242 man = repo.manifest.read(changes[0])
1247 1243 aaa = aa[:]
1248 1244 matchfn = cmdutil.match(repo, pats, opts)
1249 1245 # in short mode, we only diff the files included in the
1250 1246 # patch already plus specified files
1251 1247 if opts.get('short'):
1252 1248 # if amending a patch, we start with existing
1253 1249 # files plus specified files - unfiltered
1254 1250 match = cmdutil.matchfiles(repo, mm + aa + dd + matchfn.files())
1255 1251 # filter with inc/exl options
1256 1252 matchfn = cmdutil.match(repo, opts=opts)
1257 1253 else:
1258 1254 match = cmdutil.matchall(repo)
1259 1255 m, a, r, d = repo.status(match=match)[:4]
1260 1256
1261 1257 # we might end up with files that were added between
1262 1258 # qtip and the dirstate parent, but then changed in the
1263 1259 # local dirstate. in this case, we want them to only
1264 1260 # show up in the added section
1265 1261 for x in m:
1266 1262 if x not in aa:
1267 1263 mm.append(x)
1268 1264 # we might end up with files added by the local dirstate that
1269 1265 # were deleted by the patch. In this case, they should only
1270 1266 # show up in the changed section.
1271 1267 for x in a:
1272 1268 if x in dd:
1273 1269 del dd[dd.index(x)]
1274 1270 mm.append(x)
1275 1271 else:
1276 1272 aa.append(x)
1277 1273 # make sure any files deleted in the local dirstate
1278 1274 # are not in the add or change column of the patch
1279 1275 forget = []
1280 1276 for x in d + r:
1281 1277 if x in aa:
1282 1278 del aa[aa.index(x)]
1283 1279 forget.append(x)
1284 1280 continue
1285 1281 elif x in mm:
1286 1282 del mm[mm.index(x)]
1287 1283 dd.append(x)
1288 1284
1289 1285 m = list(set(mm))
1290 1286 r = list(set(dd))
1291 1287 a = list(set(aa))
1292 1288 c = [filter(matchfn, l) for l in (m, a, r)]
1293 1289 match = cmdutil.matchfiles(repo, set(c[0] + c[1] + c[2]))
1294 1290 chunks = patch.diff(repo, patchparent, match=match,
1295 1291 changes=c, opts=diffopts)
1296 1292 for chunk in chunks:
1297 1293 patchf.write(chunk)
1298 1294
1299 1295 try:
1300 1296 if diffopts.git or diffopts.upgrade:
1301 1297 copies = {}
1302 1298 for dst in a:
1303 1299 src = repo.dirstate.copied(dst)
1304 1300 # during qfold, the source file for copies may
1305 1301 # be removed. Treat this as a simple add.
1306 1302 if src is not None and src in repo.dirstate:
1307 1303 copies.setdefault(src, []).append(dst)
1308 1304 repo.dirstate.add(dst)
1309 1305 # remember the copies between patchparent and qtip
1310 1306 for dst in aaa:
1311 1307 f = repo.file(dst)
1312 1308 src = f.renamed(man[dst])
1313 1309 if src:
1314 1310 copies.setdefault(src[0], []).extend(
1315 1311 copies.get(dst, []))
1316 1312 if dst in a:
1317 1313 copies[src[0]].append(dst)
1318 1314 # we can't copy a file created by the patch itself
1319 1315 if dst in copies:
1320 1316 del copies[dst]
1321 1317 for src, dsts in copies.iteritems():
1322 1318 for dst in dsts:
1323 1319 repo.dirstate.copy(src, dst)
1324 1320 else:
1325 1321 for dst in a:
1326 1322 repo.dirstate.add(dst)
1327 1323 # Drop useless copy information
1328 1324 for f in list(repo.dirstate.copies()):
1329 1325 repo.dirstate.copy(None, f)
1330 1326 for f in r:
1331 1327 repo.dirstate.remove(f)
1332 1328 # if the patch excludes a modified file, mark that
1333 1329 # file with mtime=0 so status can see it.
1334 1330 mm = []
1335 1331 for i in xrange(len(m)-1, -1, -1):
1336 1332 if not matchfn(m[i]):
1337 1333 mm.append(m[i])
1338 1334 del m[i]
1339 1335 for f in m:
1340 1336 repo.dirstate.normal(f)
1341 1337 for f in mm:
1342 1338 repo.dirstate.normallookup(f)
1343 1339 for f in forget:
1344 1340 repo.dirstate.forget(f)
1345 1341
1346 1342 if not msg:
1347 1343 if not ph.message:
1348 1344 message = "[mq]: %s\n" % patchfn
1349 1345 else:
1350 1346 message = "\n".join(ph.message)
1351 1347 else:
1352 1348 message = msg
1353 1349
1354 1350 user = ph.user or changes[1]
1355 1351
1356 1352 # assumes strip can roll itself back if interrupted
1357 1353 repo.dirstate.setparents(*cparents)
1358 1354 self.applied.pop()
1359 1355 self.applied_dirty = 1
1360 1356 self.strip(repo, top, update=False,
1361 1357 backup='strip')
1362 1358 except:
1363 1359 repo.dirstate.invalidate()
1364 1360 raise
1365 1361
1366 1362 try:
1367 1363 # might be nice to attempt to roll back strip after this
1368 1364 patchf.rename()
1369 1365 n = repo.commit(message, user, ph.date, match=match,
1370 1366 force=True)
1371 1367 self.applied.append(statusentry(hex(n), patchfn))
1372 1368 except:
1373 1369 ctx = repo[cparents[0]]
1374 1370 repo.dirstate.rebuild(ctx.node(), ctx.manifest())
1375 1371 self.save_dirty()
1376 1372 self.ui.warn(_('refresh interrupted while patch was popped! '
1377 1373 '(revert --all, qpush to recover)\n'))
1378 1374 raise
1379 1375 finally:
1380 1376 wlock.release()
1381 1377 self.removeundo(repo)
1382 1378
1383 1379 def init(self, repo, create=False):
1384 1380 if not create and os.path.isdir(self.path):
1385 1381 raise util.Abort(_("patch queue directory already exists"))
1386 1382 try:
1387 1383 os.mkdir(self.path)
1388 1384 except OSError, inst:
1389 1385 if inst.errno != errno.EEXIST or not create:
1390 1386 raise
1391 1387 if create:
1392 1388 return self.qrepo(create=True)
1393 1389
1394 1390 def unapplied(self, repo, patch=None):
1395 1391 if patch and patch not in self.series:
1396 1392 raise util.Abort(_("patch %s is not in series file") % patch)
1397 1393 if not patch:
1398 1394 start = self.series_end()
1399 1395 else:
1400 1396 start = self.series.index(patch) + 1
1401 1397 unapplied = []
1402 1398 for i in xrange(start, len(self.series)):
1403 1399 pushable, reason = self.pushable(i)
1404 1400 if pushable:
1405 1401 unapplied.append((i, self.series[i]))
1406 1402 self.explain_pushable(i)
1407 1403 return unapplied
1408 1404
1409 1405 def qseries(self, repo, missing=None, start=0, length=None, status=None,
1410 1406 summary=False):
1411 1407 def displayname(pfx, patchname):
1412 1408 if summary:
1413 1409 ph = patchheader(self.join(patchname))
1414 1410 msg = ph.message and ph.message[0] or ''
1415 1411 if self.ui.interactive():
1416 1412 width = util.termwidth() - len(pfx) - len(patchname) - 2
1417 1413 if width > 0:
1418 1414 msg = util.ellipsis(msg, width)
1419 1415 else:
1420 1416 msg = ''
1421 1417 msg = "%s%s: %s" % (pfx, patchname, msg)
1422 1418 else:
1423 1419 msg = pfx + patchname
1424 1420 self.ui.write(msg + '\n')
1425 1421
1426 1422 applied = set([p.name for p in self.applied])
1427 1423 if length is None:
1428 1424 length = len(self.series) - start
1429 1425 if not missing:
1430 1426 if self.ui.verbose:
1431 1427 idxwidth = len(str(start + length - 1))
1432 1428 for i in xrange(start, start + length):
1433 1429 patch = self.series[i]
1434 1430 if patch in applied:
1435 1431 stat = 'A'
1436 1432 elif self.pushable(i)[0]:
1437 1433 stat = 'U'
1438 1434 else:
1439 1435 stat = 'G'
1440 1436 pfx = ''
1441 1437 if self.ui.verbose:
1442 1438 pfx = '%*d %s ' % (idxwidth, i, stat)
1443 1439 elif status and status != stat:
1444 1440 continue
1445 1441 displayname(pfx, patch)
1446 1442 else:
1447 1443 msng_list = []
1448 1444 for root, dirs, files in os.walk(self.path):
1449 1445 d = root[len(self.path) + 1:]
1450 1446 for f in files:
1451 1447 fl = os.path.join(d, f)
1452 1448 if (fl not in self.series and
1453 1449 fl not in (self.status_path, self.series_path,
1454 1450 self.guards_path)
1455 1451 and not fl.startswith('.')):
1456 1452 msng_list.append(fl)
1457 1453 for x in sorted(msng_list):
1458 1454 pfx = self.ui.verbose and ('D ') or ''
1459 1455 displayname(pfx, x)
1460 1456
1461 1457 def issaveline(self, l):
1462 1458 if l.name == '.hg.patches.save.line':
1463 1459 return True
1464 1460
1465 1461 def qrepo(self, create=False):
1466 1462 if create or os.path.isdir(self.join(".hg")):
1467 1463 return hg.repository(self.ui, path=self.path, create=create)
1468 1464
1469 1465 def restore(self, repo, rev, delete=None, qupdate=None):
1470 1466 c = repo.changelog.read(rev)
1471 1467 desc = c[4].strip()
1472 1468 lines = desc.splitlines()
1473 1469 i = 0
1474 1470 datastart = None
1475 1471 series = []
1476 1472 applied = []
1477 1473 qpp = None
1478 1474 for i, line in enumerate(lines):
1479 1475 if line == 'Patch Data:':
1480 1476 datastart = i + 1
1481 1477 elif line.startswith('Dirstate:'):
1482 1478 l = line.rstrip()
1483 1479 l = l[10:].split(' ')
1484 1480 qpp = [bin(x) for x in l]
1485 1481 elif datastart != None:
1486 1482 l = line.rstrip()
1487 1483 se = statusentry(l)
1488 1484 file_ = se.name
1489 1485 if se.rev:
1490 1486 applied.append(se)
1491 1487 else:
1492 1488 series.append(file_)
1493 1489 if datastart is None:
1494 1490 self.ui.warn(_("No saved patch data found\n"))
1495 1491 return 1
1496 1492 self.ui.warn(_("restoring status: %s\n") % lines[0])
1497 1493 self.full_series = series
1498 1494 self.applied = applied
1499 1495 self.parse_series()
1500 1496 self.series_dirty = 1
1501 1497 self.applied_dirty = 1
1502 1498 heads = repo.changelog.heads()
1503 1499 if delete:
1504 1500 if rev not in heads:
1505 1501 self.ui.warn(_("save entry has children, leaving it alone\n"))
1506 1502 else:
1507 1503 self.ui.warn(_("removing save entry %s\n") % short(rev))
1508 1504 pp = repo.dirstate.parents()
1509 1505 if rev in pp:
1510 1506 update = True
1511 1507 else:
1512 1508 update = False
1513 1509 self.strip(repo, rev, update=update, backup='strip')
1514 1510 if qpp:
1515 1511 self.ui.warn(_("saved queue repository parents: %s %s\n") %
1516 1512 (short(qpp[0]), short(qpp[1])))
1517 1513 if qupdate:
1518 1514 self.ui.status(_("queue directory updating\n"))
1519 1515 r = self.qrepo()
1520 1516 if not r:
1521 1517 self.ui.warn(_("Unable to load queue repository\n"))
1522 1518 return 1
1523 1519 hg.clean(r, qpp[0])
1524 1520
1525 1521 def save(self, repo, msg=None):
1526 1522 if len(self.applied) == 0:
1527 1523 self.ui.warn(_("save: no patches applied, exiting\n"))
1528 1524 return 1
1529 1525 if self.issaveline(self.applied[-1]):
1530 1526 self.ui.warn(_("status is already saved\n"))
1531 1527 return 1
1532 1528
1533 1529 ar = [':' + x for x in self.full_series]
1534 1530 if not msg:
1535 1531 msg = _("hg patches saved state")
1536 1532 else:
1537 1533 msg = "hg patches: " + msg.rstrip('\r\n')
1538 1534 r = self.qrepo()
1539 1535 if r:
1540 1536 pp = r.dirstate.parents()
1541 1537 msg += "\nDirstate: %s %s" % (hex(pp[0]), hex(pp[1]))
1542 1538 msg += "\n\nPatch Data:\n"
1543 1539 text = msg + "\n".join([str(x) for x in self.applied]) + '\n' + (ar and
1544 1540 "\n".join(ar) + '\n' or "")
1545 1541 n = repo.commit(text, force=True)
1546 1542 if not n:
1547 1543 self.ui.warn(_("repo commit failed\n"))
1548 1544 return 1
1549 1545 self.applied.append(statusentry(hex(n),'.hg.patches.save.line'))
1550 1546 self.applied_dirty = 1
1551 1547 self.removeundo(repo)
1552 1548
1553 1549 def full_series_end(self):
1554 1550 if len(self.applied) > 0:
1555 1551 p = self.applied[-1].name
1556 1552 end = self.find_series(p)
1557 1553 if end is None:
1558 1554 return len(self.full_series)
1559 1555 return end + 1
1560 1556 return 0
1561 1557
1562 1558 def series_end(self, all_patches=False):
1563 1559 """If all_patches is False, return the index of the next pushable patch
1564 1560 in the series, or the series length. If all_patches is True, return the
1565 1561 index of the first patch past the last applied one.
1566 1562 """
1567 1563 end = 0
1568 1564 def next(start):
1569 1565 if all_patches:
1570 1566 return start
1571 1567 i = start
1572 1568 while i < len(self.series):
1573 1569 p, reason = self.pushable(i)
1574 1570 if p:
1575 1571 break
1576 1572 self.explain_pushable(i)
1577 1573 i += 1
1578 1574 return i
1579 1575 if len(self.applied) > 0:
1580 1576 p = self.applied[-1].name
1581 1577 try:
1582 1578 end = self.series.index(p)
1583 1579 except ValueError:
1584 1580 return 0
1585 1581 return next(end + 1)
1586 1582 return next(end)
1587 1583
1588 1584 def appliedname(self, index):
1589 1585 pname = self.applied[index].name
1590 1586 if not self.ui.verbose:
1591 1587 p = pname
1592 1588 else:
1593 1589 p = str(self.series.index(pname)) + " " + pname
1594 1590 return p
1595 1591
1596 1592 def qimport(self, repo, files, patchname=None, rev=None, existing=None,
1597 1593 force=None, git=False):
1598 1594 def checkseries(patchname):
1599 1595 if patchname in self.series:
1600 1596 raise util.Abort(_('patch %s is already in the series file')
1601 1597 % patchname)
1602 1598 def checkfile(patchname):
1603 1599 if not force and os.path.exists(self.join(patchname)):
1604 1600 raise util.Abort(_('patch "%s" already exists')
1605 1601 % patchname)
1606 1602
1607 1603 if rev:
1608 1604 if files:
1609 1605 raise util.Abort(_('option "-r" not valid when importing '
1610 1606 'files'))
1611 1607 rev = cmdutil.revrange(repo, rev)
1612 1608 rev.sort(reverse=True)
1613 1609 if (len(files) > 1 or len(rev) > 1) and patchname:
1614 1610 raise util.Abort(_('option "-n" not valid when importing multiple '
1615 1611 'patches'))
1616 1612 i = 0
1617 1613 added = []
1618 1614 if rev:
1619 1615 # If mq patches are applied, we can only import revisions
1620 1616 # that form a linear path to qbase.
1621 1617 # Otherwise, they should form a linear path to a head.
1622 1618 heads = repo.changelog.heads(repo.changelog.node(rev[-1]))
1623 1619 if len(heads) > 1:
1624 1620 raise util.Abort(_('revision %d is the root of more than one '
1625 1621 'branch') % rev[-1])
1626 1622 if self.applied:
1627 1623 base = hex(repo.changelog.node(rev[0]))
1628 1624 if base in [n.rev for n in self.applied]:
1629 1625 raise util.Abort(_('revision %d is already managed')
1630 1626 % rev[0])
1631 1627 if heads != [bin(self.applied[-1].rev)]:
1632 1628 raise util.Abort(_('revision %d is not the parent of '
1633 1629 'the queue') % rev[0])
1634 1630 base = repo.changelog.rev(bin(self.applied[0].rev))
1635 1631 lastparent = repo.changelog.parentrevs(base)[0]
1636 1632 else:
1637 1633 if heads != [repo.changelog.node(rev[0])]:
1638 1634 raise util.Abort(_('revision %d has unmanaged children')
1639 1635 % rev[0])
1640 1636 lastparent = None
1641 1637
1642 1638 diffopts = self.diffopts({'git': git})
1643 1639 for r in rev:
1644 1640 p1, p2 = repo.changelog.parentrevs(r)
1645 1641 n = repo.changelog.node(r)
1646 1642 if p2 != nullrev:
1647 1643 raise util.Abort(_('cannot import merge revision %d') % r)
1648 1644 if lastparent and lastparent != r:
1649 1645 raise util.Abort(_('revision %d is not the parent of %d')
1650 1646 % (r, lastparent))
1651 1647 lastparent = p1
1652 1648
1653 1649 if not patchname:
1654 1650 patchname = normname('%d.diff' % r)
1655 1651 self.check_reserved_name(patchname)
1656 1652 checkseries(patchname)
1657 1653 checkfile(patchname)
1658 1654 self.full_series.insert(0, patchname)
1659 1655
1660 1656 patchf = self.opener(patchname, "w")
1661 1657 patch.export(repo, [n], fp=patchf, opts=diffopts)
1662 1658 patchf.close()
1663 1659
1664 1660 se = statusentry(hex(n), patchname)
1665 1661 self.applied.insert(0, se)
1666 1662
1667 1663 added.append(patchname)
1668 1664 patchname = None
1669 1665 self.parse_series()
1670 1666 self.applied_dirty = 1
1671 1667
1672 1668 for filename in files:
1673 1669 if existing:
1674 1670 if filename == '-':
1675 1671 raise util.Abort(_('-e is incompatible with import from -'))
1676 1672 if not patchname:
1677 1673 patchname = normname(filename)
1678 1674 self.check_reserved_name(patchname)
1679 1675 if not os.path.isfile(self.join(patchname)):
1680 1676 raise util.Abort(_("patch %s does not exist") % patchname)
1681 1677 else:
1682 1678 try:
1683 1679 if filename == '-':
1684 1680 if not patchname:
1685 1681 raise util.Abort(
1686 1682 _('need --name to import a patch from -'))
1687 1683 text = sys.stdin.read()
1688 1684 else:
1689 1685 text = url.open(self.ui, filename).read()
1690 1686 except (OSError, IOError):
1691 1687 raise util.Abort(_("unable to read %s") % filename)
1692 1688 if not patchname:
1693 1689 patchname = normname(os.path.basename(filename))
1694 1690 self.check_reserved_name(patchname)
1695 1691 checkfile(patchname)
1696 1692 patchf = self.opener(patchname, "w")
1697 1693 patchf.write(text)
1698 1694 if not force:
1699 1695 checkseries(patchname)
1700 1696 if patchname not in self.series:
1701 1697 index = self.full_series_end() + i
1702 1698 self.full_series[index:index] = [patchname]
1703 1699 self.parse_series()
1704 1700 self.ui.warn(_("adding %s to series file\n") % patchname)
1705 1701 i += 1
1706 1702 added.append(patchname)
1707 1703 patchname = None
1708 1704 self.series_dirty = 1
1709 1705 qrepo = self.qrepo()
1710 1706 if qrepo:
1711 1707 qrepo.add(added)
1712 1708
1713 1709 def delete(ui, repo, *patches, **opts):
1714 1710 """remove patches from queue
1715 1711
1716 1712 The patches must not be applied, and at least one patch is required. With
1717 1713 -k/--keep, the patch files are preserved in the patch directory.
1718 1714
1719 1715 To stop managing a patch and move it into permanent history,
1720 1716 use the qfinish command."""
1721 1717 q = repo.mq
1722 1718 q.delete(repo, patches, opts)
1723 1719 q.save_dirty()
1724 1720 return 0
1725 1721
1726 1722 def applied(ui, repo, patch=None, **opts):
1727 1723 """print the patches already applied"""
1728 1724
1729 1725 q = repo.mq
1730 1726 l = len(q.applied)
1731 1727
1732 1728 if patch:
1733 1729 if patch not in q.series:
1734 1730 raise util.Abort(_("patch %s is not in series file") % patch)
1735 1731 end = q.series.index(patch) + 1
1736 1732 else:
1737 1733 end = q.series_end(True)
1738 1734
1739 1735 if opts.get('last') and not end:
1740 1736 ui.write(_("no patches applied\n"))
1741 1737 return 1
1742 1738 elif opts.get('last') and end == 1:
1743 1739 ui.write(_("only one patch applied\n"))
1744 1740 return 1
1745 1741 elif opts.get('last'):
1746 1742 start = end - 2
1747 1743 end = 1
1748 1744 else:
1749 1745 start = 0
1750 1746
1751 1747 return q.qseries(repo, length=end, start=start, status='A',
1752 1748 summary=opts.get('summary'))
1753 1749
1754 1750 def unapplied(ui, repo, patch=None, **opts):
1755 1751 """print the patches not yet applied"""
1756 1752
1757 1753 q = repo.mq
1758 1754 if patch:
1759 1755 if patch not in q.series:
1760 1756 raise util.Abort(_("patch %s is not in series file") % patch)
1761 1757 start = q.series.index(patch) + 1
1762 1758 else:
1763 1759 start = q.series_end(True)
1764 1760
1765 1761 if start == len(q.series) and opts.get('first'):
1766 1762 ui.write(_("all patches applied\n"))
1767 1763 return 1
1768 1764
1769 1765 length = opts.get('first') and 1 or None
1770 1766 return q.qseries(repo, start=start, length=length, status='U',
1771 1767 summary=opts.get('summary'))
1772 1768
1773 1769 def qimport(ui, repo, *filename, **opts):
1774 1770 """import a patch
1775 1771
1776 1772 The patch is inserted into the series after the last applied
1777 1773 patch. If no patches have been applied, qimport prepends the patch
1778 1774 to the series.
1779 1775
1780 1776 The patch will have the same name as its source file unless you
1781 1777 give it a new one with -n/--name.
1782 1778
1783 1779 You can register an existing patch inside the patch directory with
1784 1780 the -e/--existing flag.
1785 1781
1786 1782 With -f/--force, an existing patch of the same name will be
1787 1783 overwritten.
1788 1784
1789 1785 An existing changeset may be placed under mq control with -r/--rev
1790 1786 (e.g. qimport --rev tip -n patch will place tip under mq control).
1791 1787 With -g/--git, patches imported with --rev will use the git diff
1792 1788 format. See the diffs help topic for information on why this is
1793 1789 important for preserving rename/copy information and permission
1794 1790 changes.
1795 1791
1796 1792 To import a patch from standard input, pass - as the patch file.
1797 1793 When importing from standard input, a patch name must be specified
1798 1794 using the --name flag.
1799 1795 """
1800 1796 q = repo.mq
1801 1797 q.qimport(repo, filename, patchname=opts['name'],
1802 1798 existing=opts['existing'], force=opts['force'], rev=opts['rev'],
1803 1799 git=opts['git'])
1804 1800 q.save_dirty()
1805 1801
1806 1802 if opts.get('push') and not opts.get('rev'):
1807 1803 return q.push(repo, None)
1808 1804 return 0
1809 1805
1810 1806 def init(ui, repo, **opts):
1811 1807 """init a new queue repository (DEPRECATED)
1812 1808
1813 1809 The queue repository is unversioned by default. If
1814 1810 -c/--create-repo is specified, qinit will create a separate nested
1815 1811 repository for patches (qinit -c may also be run later to convert
1816 1812 an unversioned patch repository into a versioned one). You can use
1817 1813 qcommit to commit changes to this queue repository.
1818 1814
1819 1815 This command is deprecated. Without -c, it's implied by other relevant
1820 1816 commands. With -c, use hg -Q init instead."""
1821 1817 q = repo.mq
1822 1818 r = q.init(repo, create=opts['create_repo'])
1823 1819 q.save_dirty()
1824 1820 if r:
1825 1821 if not os.path.exists(r.wjoin('.hgignore')):
1826 1822 fp = r.wopener('.hgignore', 'w')
1827 1823 fp.write('^\\.hg\n')
1828 1824 fp.write('^\\.mq\n')
1829 1825 fp.write('syntax: glob\n')
1830 1826 fp.write('status\n')
1831 1827 fp.write('guards\n')
1832 1828 fp.close()
1833 1829 if not os.path.exists(r.wjoin('series')):
1834 1830 r.wopener('series', 'w').close()
1835 1831 r.add(['.hgignore', 'series'])
1836 1832 commands.add(ui, r)
1837 1833 return 0
1838 1834
1839 1835 def clone(ui, source, dest=None, **opts):
1840 1836 '''clone main and patch repository at same time
1841 1837
1842 1838 If source is local, destination will have no patches applied. If
1843 1839 source is remote, this command can not check if patches are
1844 1840 applied in source, so cannot guarantee that patches are not
1845 1841 applied in destination. If you clone remote repository, be sure
1846 1842 before that it has no patches applied.
1847 1843
1848 1844 Source patch repository is looked for in <src>/.hg/patches by
1849 1845 default. Use -p <url> to change.
1850 1846
1851 1847 The patch directory must be a nested Mercurial repository, as
1852 1848 would be created by qinit -c.
1853 1849 '''
1854 1850 def patchdir(repo):
1855 1851 url = repo.url()
1856 1852 if url.endswith('/'):
1857 1853 url = url[:-1]
1858 1854 return url + '/.hg/patches'
1859 1855 if dest is None:
1860 1856 dest = hg.defaultdest(source)
1861 1857 sr = hg.repository(cmdutil.remoteui(ui, opts), ui.expandpath(source))
1862 1858 if opts['patches']:
1863 1859 patchespath = ui.expandpath(opts['patches'])
1864 1860 else:
1865 1861 patchespath = patchdir(sr)
1866 1862 try:
1867 1863 hg.repository(ui, patchespath)
1868 1864 except error.RepoError:
1869 1865 raise util.Abort(_('versioned patch repository not found'
1870 1866 ' (see qinit -c)'))
1871 1867 qbase, destrev = None, None
1872 1868 if sr.local():
1873 1869 if sr.mq.applied:
1874 1870 qbase = bin(sr.mq.applied[0].rev)
1875 1871 if not hg.islocal(dest):
1876 1872 heads = set(sr.heads())
1877 1873 destrev = list(heads.difference(sr.heads(qbase)))
1878 1874 destrev.append(sr.changelog.parents(qbase)[0])
1879 1875 elif sr.capable('lookup'):
1880 1876 try:
1881 1877 qbase = sr.lookup('qbase')
1882 1878 except error.RepoError:
1883 1879 pass
1884 1880 ui.note(_('cloning main repository\n'))
1885 1881 sr, dr = hg.clone(ui, sr.url(), dest,
1886 1882 pull=opts['pull'],
1887 1883 rev=destrev,
1888 1884 update=False,
1889 1885 stream=opts['uncompressed'])
1890 1886 ui.note(_('cloning patch repository\n'))
1891 1887 hg.clone(ui, opts['patches'] or patchdir(sr), patchdir(dr),
1892 1888 pull=opts['pull'], update=not opts['noupdate'],
1893 1889 stream=opts['uncompressed'])
1894 1890 if dr.local():
1895 1891 if qbase:
1896 1892 ui.note(_('stripping applied patches from destination '
1897 1893 'repository\n'))
1898 1894 dr.mq.strip(dr, qbase, update=False, backup=None)
1899 1895 if not opts['noupdate']:
1900 1896 ui.note(_('updating destination repository\n'))
1901 1897 hg.update(dr, dr.changelog.tip())
1902 1898
1903 1899 def commit(ui, repo, *pats, **opts):
1904 1900 """commit changes in the queue repository (DEPRECATED)
1905 1901
1906 1902 This command is deprecated; use hg -Q commit instead."""
1907 1903 q = repo.mq
1908 1904 r = q.qrepo()
1909 1905 if not r:
1910 1906 raise util.Abort('no queue repository')
1911 1907 commands.commit(r.ui, r, *pats, **opts)
1912 1908
1913 1909 def series(ui, repo, **opts):
1914 1910 """print the entire series file"""
1915 1911 repo.mq.qseries(repo, missing=opts['missing'], summary=opts['summary'])
1916 1912 return 0
1917 1913
1918 1914 def top(ui, repo, **opts):
1919 1915 """print the name of the current patch"""
1920 1916 q = repo.mq
1921 1917 t = q.applied and q.series_end(True) or 0
1922 1918 if t:
1923 1919 return q.qseries(repo, start=t - 1, length=1, status='A',
1924 1920 summary=opts.get('summary'))
1925 1921 else:
1926 1922 ui.write(_("no patches applied\n"))
1927 1923 return 1
1928 1924
1929 1925 def next(ui, repo, **opts):
1930 1926 """print the name of the next patch"""
1931 1927 q = repo.mq
1932 1928 end = q.series_end()
1933 1929 if end == len(q.series):
1934 1930 ui.write(_("all patches applied\n"))
1935 1931 return 1
1936 1932 return q.qseries(repo, start=end, length=1, summary=opts.get('summary'))
1937 1933
1938 1934 def prev(ui, repo, **opts):
1939 1935 """print the name of the previous patch"""
1940 1936 q = repo.mq
1941 1937 l = len(q.applied)
1942 1938 if l == 1:
1943 1939 ui.write(_("only one patch applied\n"))
1944 1940 return 1
1945 1941 if not l:
1946 1942 ui.write(_("no patches applied\n"))
1947 1943 return 1
1948 1944 return q.qseries(repo, start=l - 2, length=1, status='A',
1949 1945 summary=opts.get('summary'))
1950 1946
1951 1947 def setupheaderopts(ui, opts):
1952 1948 if not opts.get('user') and opts.get('currentuser'):
1953 1949 opts['user'] = ui.username()
1954 1950 if not opts.get('date') and opts.get('currentdate'):
1955 1951 opts['date'] = "%d %d" % util.makedate()
1956 1952
1957 1953 def new(ui, repo, patch, *args, **opts):
1958 1954 """create a new patch
1959 1955
1960 1956 qnew creates a new patch on top of the currently-applied patch (if
1961 1957 any). It will refuse to run if there are any outstanding changes
1962 1958 unless -f/--force is specified, in which case the patch will be
1963 1959 initialized with them. You may also use -I/--include,
1964 1960 -X/--exclude, and/or a list of files after the patch name to add
1965 1961 only changes to matching files to the new patch, leaving the rest
1966 1962 as uncommitted modifications.
1967 1963
1968 1964 -u/--user and -d/--date can be used to set the (given) user and
1969 1965 date, respectively. -U/--currentuser and -D/--currentdate set user
1970 1966 to current user and date to current date.
1971 1967
1972 1968 -e/--edit, -m/--message or -l/--logfile set the patch header as
1973 1969 well as the commit message. If none is specified, the header is
1974 1970 empty and the commit message is '[mq]: PATCH'.
1975 1971
1976 1972 Use the -g/--git option to keep the patch in the git extended diff
1977 1973 format. Read the diffs help topic for more information on why this
1978 1974 is important for preserving permission changes and copy/rename
1979 1975 information.
1980 1976 """
1981 1977 msg = cmdutil.logmessage(opts)
1982 1978 def getmsg():
1983 1979 return ui.edit(msg, ui.username())
1984 1980 q = repo.mq
1985 1981 opts['msg'] = msg
1986 1982 if opts.get('edit'):
1987 1983 opts['msg'] = getmsg
1988 1984 else:
1989 1985 opts['msg'] = msg
1990 1986 setupheaderopts(ui, opts)
1991 1987 q.new(repo, patch, *args, **opts)
1992 1988 q.save_dirty()
1993 1989 return 0
1994 1990
1995 1991 def refresh(ui, repo, *pats, **opts):
1996 1992 """update the current patch
1997 1993
1998 1994 If any file patterns are provided, the refreshed patch will
1999 1995 contain only the modifications that match those patterns; the
2000 1996 remaining modifications will remain in the working directory.
2001 1997
2002 1998 If -s/--short is specified, files currently included in the patch
2003 1999 will be refreshed just like matched files and remain in the patch.
2004 2000
2005 2001 hg add/remove/copy/rename work as usual, though you might want to
2006 2002 use git-style patches (-g/--git or [diff] git=1) to track copies
2007 2003 and renames. See the diffs help topic for more information on the
2008 2004 git diff format.
2009 2005 """
2010 2006 q = repo.mq
2011 2007 message = cmdutil.logmessage(opts)
2012 2008 if opts['edit']:
2013 2009 if not q.applied:
2014 2010 ui.write(_("no patches applied\n"))
2015 2011 return 1
2016 2012 if message:
2017 2013 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
2018 2014 patch = q.applied[-1].name
2019 2015 ph = patchheader(q.join(patch))
2020 2016 message = ui.edit('\n'.join(ph.message), ph.user or ui.username())
2021 2017 setupheaderopts(ui, opts)
2022 2018 ret = q.refresh(repo, pats, msg=message, **opts)
2023 2019 q.save_dirty()
2024 2020 return ret
2025 2021
2026 2022 def diff(ui, repo, *pats, **opts):
2027 2023 """diff of the current patch and subsequent modifications
2028 2024
2029 2025 Shows a diff which includes the current patch as well as any
2030 2026 changes which have been made in the working directory since the
2031 2027 last refresh (thus showing what the current patch would become
2032 2028 after a qrefresh).
2033 2029
2034 2030 Use 'hg diff' if you only want to see the changes made since the
2035 2031 last qrefresh, or 'hg export qtip' if you want to see changes made
2036 2032 by the current patch without including changes made since the
2037 2033 qrefresh.
2038 2034 """
2039 2035 repo.mq.diff(repo, pats, opts)
2040 2036 return 0
2041 2037
2042 2038 def fold(ui, repo, *files, **opts):
2043 2039 """fold the named patches into the current patch
2044 2040
2045 2041 Patches must not yet be applied. Each patch will be successively
2046 2042 applied to the current patch in the order given. If all the
2047 2043 patches apply successfully, the current patch will be refreshed
2048 2044 with the new cumulative patch, and the folded patches will be
2049 2045 deleted. With -k/--keep, the folded patch files will not be
2050 2046 removed afterwards.
2051 2047
2052 2048 The header for each folded patch will be concatenated with the
2053 2049 current patch header, separated by a line of '* * *'."""
2054 2050
2055 2051 q = repo.mq
2056 2052
2057 2053 if not files:
2058 2054 raise util.Abort(_('qfold requires at least one patch name'))
2059 2055 if not q.check_toppatch(repo)[0]:
2060 2056 raise util.Abort(_('No patches applied'))
2061 2057 q.check_localchanges(repo)
2062 2058
2063 2059 message = cmdutil.logmessage(opts)
2064 2060 if opts['edit']:
2065 2061 if message:
2066 2062 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
2067 2063
2068 2064 parent = q.lookup('qtip')
2069 2065 patches = []
2070 2066 messages = []
2071 2067 for f in files:
2072 2068 p = q.lookup(f)
2073 2069 if p in patches or p == parent:
2074 2070 ui.warn(_('Skipping already folded patch %s') % p)
2075 2071 if q.isapplied(p):
2076 2072 raise util.Abort(_('qfold cannot fold already applied patch %s') % p)
2077 2073 patches.append(p)
2078 2074
2079 2075 for p in patches:
2080 2076 if not message:
2081 2077 ph = patchheader(q.join(p))
2082 2078 if ph.message:
2083 2079 messages.append(ph.message)
2084 2080 pf = q.join(p)
2085 2081 (patchsuccess, files, fuzz) = q.patch(repo, pf)
2086 2082 if not patchsuccess:
2087 2083 raise util.Abort(_('Error folding patch %s') % p)
2088 2084 patch.updatedir(ui, repo, files)
2089 2085
2090 2086 if not message:
2091 2087 ph = patchheader(q.join(parent))
2092 2088 message, user = ph.message, ph.user
2093 2089 for msg in messages:
2094 2090 message.append('* * *')
2095 2091 message.extend(msg)
2096 2092 message = '\n'.join(message)
2097 2093
2098 2094 if opts['edit']:
2099 2095 message = ui.edit(message, user or ui.username())
2100 2096
2101 2097 diffopts = q.patchopts(q.diffopts(), *patches)
2102 2098 q.refresh(repo, msg=message, git=diffopts.git)
2103 2099 q.delete(repo, patches, opts)
2104 2100 q.save_dirty()
2105 2101
2106 2102 def goto(ui, repo, patch, **opts):
2107 2103 '''push or pop patches until named patch is at top of stack'''
2108 2104 q = repo.mq
2109 2105 patch = q.lookup(patch)
2110 2106 if q.isapplied(patch):
2111 2107 ret = q.pop(repo, patch, force=opts['force'])
2112 2108 else:
2113 2109 ret = q.push(repo, patch, force=opts['force'])
2114 2110 q.save_dirty()
2115 2111 return ret
2116 2112
2117 2113 def guard(ui, repo, *args, **opts):
2118 2114 '''set or print guards for a patch
2119 2115
2120 2116 Guards control whether a patch can be pushed. A patch with no
2121 2117 guards is always pushed. A patch with a positive guard ("+foo") is
2122 2118 pushed only if the qselect command has activated it. A patch with
2123 2119 a negative guard ("-foo") is never pushed if the qselect command
2124 2120 has activated it.
2125 2121
2126 2122 With no arguments, print the currently active guards.
2127 2123 With arguments, set guards for the named patch.
2128 2124 NOTE: Specifying negative guards now requires '--'.
2129 2125
2130 2126 To set guards on another patch::
2131 2127
2132 2128 hg qguard -- other.patch +2.6.17 -stable
2133 2129 '''
2134 2130 def status(idx):
2135 2131 guards = q.series_guards[idx] or ['unguarded']
2136 2132 ui.write('%s: %s\n' % (q.series[idx], ' '.join(guards)))
2137 2133 q = repo.mq
2138 2134 patch = None
2139 2135 args = list(args)
2140 2136 if opts['list']:
2141 2137 if args or opts['none']:
2142 2138 raise util.Abort(_('cannot mix -l/--list with options or arguments'))
2143 2139 for i in xrange(len(q.series)):
2144 2140 status(i)
2145 2141 return
2146 2142 if not args or args[0][0:1] in '-+':
2147 2143 if not q.applied:
2148 2144 raise util.Abort(_('no patches applied'))
2149 2145 patch = q.applied[-1].name
2150 2146 if patch is None and args[0][0:1] not in '-+':
2151 2147 patch = args.pop(0)
2152 2148 if patch is None:
2153 2149 raise util.Abort(_('no patch to work with'))
2154 2150 if args or opts['none']:
2155 2151 idx = q.find_series(patch)
2156 2152 if idx is None:
2157 2153 raise util.Abort(_('no patch named %s') % patch)
2158 2154 q.set_guards(idx, args)
2159 2155 q.save_dirty()
2160 2156 else:
2161 2157 status(q.series.index(q.lookup(patch)))
2162 2158
2163 2159 def header(ui, repo, patch=None):
2164 2160 """print the header of the topmost or specified patch"""
2165 2161 q = repo.mq
2166 2162
2167 2163 if patch:
2168 2164 patch = q.lookup(patch)
2169 2165 else:
2170 2166 if not q.applied:
2171 2167 ui.write('no patches applied\n')
2172 2168 return 1
2173 2169 patch = q.lookup('qtip')
2174 2170 ph = patchheader(repo.mq.join(patch))
2175 2171
2176 2172 ui.write('\n'.join(ph.message) + '\n')
2177 2173
2178 2174 def lastsavename(path):
2179 2175 (directory, base) = os.path.split(path)
2180 2176 names = os.listdir(directory)
2181 2177 namere = re.compile("%s.([0-9]+)" % base)
2182 2178 maxindex = None
2183 2179 maxname = None
2184 2180 for f in names:
2185 2181 m = namere.match(f)
2186 2182 if m:
2187 2183 index = int(m.group(1))
2188 2184 if maxindex is None or index > maxindex:
2189 2185 maxindex = index
2190 2186 maxname = f
2191 2187 if maxname:
2192 2188 return (os.path.join(directory, maxname), maxindex)
2193 2189 return (None, None)
2194 2190
2195 2191 def savename(path):
2196 2192 (last, index) = lastsavename(path)
2197 2193 if last is None:
2198 2194 index = 0
2199 2195 newpath = path + ".%d" % (index + 1)
2200 2196 return newpath
2201 2197
2202 2198 def push(ui, repo, patch=None, **opts):
2203 2199 """push the next patch onto the stack
2204 2200
2205 2201 When -f/--force is applied, all local changes in patched files
2206 2202 will be lost.
2207 2203 """
2208 2204 q = repo.mq
2209 2205 mergeq = None
2210 2206
2211 2207 if opts['merge']:
2212 2208 if opts['name']:
2213 2209 newpath = repo.join(opts['name'])
2214 2210 else:
2215 2211 newpath, i = lastsavename(q.path)
2216 2212 if not newpath:
2217 2213 ui.warn(_("no saved queues found, please use -n\n"))
2218 2214 return 1
2219 2215 mergeq = queue(ui, repo.join(""), newpath)
2220 2216 ui.warn(_("merging with queue at: %s\n") % mergeq.path)
2221 2217 ret = q.push(repo, patch, force=opts['force'], list=opts['list'],
2222 2218 mergeq=mergeq, all=opts.get('all'))
2223 2219 return ret
2224 2220
2225 2221 def pop(ui, repo, patch=None, **opts):
2226 2222 """pop the current patch off the stack
2227 2223
2228 2224 By default, pops off the top of the patch stack. If given a patch
2229 2225 name, keeps popping off patches until the named patch is at the
2230 2226 top of the stack.
2231 2227 """
2232 2228 localupdate = True
2233 2229 if opts['name']:
2234 2230 q = queue(ui, repo.join(""), repo.join(opts['name']))
2235 2231 ui.warn(_('using patch queue: %s\n') % q.path)
2236 2232 localupdate = False
2237 2233 else:
2238 2234 q = repo.mq
2239 2235 ret = q.pop(repo, patch, force=opts['force'], update=localupdate,
2240 2236 all=opts['all'])
2241 2237 q.save_dirty()
2242 2238 return ret
2243 2239
2244 2240 def rename(ui, repo, patch, name=None, **opts):
2245 2241 """rename a patch
2246 2242
2247 2243 With one argument, renames the current patch to PATCH1.
2248 2244 With two arguments, renames PATCH1 to PATCH2."""
2249 2245
2250 2246 q = repo.mq
2251 2247
2252 2248 if not name:
2253 2249 name = patch
2254 2250 patch = None
2255 2251
2256 2252 if patch:
2257 2253 patch = q.lookup(patch)
2258 2254 else:
2259 2255 if not q.applied:
2260 2256 ui.write(_('no patches applied\n'))
2261 2257 return
2262 2258 patch = q.lookup('qtip')
2263 2259 absdest = q.join(name)
2264 2260 if os.path.isdir(absdest):
2265 2261 name = normname(os.path.join(name, os.path.basename(patch)))
2266 2262 absdest = q.join(name)
2267 2263 if os.path.exists(absdest):
2268 2264 raise util.Abort(_('%s already exists') % absdest)
2269 2265
2270 2266 if name in q.series:
2271 2267 raise util.Abort(
2272 2268 _('A patch named %s already exists in the series file') % name)
2273 2269
2274 2270 if ui.verbose:
2275 2271 ui.write('renaming %s to %s\n' % (patch, name))
2276 2272 i = q.find_series(patch)
2277 2273 guards = q.guard_re.findall(q.full_series[i])
2278 2274 q.full_series[i] = name + ''.join([' #' + g for g in guards])
2279 2275 q.parse_series()
2280 2276 q.series_dirty = 1
2281 2277
2282 2278 info = q.isapplied(patch)
2283 2279 if info:
2284 2280 q.applied[info[0]] = statusentry(info[1], name)
2285 2281 q.applied_dirty = 1
2286 2282
2287 2283 util.rename(q.join(patch), absdest)
2288 2284 r = q.qrepo()
2289 2285 if r:
2290 2286 wlock = r.wlock()
2291 2287 try:
2292 2288 if r.dirstate[patch] == 'a':
2293 2289 r.dirstate.forget(patch)
2294 2290 r.dirstate.add(name)
2295 2291 else:
2296 2292 if r.dirstate[name] == 'r':
2297 2293 r.undelete([name])
2298 2294 r.copy(patch, name)
2299 2295 r.remove([patch], False)
2300 2296 finally:
2301 2297 wlock.release()
2302 2298
2303 2299 q.save_dirty()
2304 2300
2305 2301 def restore(ui, repo, rev, **opts):
2306 2302 """restore the queue state saved by a revision (DEPRECATED)
2307 2303
2308 2304 This command is deprecated, use rebase --mq instead."""
2309 2305 rev = repo.lookup(rev)
2310 2306 q = repo.mq
2311 2307 q.restore(repo, rev, delete=opts['delete'],
2312 2308 qupdate=opts['update'])
2313 2309 q.save_dirty()
2314 2310 return 0
2315 2311
2316 2312 def save(ui, repo, **opts):
2317 2313 """save current queue state (DEPRECATED)
2318 2314
2319 2315 This command is deprecated, use rebase --mq instead."""
2320 2316 q = repo.mq
2321 2317 message = cmdutil.logmessage(opts)
2322 2318 ret = q.save(repo, msg=message)
2323 2319 if ret:
2324 2320 return ret
2325 2321 q.save_dirty()
2326 2322 if opts['copy']:
2327 2323 path = q.path
2328 2324 if opts['name']:
2329 2325 newpath = os.path.join(q.basepath, opts['name'])
2330 2326 if os.path.exists(newpath):
2331 2327 if not os.path.isdir(newpath):
2332 2328 raise util.Abort(_('destination %s exists and is not '
2333 2329 'a directory') % newpath)
2334 2330 if not opts['force']:
2335 2331 raise util.Abort(_('destination %s exists, '
2336 2332 'use -f to force') % newpath)
2337 2333 else:
2338 2334 newpath = savename(path)
2339 2335 ui.warn(_("copy %s to %s\n") % (path, newpath))
2340 2336 util.copyfiles(path, newpath)
2341 2337 if opts['empty']:
2342 2338 try:
2343 2339 os.unlink(q.join(q.status_path))
2344 2340 except:
2345 2341 pass
2346 2342 return 0
2347 2343
2348 2344 def strip(ui, repo, rev, **opts):
2349 2345 """strip a revision and all its descendants from the repository
2350 2346
2351 2347 If one of the working directory's parent revisions is stripped, the
2352 2348 working directory will be updated to the parent of the stripped
2353 2349 revision.
2354 2350 """
2355 2351 backup = 'all'
2356 2352 if opts['backup']:
2357 2353 backup = 'strip'
2358 2354 elif opts['nobackup']:
2359 2355 backup = 'none'
2360 2356
2361 2357 rev = repo.lookup(rev)
2362 2358 p = repo.dirstate.parents()
2363 2359 cl = repo.changelog
2364 2360 update = True
2365 2361 if p[0] == nullid:
2366 2362 update = False
2367 2363 elif p[1] == nullid and rev != cl.ancestor(p[0], rev):
2368 2364 update = False
2369 2365 elif rev not in (cl.ancestor(p[0], rev), cl.ancestor(p[1], rev)):
2370 2366 update = False
2371 2367
2372 2368 repo.mq.strip(repo, rev, backup=backup, update=update, force=opts['force'])
2373 2369 return 0
2374 2370
2375 2371 def select(ui, repo, *args, **opts):
2376 2372 '''set or print guarded patches to push
2377 2373
2378 2374 Use the qguard command to set or print guards on patch, then use
2379 2375 qselect to tell mq which guards to use. A patch will be pushed if
2380 2376 it has no guards or any positive guards match the currently
2381 2377 selected guard, but will not be pushed if any negative guards
2382 2378 match the current guard. For example::
2383 2379
2384 2380 qguard foo.patch -stable (negative guard)
2385 2381 qguard bar.patch +stable (positive guard)
2386 2382 qselect stable
2387 2383
2388 2384 This activates the "stable" guard. mq will skip foo.patch (because
2389 2385 it has a negative match) but push bar.patch (because it has a
2390 2386 positive match).
2391 2387
2392 2388 With no arguments, prints the currently active guards.
2393 2389 With one argument, sets the active guard.
2394 2390
2395 2391 Use -n/--none to deactivate guards (no other arguments needed).
2396 2392 When no guards are active, patches with positive guards are
2397 2393 skipped and patches with negative guards are pushed.
2398 2394
2399 2395 qselect can change the guards on applied patches. It does not pop
2400 2396 guarded patches by default. Use --pop to pop back to the last
2401 2397 applied patch that is not guarded. Use --reapply (which implies
2402 2398 --pop) to push back to the current patch afterwards, but skip
2403 2399 guarded patches.
2404 2400
2405 2401 Use -s/--series to print a list of all guards in the series file
2406 2402 (no other arguments needed). Use -v for more information.'''
2407 2403
2408 2404 q = repo.mq
2409 2405 guards = q.active()
2410 2406 if args or opts['none']:
2411 2407 old_unapplied = q.unapplied(repo)
2412 2408 old_guarded = [i for i in xrange(len(q.applied)) if
2413 2409 not q.pushable(i)[0]]
2414 2410 q.set_active(args)
2415 2411 q.save_dirty()
2416 2412 if not args:
2417 2413 ui.status(_('guards deactivated\n'))
2418 2414 if not opts['pop'] and not opts['reapply']:
2419 2415 unapplied = q.unapplied(repo)
2420 2416 guarded = [i for i in xrange(len(q.applied))
2421 2417 if not q.pushable(i)[0]]
2422 2418 if len(unapplied) != len(old_unapplied):
2423 2419 ui.status(_('number of unguarded, unapplied patches has '
2424 2420 'changed from %d to %d\n') %
2425 2421 (len(old_unapplied), len(unapplied)))
2426 2422 if len(guarded) != len(old_guarded):
2427 2423 ui.status(_('number of guarded, applied patches has changed '
2428 2424 'from %d to %d\n') %
2429 2425 (len(old_guarded), len(guarded)))
2430 2426 elif opts['series']:
2431 2427 guards = {}
2432 2428 noguards = 0
2433 2429 for gs in q.series_guards:
2434 2430 if not gs:
2435 2431 noguards += 1
2436 2432 for g in gs:
2437 2433 guards.setdefault(g, 0)
2438 2434 guards[g] += 1
2439 2435 if ui.verbose:
2440 2436 guards['NONE'] = noguards
2441 2437 guards = guards.items()
2442 2438 guards.sort(key=lambda x: x[0][1:])
2443 2439 if guards:
2444 2440 ui.note(_('guards in series file:\n'))
2445 2441 for guard, count in guards:
2446 2442 ui.note('%2d ' % count)
2447 2443 ui.write(guard, '\n')
2448 2444 else:
2449 2445 ui.note(_('no guards in series file\n'))
2450 2446 else:
2451 2447 if guards:
2452 2448 ui.note(_('active guards:\n'))
2453 2449 for g in guards:
2454 2450 ui.write(g, '\n')
2455 2451 else:
2456 2452 ui.write(_('no active guards\n'))
2457 2453 reapply = opts['reapply'] and q.applied and q.appliedname(-1)
2458 2454 popped = False
2459 2455 if opts['pop'] or opts['reapply']:
2460 2456 for i in xrange(len(q.applied)):
2461 2457 pushable, reason = q.pushable(i)
2462 2458 if not pushable:
2463 2459 ui.status(_('popping guarded patches\n'))
2464 2460 popped = True
2465 2461 if i == 0:
2466 2462 q.pop(repo, all=True)
2467 2463 else:
2468 2464 q.pop(repo, i - 1)
2469 2465 break
2470 2466 if popped:
2471 2467 try:
2472 2468 if reapply:
2473 2469 ui.status(_('reapplying unguarded patches\n'))
2474 2470 q.push(repo, reapply)
2475 2471 finally:
2476 2472 q.save_dirty()
2477 2473
2478 2474 def finish(ui, repo, *revrange, **opts):
2479 2475 """move applied patches into repository history
2480 2476
2481 2477 Finishes the specified revisions (corresponding to applied
2482 2478 patches) by moving them out of mq control into regular repository
2483 2479 history.
2484 2480
2485 2481 Accepts a revision range or the -a/--applied option. If --applied
2486 2482 is specified, all applied mq revisions are removed from mq
2487 2483 control. Otherwise, the given revisions must be at the base of the
2488 2484 stack of applied patches.
2489 2485
2490 2486 This can be especially useful if your changes have been applied to
2491 2487 an upstream repository, or if you are about to push your changes
2492 2488 to upstream.
2493 2489 """
2494 2490 if not opts['applied'] and not revrange:
2495 2491 raise util.Abort(_('no revisions specified'))
2496 2492 elif opts['applied']:
2497 2493 revrange = ('qbase:qtip',) + revrange
2498 2494
2499 2495 q = repo.mq
2500 2496 if not q.applied:
2501 2497 ui.status(_('no patches applied\n'))
2502 2498 return 0
2503 2499
2504 2500 revs = cmdutil.revrange(repo, revrange)
2505 2501 q.finish(repo, revs)
2506 2502 q.save_dirty()
2507 2503 return 0
2508 2504
2509 2505 def reposetup(ui, repo):
2510 2506 class mqrepo(repo.__class__):
2511 2507 @util.propertycache
2512 2508 def mq(self):
2513 2509 return queue(self.ui, self.join(""))
2514 2510
2515 2511 def abort_if_wdir_patched(self, errmsg, force=False):
2516 2512 if self.mq.applied and not force:
2517 2513 parent = hex(self.dirstate.parents()[0])
2518 2514 if parent in [s.rev for s in self.mq.applied]:
2519 2515 raise util.Abort(errmsg)
2520 2516
2521 2517 def commit(self, text="", user=None, date=None, match=None,
2522 2518 force=False, editor=False, extra={}):
2523 2519 self.abort_if_wdir_patched(
2524 2520 _('cannot commit over an applied mq patch'),
2525 2521 force)
2526 2522
2527 2523 return super(mqrepo, self).commit(text, user, date, match, force,
2528 2524 editor, extra)
2529 2525
2530 2526 def push(self, remote, force=False, revs=None):
2531 2527 if self.mq.applied and not force and not revs:
2532 2528 raise util.Abort(_('source has mq patches applied'))
2533 2529 return super(mqrepo, self).push(remote, force, revs)
2534 2530
2535 2531 def _findtags(self):
2536 2532 '''augment tags from base class with patch tags'''
2537 2533 result = super(mqrepo, self)._findtags()
2538 2534
2539 2535 q = self.mq
2540 2536 if not q.applied:
2541 2537 return result
2542 2538
2543 2539 mqtags = [(bin(patch.rev), patch.name) for patch in q.applied]
2544 2540
2545 2541 if mqtags[-1][0] not in self.changelog.nodemap:
2546 2542 self.ui.warn(_('mq status file refers to unknown node %s\n')
2547 2543 % short(mqtags[-1][0]))
2548 2544 return result
2549 2545
2550 2546 mqtags.append((mqtags[-1][0], 'qtip'))
2551 2547 mqtags.append((mqtags[0][0], 'qbase'))
2552 2548 mqtags.append((self.changelog.parents(mqtags[0][0])[0], 'qparent'))
2553 2549 tags = result[0]
2554 2550 for patch in mqtags:
2555 2551 if patch[1] in tags:
2556 2552 self.ui.warn(_('Tag %s overrides mq patch of the same name\n')
2557 2553 % patch[1])
2558 2554 else:
2559 2555 tags[patch[1]] = patch[0]
2560 2556
2561 2557 return result
2562 2558
2563 2559 def _branchtags(self, partial, lrev):
2564 2560 q = self.mq
2565 2561 if not q.applied:
2566 2562 return super(mqrepo, self)._branchtags(partial, lrev)
2567 2563
2568 2564 cl = self.changelog
2569 2565 qbasenode = bin(q.applied[0].rev)
2570 2566 if qbasenode not in cl.nodemap:
2571 2567 self.ui.warn(_('mq status file refers to unknown node %s\n')
2572 2568 % short(qbasenode))
2573 2569 return super(mqrepo, self)._branchtags(partial, lrev)
2574 2570
2575 2571 qbase = cl.rev(qbasenode)
2576 2572 start = lrev + 1
2577 2573 if start < qbase:
2578 2574 # update the cache (excluding the patches) and save it
2579 2575 self._updatebranchcache(partial, lrev + 1, qbase)
2580 2576 self._writebranchcache(partial, cl.node(qbase - 1), qbase - 1)
2581 2577 start = qbase
2582 2578 # if start = qbase, the cache is as updated as it should be.
2583 2579 # if start > qbase, the cache includes (part of) the patches.
2584 2580 # we might as well use it, but we won't save it.
2585 2581
2586 2582 # update the cache up to the tip
2587 2583 self._updatebranchcache(partial, start, len(cl))
2588 2584
2589 2585 return partial
2590 2586
2591 2587 if repo.local():
2592 2588 repo.__class__ = mqrepo
2593 2589
2594 2590 def mqimport(orig, ui, repo, *args, **kwargs):
2595 2591 if (hasattr(repo, 'abort_if_wdir_patched')
2596 2592 and not kwargs.get('no_commit', False)):
2597 2593 repo.abort_if_wdir_patched(_('cannot import over an applied patch'),
2598 2594 kwargs.get('force'))
2599 2595 return orig(ui, repo, *args, **kwargs)
2600 2596
2601 2597 def mqcommand(orig, ui, repo, *args, **kwargs):
2602 2598 """Add --mq option to operate on patch repository instead of main"""
2603 2599
2604 2600 # some commands do not like getting unknown options
2605 2601 mq = kwargs['mq']
2606 2602 del kwargs['mq']
2607 2603
2608 2604 if not mq:
2609 2605 return orig(ui, repo, *args, **kwargs)
2610 2606
2611 2607 q = repo.mq
2612 2608 r = q.qrepo()
2613 2609 if not r:
2614 2610 raise util.Abort('no queue repository')
2615 2611 return orig(ui, r, *args, **kwargs)
2616 2612
2617 2613 def uisetup(ui):
2618 2614 extensions.wrapcommand(commands.table, 'import', mqimport)
2619 2615 for cmd in ('commit', 'export', 'incoming', 'log', 'outgoing', 'pull',
2620 2616 'push', 'status', 'tag', 'tags', 'tip', 'update'):
2621 2617 entry = extensions.wrapcommand(commands.table, cmd, mqcommand)
2622 2618 entry[1].extend([('Q', 'mq', None, _("operate on patch repository"))])
2623 2619
2624 2620 seriesopts = [('s', 'summary', None, _('print first line of patch header'))]
2625 2621
2626 2622 cmdtable = {
2627 2623 "qapplied":
2628 2624 (applied,
2629 2625 [('1', 'last', None, _('show only the last patch'))] + seriesopts,
2630 2626 _('hg qapplied [-1] [-s] [PATCH]')),
2631 2627 "qclone":
2632 2628 (clone,
2633 2629 [('', 'pull', None, _('use pull protocol to copy metadata')),
2634 2630 ('U', 'noupdate', None, _('do not update the new working directories')),
2635 2631 ('', 'uncompressed', None,
2636 2632 _('use uncompressed transfer (fast over LAN)')),
2637 2633 ('p', 'patches', '', _('location of source patch repository')),
2638 2634 ] + commands.remoteopts,
2639 2635 _('hg qclone [OPTION]... SOURCE [DEST]')),
2640 2636 "qcommit|qci":
2641 2637 (commit,
2642 2638 commands.table["^commit|ci"][1],
2643 2639 _('hg qcommit [OPTION]... [FILE]...')),
2644 2640 "^qdiff":
2645 2641 (diff,
2646 2642 commands.diffopts + commands.diffopts2 + commands.walkopts,
2647 2643 _('hg qdiff [OPTION]... [FILE]...')),
2648 2644 "qdelete|qremove|qrm":
2649 2645 (delete,
2650 2646 [('k', 'keep', None, _('keep patch file')),
2651 2647 ('r', 'rev', [], _('stop managing a revision (DEPRECATED)'))],
2652 2648 _('hg qdelete [-k] [-r REV]... [PATCH]...')),
2653 2649 'qfold':
2654 2650 (fold,
2655 2651 [('e', 'edit', None, _('edit patch header')),
2656 2652 ('k', 'keep', None, _('keep folded patch files')),
2657 2653 ] + commands.commitopts,
2658 2654 _('hg qfold [-e] [-k] [-m TEXT] [-l FILE] PATCH...')),
2659 2655 'qgoto':
2660 2656 (goto,
2661 2657 [('f', 'force', None, _('overwrite any local changes'))],
2662 2658 _('hg qgoto [OPTION]... PATCH')),
2663 2659 'qguard':
2664 2660 (guard,
2665 2661 [('l', 'list', None, _('list all patches and guards')),
2666 2662 ('n', 'none', None, _('drop all guards'))],
2667 2663 _('hg qguard [-l] [-n] -- [PATCH] [+GUARD]... [-GUARD]...')),
2668 2664 'qheader': (header, [], _('hg qheader [PATCH]')),
2669 2665 "^qimport":
2670 2666 (qimport,
2671 2667 [('e', 'existing', None, _('import file in patch directory')),
2672 2668 ('n', 'name', '', _('name of patch file')),
2673 2669 ('f', 'force', None, _('overwrite existing files')),
2674 2670 ('r', 'rev', [], _('place existing revisions under mq control')),
2675 2671 ('g', 'git', None, _('use git extended diff format')),
2676 2672 ('P', 'push', None, _('qpush after importing'))],
2677 2673 _('hg qimport [-e] [-n NAME] [-f] [-g] [-P] [-r REV]... FILE...')),
2678 2674 "^qinit":
2679 2675 (init,
2680 2676 [('c', 'create-repo', None, _('create queue repository'))],
2681 2677 _('hg qinit [-c]')),
2682 2678 "qnew":
2683 2679 (new,
2684 2680 [('e', 'edit', None, _('edit commit message')),
2685 ('f', 'force', None, _('import uncommitted changes into patch')),
2681 ('f', 'force', None, _('import uncommitted changes into patch (deprecated)')),
2686 2682 ('g', 'git', None, _('use git extended diff format')),
2687 2683 ('U', 'currentuser', None, _('add "From: <current user>" to patch')),
2688 2684 ('u', 'user', '', _('add "From: <given user>" to patch')),
2689 2685 ('D', 'currentdate', None, _('add "Date: <current date>" to patch')),
2690 2686 ('d', 'date', '', _('add "Date: <given date>" to patch'))
2691 2687 ] + commands.walkopts + commands.commitopts,
2692 2688 _('hg qnew [-e] [-m TEXT] [-l FILE] [-f] PATCH [FILE]...')),
2693 2689 "qnext": (next, [] + seriesopts, _('hg qnext [-s]')),
2694 2690 "qprev": (prev, [] + seriesopts, _('hg qprev [-s]')),
2695 2691 "^qpop":
2696 2692 (pop,
2697 2693 [('a', 'all', None, _('pop all patches')),
2698 2694 ('n', 'name', '', _('queue name to pop (DEPRECATED)')),
2699 2695 ('f', 'force', None, _('forget any local changes to patched files'))],
2700 2696 _('hg qpop [-a] [-n NAME] [-f] [PATCH | INDEX]')),
2701 2697 "^qpush":
2702 2698 (push,
2703 2699 [('f', 'force', None, _('apply if the patch has rejects')),
2704 2700 ('l', 'list', None, _('list patch name in commit text')),
2705 2701 ('a', 'all', None, _('apply all patches')),
2706 2702 ('m', 'merge', None, _('merge from another queue (DEPRECATED)')),
2707 2703 ('n', 'name', '', _('merge queue name (DEPRECATED)'))],
2708 2704 _('hg qpush [-f] [-l] [-a] [-m] [-n NAME] [PATCH | INDEX]')),
2709 2705 "^qrefresh":
2710 2706 (refresh,
2711 2707 [('e', 'edit', None, _('edit commit message')),
2712 2708 ('g', 'git', None, _('use git extended diff format')),
2713 2709 ('s', 'short', None,
2714 2710 _('refresh only files already in the patch and specified files')),
2715 2711 ('U', 'currentuser', None,
2716 2712 _('add/update author field in patch with current user')),
2717 2713 ('u', 'user', '',
2718 2714 _('add/update author field in patch with given user')),
2719 2715 ('D', 'currentdate', None,
2720 2716 _('add/update date field in patch with current date')),
2721 2717 ('d', 'date', '',
2722 2718 _('add/update date field in patch with given date'))
2723 2719 ] + commands.walkopts + commands.commitopts,
2724 2720 _('hg qrefresh [-I] [-X] [-e] [-m TEXT] [-l FILE] [-s] [FILE]...')),
2725 2721 'qrename|qmv':
2726 2722 (rename, [], _('hg qrename PATCH1 [PATCH2]')),
2727 2723 "qrestore":
2728 2724 (restore,
2729 2725 [('d', 'delete', None, _('delete save entry')),
2730 2726 ('u', 'update', None, _('update queue working directory'))],
2731 2727 _('hg qrestore [-d] [-u] REV')),
2732 2728 "qsave":
2733 2729 (save,
2734 2730 [('c', 'copy', None, _('copy patch directory')),
2735 2731 ('n', 'name', '', _('copy directory name')),
2736 2732 ('e', 'empty', None, _('clear queue status file')),
2737 2733 ('f', 'force', None, _('force copy'))] + commands.commitopts,
2738 2734 _('hg qsave [-m TEXT] [-l FILE] [-c] [-n NAME] [-e] [-f]')),
2739 2735 "qselect":
2740 2736 (select,
2741 2737 [('n', 'none', None, _('disable all guards')),
2742 2738 ('s', 'series', None, _('list all guards in series file')),
2743 2739 ('', 'pop', None, _('pop to before first guarded applied patch')),
2744 2740 ('', 'reapply', None, _('pop, then reapply patches'))],
2745 2741 _('hg qselect [OPTION]... [GUARD]...')),
2746 2742 "qseries":
2747 2743 (series,
2748 2744 [('m', 'missing', None, _('print patches not in series')),
2749 2745 ] + seriesopts,
2750 2746 _('hg qseries [-ms]')),
2751 2747 "^strip":
2752 2748 (strip,
2753 2749 [('f', 'force', None, _('force removal with local changes')),
2754 2750 ('b', 'backup', None, _('bundle unrelated changesets')),
2755 2751 ('n', 'nobackup', None, _('no backups'))],
2756 2752 _('hg strip [-f] [-b] [-n] REV')),
2757 2753 "qtop": (top, [] + seriesopts, _('hg qtop [-s]')),
2758 2754 "qunapplied":
2759 2755 (unapplied,
2760 2756 [('1', 'first', None, _('show only the first patch'))] + seriesopts,
2761 2757 _('hg qunapplied [-1] [-s] [PATCH]')),
2762 2758 "qfinish":
2763 2759 (finish,
2764 2760 [('a', 'applied', None, _('finish all applied changesets'))],
2765 2761 _('hg qfinish [-a] [REV]...')),
2766 2762 }
@@ -1,76 +1,73
1 1 #!/bin/sh
2 2
3 3 echo "[extensions]" >> $HGRCPATH
4 4 echo "mq=" >> $HGRCPATH
5 5
6 6 hg init mq
7 7 cd mq
8 8
9 9 echo a > a
10 10 hg ci -Ama
11 11
12 12 echo '% qnew should refuse bad patch names'
13 13 hg qnew series
14 14 hg qnew status
15 15 hg qnew guards
16 16 hg qnew .hgignore
17 17
18 18 hg qinit -c
19 19
20 20 echo '% qnew with uncommitted changes'
21 21 echo a > somefile
22 22 hg add somefile
23 23 hg qnew uncommitted.patch
24 24 hg st
25 25 hg qseries
26 hg revert --no-backup somefile
27 rm somefile
28 26
29 27 echo '% qnew implies add'
30 hg qnew test.patch
31 28 hg -R .hg/patches st
32 29
33 30 echo '% qnew missing'
34 31 hg qnew missing.patch missing
35 32
36 33 echo '% qnew -m'
37 34 hg qnew -m 'foo bar' mtest.patch
38 35 cat .hg/patches/mtest.patch
39 36
40 37 echo '% qnew twice'
41 38 hg qnew first.patch
42 39 hg qnew first.patch
43 40
44 41 touch ../first.patch
45 42 hg qimport ../first.patch
46 43
47 44 echo '% qnew -f from a subdirectory'
48 45 hg qpop -a
49 46 mkdir d
50 47 cd d
51 48 echo b > b
52 49 hg ci -Am t
53 50 echo b >> b
54 51 hg st
55 52 hg qnew -g -f p
56 53 cat ../.hg/patches/p
57 54
58 55 echo '% qnew -u with no username configured'
59 56 HGUSER= hg qnew -u blue red
60 57 cat ../.hg/patches/red
61 58
62 59 echo '% fail when trying to import a merge'
63 60 hg init merge
64 61 cd merge
65 62 touch a
66 63 hg ci -Am null
67 64 echo a >> a
68 65 hg ci -m a
69 66 hg up -r 0
70 67 echo b >> a
71 68 hg ci -m b
72 69 hg merge -f 1
73 70 hg resolve --mark a
74 71 hg qnew -f merge
75 72
76 73 exit 0
@@ -1,47 +1,46
1 1 adding a
2 2 % qnew should refuse bad patch names
3 3 abort: "series" cannot be used as the name of a patch
4 4 abort: "status" cannot be used as the name of a patch
5 5 abort: "guards" cannot be used as the name of a patch
6 6 abort: ".hgignore" cannot be used as the name of a patch
7 7 % qnew with uncommitted changes
8 abort: local changes found, refresh first
9 A somefile
8 uncommitted.patch
10 9 % qnew implies add
11 10 A .hgignore
12 11 A series
13 A test.patch
12 A uncommitted.patch
14 13 % qnew missing
15 14 abort: missing: No such file or directory
16 15 % qnew -m
17 16 foo bar
18 17
19 18 % qnew twice
20 19 abort: patch "first.patch" already exists
21 20 abort: patch "first.patch" already exists
22 21 % qnew -f from a subdirectory
23 22 popping first.patch
24 23 popping mtest.patch
25 popping test.patch
24 popping uncommitted.patch
26 25 patch queue now empty
27 26 adding d/b
28 27 M d/b
29 28 diff --git a/d/b b/d/b
30 29 --- a/d/b
31 30 +++ b/d/b
32 31 @@ -1,1 +1,2 @@
33 32 b
34 33 +b
35 34 % qnew -u with no username configured
36 35 From: blue
37 36
38 37 % fail when trying to import a merge
39 38 adding a
40 39 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
41 40 created new head
42 41 merging a
43 42 warning: conflicts during merge.
44 43 merging a failed!
45 44 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
46 45 use 'hg resolve' to retry unresolved file merges or 'hg update -C' to abandon
47 46 abort: cannot manage merge changesets
General Comments 0
You need to be logged in to leave comments. Login now