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