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