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