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