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