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