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