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