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