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