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