##// END OF EJS Templates
scmutil: add a cleanupnodes method for developers...
Jun Wu -
r33088:65cadeea default
parent child Browse files
Show More
@@ -1,996 +1,1061 b''
1 # scmutil.py - Mercurial core utility functions
1 # scmutil.py - Mercurial core utility functions
2 #
2 #
3 # Copyright Matt Mackall <mpm@selenic.com>
3 # Copyright Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import errno
10 import errno
11 import glob
11 import glob
12 import hashlib
12 import hashlib
13 import os
13 import os
14 import re
14 import re
15 import socket
15 import socket
16
16
17 from .i18n import _
17 from .i18n import _
18 from .node import (
18 from .node import (
19 hex,
20 nullid,
19 wdirid,
21 wdirid,
20 wdirrev,
22 wdirrev,
21 )
23 )
22
24
23 from . import (
25 from . import (
24 encoding,
26 encoding,
25 error,
27 error,
26 match as matchmod,
28 match as matchmod,
29 obsolete,
27 pathutil,
30 pathutil,
28 phases,
31 phases,
29 pycompat,
32 pycompat,
30 revsetlang,
33 revsetlang,
31 similar,
34 similar,
32 util,
35 util,
33 )
36 )
34
37
35 if pycompat.osname == 'nt':
38 if pycompat.osname == 'nt':
36 from . import scmwindows as scmplatform
39 from . import scmwindows as scmplatform
37 else:
40 else:
38 from . import scmposix as scmplatform
41 from . import scmposix as scmplatform
39
42
40 termsize = scmplatform.termsize
43 termsize = scmplatform.termsize
41
44
42 class status(tuple):
45 class status(tuple):
43 '''Named tuple with a list of files per status. The 'deleted', 'unknown'
46 '''Named tuple with a list of files per status. The 'deleted', 'unknown'
44 and 'ignored' properties are only relevant to the working copy.
47 and 'ignored' properties are only relevant to the working copy.
45 '''
48 '''
46
49
47 __slots__ = ()
50 __slots__ = ()
48
51
49 def __new__(cls, modified, added, removed, deleted, unknown, ignored,
52 def __new__(cls, modified, added, removed, deleted, unknown, ignored,
50 clean):
53 clean):
51 return tuple.__new__(cls, (modified, added, removed, deleted, unknown,
54 return tuple.__new__(cls, (modified, added, removed, deleted, unknown,
52 ignored, clean))
55 ignored, clean))
53
56
54 @property
57 @property
55 def modified(self):
58 def modified(self):
56 '''files that have been modified'''
59 '''files that have been modified'''
57 return self[0]
60 return self[0]
58
61
59 @property
62 @property
60 def added(self):
63 def added(self):
61 '''files that have been added'''
64 '''files that have been added'''
62 return self[1]
65 return self[1]
63
66
64 @property
67 @property
65 def removed(self):
68 def removed(self):
66 '''files that have been removed'''
69 '''files that have been removed'''
67 return self[2]
70 return self[2]
68
71
69 @property
72 @property
70 def deleted(self):
73 def deleted(self):
71 '''files that are in the dirstate, but have been deleted from the
74 '''files that are in the dirstate, but have been deleted from the
72 working copy (aka "missing")
75 working copy (aka "missing")
73 '''
76 '''
74 return self[3]
77 return self[3]
75
78
76 @property
79 @property
77 def unknown(self):
80 def unknown(self):
78 '''files not in the dirstate that are not ignored'''
81 '''files not in the dirstate that are not ignored'''
79 return self[4]
82 return self[4]
80
83
81 @property
84 @property
82 def ignored(self):
85 def ignored(self):
83 '''files not in the dirstate that are ignored (by _dirignore())'''
86 '''files not in the dirstate that are ignored (by _dirignore())'''
84 return self[5]
87 return self[5]
85
88
86 @property
89 @property
87 def clean(self):
90 def clean(self):
88 '''files that have not been modified'''
91 '''files that have not been modified'''
89 return self[6]
92 return self[6]
90
93
91 def __repr__(self, *args, **kwargs):
94 def __repr__(self, *args, **kwargs):
92 return (('<status modified=%r, added=%r, removed=%r, deleted=%r, '
95 return (('<status modified=%r, added=%r, removed=%r, deleted=%r, '
93 'unknown=%r, ignored=%r, clean=%r>') % self)
96 'unknown=%r, ignored=%r, clean=%r>') % self)
94
97
95 def itersubrepos(ctx1, ctx2):
98 def itersubrepos(ctx1, ctx2):
96 """find subrepos in ctx1 or ctx2"""
99 """find subrepos in ctx1 or ctx2"""
97 # Create a (subpath, ctx) mapping where we prefer subpaths from
100 # Create a (subpath, ctx) mapping where we prefer subpaths from
98 # ctx1. The subpaths from ctx2 are important when the .hgsub file
101 # ctx1. The subpaths from ctx2 are important when the .hgsub file
99 # has been modified (in ctx2) but not yet committed (in ctx1).
102 # has been modified (in ctx2) but not yet committed (in ctx1).
100 subpaths = dict.fromkeys(ctx2.substate, ctx2)
103 subpaths = dict.fromkeys(ctx2.substate, ctx2)
101 subpaths.update(dict.fromkeys(ctx1.substate, ctx1))
104 subpaths.update(dict.fromkeys(ctx1.substate, ctx1))
102
105
103 missing = set()
106 missing = set()
104
107
105 for subpath in ctx2.substate:
108 for subpath in ctx2.substate:
106 if subpath not in ctx1.substate:
109 if subpath not in ctx1.substate:
107 del subpaths[subpath]
110 del subpaths[subpath]
108 missing.add(subpath)
111 missing.add(subpath)
109
112
110 for subpath, ctx in sorted(subpaths.iteritems()):
113 for subpath, ctx in sorted(subpaths.iteritems()):
111 yield subpath, ctx.sub(subpath)
114 yield subpath, ctx.sub(subpath)
112
115
113 # Yield an empty subrepo based on ctx1 for anything only in ctx2. That way,
116 # Yield an empty subrepo based on ctx1 for anything only in ctx2. That way,
114 # status and diff will have an accurate result when it does
117 # status and diff will have an accurate result when it does
115 # 'sub.{status|diff}(rev2)'. Otherwise, the ctx2 subrepo is compared
118 # 'sub.{status|diff}(rev2)'. Otherwise, the ctx2 subrepo is compared
116 # against itself.
119 # against itself.
117 for subpath in missing:
120 for subpath in missing:
118 yield subpath, ctx2.nullsub(subpath, ctx1)
121 yield subpath, ctx2.nullsub(subpath, ctx1)
119
122
120 def nochangesfound(ui, repo, excluded=None):
123 def nochangesfound(ui, repo, excluded=None):
121 '''Report no changes for push/pull, excluded is None or a list of
124 '''Report no changes for push/pull, excluded is None or a list of
122 nodes excluded from the push/pull.
125 nodes excluded from the push/pull.
123 '''
126 '''
124 secretlist = []
127 secretlist = []
125 if excluded:
128 if excluded:
126 for n in excluded:
129 for n in excluded:
127 ctx = repo[n]
130 ctx = repo[n]
128 if ctx.phase() >= phases.secret and not ctx.extinct():
131 if ctx.phase() >= phases.secret and not ctx.extinct():
129 secretlist.append(n)
132 secretlist.append(n)
130
133
131 if secretlist:
134 if secretlist:
132 ui.status(_("no changes found (ignored %d secret changesets)\n")
135 ui.status(_("no changes found (ignored %d secret changesets)\n")
133 % len(secretlist))
136 % len(secretlist))
134 else:
137 else:
135 ui.status(_("no changes found\n"))
138 ui.status(_("no changes found\n"))
136
139
137 def callcatch(ui, func):
140 def callcatch(ui, func):
138 """call func() with global exception handling
141 """call func() with global exception handling
139
142
140 return func() if no exception happens. otherwise do some error handling
143 return func() if no exception happens. otherwise do some error handling
141 and return an exit code accordingly. does not handle all exceptions.
144 and return an exit code accordingly. does not handle all exceptions.
142 """
145 """
143 try:
146 try:
144 try:
147 try:
145 return func()
148 return func()
146 except: # re-raises
149 except: # re-raises
147 ui.traceback()
150 ui.traceback()
148 raise
151 raise
149 # Global exception handling, alphabetically
152 # Global exception handling, alphabetically
150 # Mercurial-specific first, followed by built-in and library exceptions
153 # Mercurial-specific first, followed by built-in and library exceptions
151 except error.LockHeld as inst:
154 except error.LockHeld as inst:
152 if inst.errno == errno.ETIMEDOUT:
155 if inst.errno == errno.ETIMEDOUT:
153 reason = _('timed out waiting for lock held by %r') % inst.locker
156 reason = _('timed out waiting for lock held by %r') % inst.locker
154 else:
157 else:
155 reason = _('lock held by %r') % inst.locker
158 reason = _('lock held by %r') % inst.locker
156 ui.warn(_("abort: %s: %s\n") % (inst.desc or inst.filename, reason))
159 ui.warn(_("abort: %s: %s\n") % (inst.desc or inst.filename, reason))
157 if not inst.locker:
160 if not inst.locker:
158 ui.warn(_("(lock might be very busy)\n"))
161 ui.warn(_("(lock might be very busy)\n"))
159 except error.LockUnavailable as inst:
162 except error.LockUnavailable as inst:
160 ui.warn(_("abort: could not lock %s: %s\n") %
163 ui.warn(_("abort: could not lock %s: %s\n") %
161 (inst.desc or inst.filename, inst.strerror))
164 (inst.desc or inst.filename, inst.strerror))
162 except error.OutOfBandError as inst:
165 except error.OutOfBandError as inst:
163 if inst.args:
166 if inst.args:
164 msg = _("abort: remote error:\n")
167 msg = _("abort: remote error:\n")
165 else:
168 else:
166 msg = _("abort: remote error\n")
169 msg = _("abort: remote error\n")
167 ui.warn(msg)
170 ui.warn(msg)
168 if inst.args:
171 if inst.args:
169 ui.warn(''.join(inst.args))
172 ui.warn(''.join(inst.args))
170 if inst.hint:
173 if inst.hint:
171 ui.warn('(%s)\n' % inst.hint)
174 ui.warn('(%s)\n' % inst.hint)
172 except error.RepoError as inst:
175 except error.RepoError as inst:
173 ui.warn(_("abort: %s!\n") % inst)
176 ui.warn(_("abort: %s!\n") % inst)
174 if inst.hint:
177 if inst.hint:
175 ui.warn(_("(%s)\n") % inst.hint)
178 ui.warn(_("(%s)\n") % inst.hint)
176 except error.ResponseError as inst:
179 except error.ResponseError as inst:
177 ui.warn(_("abort: %s") % inst.args[0])
180 ui.warn(_("abort: %s") % inst.args[0])
178 if not isinstance(inst.args[1], basestring):
181 if not isinstance(inst.args[1], basestring):
179 ui.warn(" %r\n" % (inst.args[1],))
182 ui.warn(" %r\n" % (inst.args[1],))
180 elif not inst.args[1]:
183 elif not inst.args[1]:
181 ui.warn(_(" empty string\n"))
184 ui.warn(_(" empty string\n"))
182 else:
185 else:
183 ui.warn("\n%r\n" % util.ellipsis(inst.args[1]))
186 ui.warn("\n%r\n" % util.ellipsis(inst.args[1]))
184 except error.CensoredNodeError as inst:
187 except error.CensoredNodeError as inst:
185 ui.warn(_("abort: file censored %s!\n") % inst)
188 ui.warn(_("abort: file censored %s!\n") % inst)
186 except error.RevlogError as inst:
189 except error.RevlogError as inst:
187 ui.warn(_("abort: %s!\n") % inst)
190 ui.warn(_("abort: %s!\n") % inst)
188 except error.InterventionRequired as inst:
191 except error.InterventionRequired as inst:
189 ui.warn("%s\n" % inst)
192 ui.warn("%s\n" % inst)
190 if inst.hint:
193 if inst.hint:
191 ui.warn(_("(%s)\n") % inst.hint)
194 ui.warn(_("(%s)\n") % inst.hint)
192 return 1
195 return 1
193 except error.WdirUnsupported:
196 except error.WdirUnsupported:
194 ui.warn(_("abort: working directory revision cannot be specified\n"))
197 ui.warn(_("abort: working directory revision cannot be specified\n"))
195 except error.Abort as inst:
198 except error.Abort as inst:
196 ui.warn(_("abort: %s\n") % inst)
199 ui.warn(_("abort: %s\n") % inst)
197 if inst.hint:
200 if inst.hint:
198 ui.warn(_("(%s)\n") % inst.hint)
201 ui.warn(_("(%s)\n") % inst.hint)
199 except ImportError as inst:
202 except ImportError as inst:
200 ui.warn(_("abort: %s!\n") % inst)
203 ui.warn(_("abort: %s!\n") % inst)
201 m = str(inst).split()[-1]
204 m = str(inst).split()[-1]
202 if m in "mpatch bdiff".split():
205 if m in "mpatch bdiff".split():
203 ui.warn(_("(did you forget to compile extensions?)\n"))
206 ui.warn(_("(did you forget to compile extensions?)\n"))
204 elif m in "zlib".split():
207 elif m in "zlib".split():
205 ui.warn(_("(is your Python install correct?)\n"))
208 ui.warn(_("(is your Python install correct?)\n"))
206 except IOError as inst:
209 except IOError as inst:
207 if util.safehasattr(inst, "code"):
210 if util.safehasattr(inst, "code"):
208 ui.warn(_("abort: %s\n") % inst)
211 ui.warn(_("abort: %s\n") % inst)
209 elif util.safehasattr(inst, "reason"):
212 elif util.safehasattr(inst, "reason"):
210 try: # usually it is in the form (errno, strerror)
213 try: # usually it is in the form (errno, strerror)
211 reason = inst.reason.args[1]
214 reason = inst.reason.args[1]
212 except (AttributeError, IndexError):
215 except (AttributeError, IndexError):
213 # it might be anything, for example a string
216 # it might be anything, for example a string
214 reason = inst.reason
217 reason = inst.reason
215 if isinstance(reason, unicode):
218 if isinstance(reason, unicode):
216 # SSLError of Python 2.7.9 contains a unicode
219 # SSLError of Python 2.7.9 contains a unicode
217 reason = encoding.unitolocal(reason)
220 reason = encoding.unitolocal(reason)
218 ui.warn(_("abort: error: %s\n") % reason)
221 ui.warn(_("abort: error: %s\n") % reason)
219 elif (util.safehasattr(inst, "args")
222 elif (util.safehasattr(inst, "args")
220 and inst.args and inst.args[0] == errno.EPIPE):
223 and inst.args and inst.args[0] == errno.EPIPE):
221 pass
224 pass
222 elif getattr(inst, "strerror", None):
225 elif getattr(inst, "strerror", None):
223 if getattr(inst, "filename", None):
226 if getattr(inst, "filename", None):
224 ui.warn(_("abort: %s: %s\n") % (inst.strerror, inst.filename))
227 ui.warn(_("abort: %s: %s\n") % (inst.strerror, inst.filename))
225 else:
228 else:
226 ui.warn(_("abort: %s\n") % inst.strerror)
229 ui.warn(_("abort: %s\n") % inst.strerror)
227 else:
230 else:
228 raise
231 raise
229 except OSError as inst:
232 except OSError as inst:
230 if getattr(inst, "filename", None) is not None:
233 if getattr(inst, "filename", None) is not None:
231 ui.warn(_("abort: %s: '%s'\n") % (inst.strerror, inst.filename))
234 ui.warn(_("abort: %s: '%s'\n") % (inst.strerror, inst.filename))
232 else:
235 else:
233 ui.warn(_("abort: %s\n") % inst.strerror)
236 ui.warn(_("abort: %s\n") % inst.strerror)
234 except MemoryError:
237 except MemoryError:
235 ui.warn(_("abort: out of memory\n"))
238 ui.warn(_("abort: out of memory\n"))
236 except SystemExit as inst:
239 except SystemExit as inst:
237 # Commands shouldn't sys.exit directly, but give a return code.
240 # Commands shouldn't sys.exit directly, but give a return code.
238 # Just in case catch this and and pass exit code to caller.
241 # Just in case catch this and and pass exit code to caller.
239 return inst.code
242 return inst.code
240 except socket.error as inst:
243 except socket.error as inst:
241 ui.warn(_("abort: %s\n") % inst.args[-1])
244 ui.warn(_("abort: %s\n") % inst.args[-1])
242
245
243 return -1
246 return -1
244
247
245 def checknewlabel(repo, lbl, kind):
248 def checknewlabel(repo, lbl, kind):
246 # Do not use the "kind" parameter in ui output.
249 # Do not use the "kind" parameter in ui output.
247 # It makes strings difficult to translate.
250 # It makes strings difficult to translate.
248 if lbl in ['tip', '.', 'null']:
251 if lbl in ['tip', '.', 'null']:
249 raise error.Abort(_("the name '%s' is reserved") % lbl)
252 raise error.Abort(_("the name '%s' is reserved") % lbl)
250 for c in (':', '\0', '\n', '\r'):
253 for c in (':', '\0', '\n', '\r'):
251 if c in lbl:
254 if c in lbl:
252 raise error.Abort(_("%r cannot be used in a name") % c)
255 raise error.Abort(_("%r cannot be used in a name") % c)
253 try:
256 try:
254 int(lbl)
257 int(lbl)
255 raise error.Abort(_("cannot use an integer as a name"))
258 raise error.Abort(_("cannot use an integer as a name"))
256 except ValueError:
259 except ValueError:
257 pass
260 pass
258
261
259 def checkfilename(f):
262 def checkfilename(f):
260 '''Check that the filename f is an acceptable filename for a tracked file'''
263 '''Check that the filename f is an acceptable filename for a tracked file'''
261 if '\r' in f or '\n' in f:
264 if '\r' in f or '\n' in f:
262 raise error.Abort(_("'\\n' and '\\r' disallowed in filenames: %r") % f)
265 raise error.Abort(_("'\\n' and '\\r' disallowed in filenames: %r") % f)
263
266
264 def checkportable(ui, f):
267 def checkportable(ui, f):
265 '''Check if filename f is portable and warn or abort depending on config'''
268 '''Check if filename f is portable and warn or abort depending on config'''
266 checkfilename(f)
269 checkfilename(f)
267 abort, warn = checkportabilityalert(ui)
270 abort, warn = checkportabilityalert(ui)
268 if abort or warn:
271 if abort or warn:
269 msg = util.checkwinfilename(f)
272 msg = util.checkwinfilename(f)
270 if msg:
273 if msg:
271 msg = "%s: %r" % (msg, f)
274 msg = "%s: %r" % (msg, f)
272 if abort:
275 if abort:
273 raise error.Abort(msg)
276 raise error.Abort(msg)
274 ui.warn(_("warning: %s\n") % msg)
277 ui.warn(_("warning: %s\n") % msg)
275
278
276 def checkportabilityalert(ui):
279 def checkportabilityalert(ui):
277 '''check if the user's config requests nothing, a warning, or abort for
280 '''check if the user's config requests nothing, a warning, or abort for
278 non-portable filenames'''
281 non-portable filenames'''
279 val = ui.config('ui', 'portablefilenames', 'warn')
282 val = ui.config('ui', 'portablefilenames', 'warn')
280 lval = val.lower()
283 lval = val.lower()
281 bval = util.parsebool(val)
284 bval = util.parsebool(val)
282 abort = pycompat.osname == 'nt' or lval == 'abort'
285 abort = pycompat.osname == 'nt' or lval == 'abort'
283 warn = bval or lval == 'warn'
286 warn = bval or lval == 'warn'
284 if bval is None and not (warn or abort or lval == 'ignore'):
287 if bval is None and not (warn or abort or lval == 'ignore'):
285 raise error.ConfigError(
288 raise error.ConfigError(
286 _("ui.portablefilenames value is invalid ('%s')") % val)
289 _("ui.portablefilenames value is invalid ('%s')") % val)
287 return abort, warn
290 return abort, warn
288
291
289 class casecollisionauditor(object):
292 class casecollisionauditor(object):
290 def __init__(self, ui, abort, dirstate):
293 def __init__(self, ui, abort, dirstate):
291 self._ui = ui
294 self._ui = ui
292 self._abort = abort
295 self._abort = abort
293 allfiles = '\0'.join(dirstate._map)
296 allfiles = '\0'.join(dirstate._map)
294 self._loweredfiles = set(encoding.lower(allfiles).split('\0'))
297 self._loweredfiles = set(encoding.lower(allfiles).split('\0'))
295 self._dirstate = dirstate
298 self._dirstate = dirstate
296 # The purpose of _newfiles is so that we don't complain about
299 # The purpose of _newfiles is so that we don't complain about
297 # case collisions if someone were to call this object with the
300 # case collisions if someone were to call this object with the
298 # same filename twice.
301 # same filename twice.
299 self._newfiles = set()
302 self._newfiles = set()
300
303
301 def __call__(self, f):
304 def __call__(self, f):
302 if f in self._newfiles:
305 if f in self._newfiles:
303 return
306 return
304 fl = encoding.lower(f)
307 fl = encoding.lower(f)
305 if fl in self._loweredfiles and f not in self._dirstate:
308 if fl in self._loweredfiles and f not in self._dirstate:
306 msg = _('possible case-folding collision for %s') % f
309 msg = _('possible case-folding collision for %s') % f
307 if self._abort:
310 if self._abort:
308 raise error.Abort(msg)
311 raise error.Abort(msg)
309 self._ui.warn(_("warning: %s\n") % msg)
312 self._ui.warn(_("warning: %s\n") % msg)
310 self._loweredfiles.add(fl)
313 self._loweredfiles.add(fl)
311 self._newfiles.add(f)
314 self._newfiles.add(f)
312
315
313 def filteredhash(repo, maxrev):
316 def filteredhash(repo, maxrev):
314 """build hash of filtered revisions in the current repoview.
317 """build hash of filtered revisions in the current repoview.
315
318
316 Multiple caches perform up-to-date validation by checking that the
319 Multiple caches perform up-to-date validation by checking that the
317 tiprev and tipnode stored in the cache file match the current repository.
320 tiprev and tipnode stored in the cache file match the current repository.
318 However, this is not sufficient for validating repoviews because the set
321 However, this is not sufficient for validating repoviews because the set
319 of revisions in the view may change without the repository tiprev and
322 of revisions in the view may change without the repository tiprev and
320 tipnode changing.
323 tipnode changing.
321
324
322 This function hashes all the revs filtered from the view and returns
325 This function hashes all the revs filtered from the view and returns
323 that SHA-1 digest.
326 that SHA-1 digest.
324 """
327 """
325 cl = repo.changelog
328 cl = repo.changelog
326 if not cl.filteredrevs:
329 if not cl.filteredrevs:
327 return None
330 return None
328 key = None
331 key = None
329 revs = sorted(r for r in cl.filteredrevs if r <= maxrev)
332 revs = sorted(r for r in cl.filteredrevs if r <= maxrev)
330 if revs:
333 if revs:
331 s = hashlib.sha1()
334 s = hashlib.sha1()
332 for rev in revs:
335 for rev in revs:
333 s.update('%d;' % rev)
336 s.update('%d;' % rev)
334 key = s.digest()
337 key = s.digest()
335 return key
338 return key
336
339
337 def walkrepos(path, followsym=False, seen_dirs=None, recurse=False):
340 def walkrepos(path, followsym=False, seen_dirs=None, recurse=False):
338 '''yield every hg repository under path, always recursively.
341 '''yield every hg repository under path, always recursively.
339 The recurse flag will only control recursion into repo working dirs'''
342 The recurse flag will only control recursion into repo working dirs'''
340 def errhandler(err):
343 def errhandler(err):
341 if err.filename == path:
344 if err.filename == path:
342 raise err
345 raise err
343 samestat = getattr(os.path, 'samestat', None)
346 samestat = getattr(os.path, 'samestat', None)
344 if followsym and samestat is not None:
347 if followsym and samestat is not None:
345 def adddir(dirlst, dirname):
348 def adddir(dirlst, dirname):
346 match = False
349 match = False
347 dirstat = os.stat(dirname)
350 dirstat = os.stat(dirname)
348 for lstdirstat in dirlst:
351 for lstdirstat in dirlst:
349 if samestat(dirstat, lstdirstat):
352 if samestat(dirstat, lstdirstat):
350 match = True
353 match = True
351 break
354 break
352 if not match:
355 if not match:
353 dirlst.append(dirstat)
356 dirlst.append(dirstat)
354 return not match
357 return not match
355 else:
358 else:
356 followsym = False
359 followsym = False
357
360
358 if (seen_dirs is None) and followsym:
361 if (seen_dirs is None) and followsym:
359 seen_dirs = []
362 seen_dirs = []
360 adddir(seen_dirs, path)
363 adddir(seen_dirs, path)
361 for root, dirs, files in os.walk(path, topdown=True, onerror=errhandler):
364 for root, dirs, files in os.walk(path, topdown=True, onerror=errhandler):
362 dirs.sort()
365 dirs.sort()
363 if '.hg' in dirs:
366 if '.hg' in dirs:
364 yield root # found a repository
367 yield root # found a repository
365 qroot = os.path.join(root, '.hg', 'patches')
368 qroot = os.path.join(root, '.hg', 'patches')
366 if os.path.isdir(os.path.join(qroot, '.hg')):
369 if os.path.isdir(os.path.join(qroot, '.hg')):
367 yield qroot # we have a patch queue repo here
370 yield qroot # we have a patch queue repo here
368 if recurse:
371 if recurse:
369 # avoid recursing inside the .hg directory
372 # avoid recursing inside the .hg directory
370 dirs.remove('.hg')
373 dirs.remove('.hg')
371 else:
374 else:
372 dirs[:] = [] # don't descend further
375 dirs[:] = [] # don't descend further
373 elif followsym:
376 elif followsym:
374 newdirs = []
377 newdirs = []
375 for d in dirs:
378 for d in dirs:
376 fname = os.path.join(root, d)
379 fname = os.path.join(root, d)
377 if adddir(seen_dirs, fname):
380 if adddir(seen_dirs, fname):
378 if os.path.islink(fname):
381 if os.path.islink(fname):
379 for hgname in walkrepos(fname, True, seen_dirs):
382 for hgname in walkrepos(fname, True, seen_dirs):
380 yield hgname
383 yield hgname
381 else:
384 else:
382 newdirs.append(d)
385 newdirs.append(d)
383 dirs[:] = newdirs
386 dirs[:] = newdirs
384
387
385 def binnode(ctx):
388 def binnode(ctx):
386 """Return binary node id for a given basectx"""
389 """Return binary node id for a given basectx"""
387 node = ctx.node()
390 node = ctx.node()
388 if node is None:
391 if node is None:
389 return wdirid
392 return wdirid
390 return node
393 return node
391
394
392 def intrev(ctx):
395 def intrev(ctx):
393 """Return integer for a given basectx that can be used in comparison or
396 """Return integer for a given basectx that can be used in comparison or
394 arithmetic operation"""
397 arithmetic operation"""
395 rev = ctx.rev()
398 rev = ctx.rev()
396 if rev is None:
399 if rev is None:
397 return wdirrev
400 return wdirrev
398 return rev
401 return rev
399
402
400 def revsingle(repo, revspec, default='.'):
403 def revsingle(repo, revspec, default='.'):
401 if not revspec and revspec != 0:
404 if not revspec and revspec != 0:
402 return repo[default]
405 return repo[default]
403
406
404 l = revrange(repo, [revspec])
407 l = revrange(repo, [revspec])
405 if not l:
408 if not l:
406 raise error.Abort(_('empty revision set'))
409 raise error.Abort(_('empty revision set'))
407 return repo[l.last()]
410 return repo[l.last()]
408
411
409 def _pairspec(revspec):
412 def _pairspec(revspec):
410 tree = revsetlang.parse(revspec)
413 tree = revsetlang.parse(revspec)
411 return tree and tree[0] in ('range', 'rangepre', 'rangepost', 'rangeall')
414 return tree and tree[0] in ('range', 'rangepre', 'rangepost', 'rangeall')
412
415
413 def revpair(repo, revs):
416 def revpair(repo, revs):
414 if not revs:
417 if not revs:
415 return repo.dirstate.p1(), None
418 return repo.dirstate.p1(), None
416
419
417 l = revrange(repo, revs)
420 l = revrange(repo, revs)
418
421
419 if not l:
422 if not l:
420 first = second = None
423 first = second = None
421 elif l.isascending():
424 elif l.isascending():
422 first = l.min()
425 first = l.min()
423 second = l.max()
426 second = l.max()
424 elif l.isdescending():
427 elif l.isdescending():
425 first = l.max()
428 first = l.max()
426 second = l.min()
429 second = l.min()
427 else:
430 else:
428 first = l.first()
431 first = l.first()
429 second = l.last()
432 second = l.last()
430
433
431 if first is None:
434 if first is None:
432 raise error.Abort(_('empty revision range'))
435 raise error.Abort(_('empty revision range'))
433 if (first == second and len(revs) >= 2
436 if (first == second and len(revs) >= 2
434 and not all(revrange(repo, [r]) for r in revs)):
437 and not all(revrange(repo, [r]) for r in revs)):
435 raise error.Abort(_('empty revision on one side of range'))
438 raise error.Abort(_('empty revision on one side of range'))
436
439
437 # if top-level is range expression, the result must always be a pair
440 # if top-level is range expression, the result must always be a pair
438 if first == second and len(revs) == 1 and not _pairspec(revs[0]):
441 if first == second and len(revs) == 1 and not _pairspec(revs[0]):
439 return repo.lookup(first), None
442 return repo.lookup(first), None
440
443
441 return repo.lookup(first), repo.lookup(second)
444 return repo.lookup(first), repo.lookup(second)
442
445
443 def revrange(repo, specs):
446 def revrange(repo, specs):
444 """Execute 1 to many revsets and return the union.
447 """Execute 1 to many revsets and return the union.
445
448
446 This is the preferred mechanism for executing revsets using user-specified
449 This is the preferred mechanism for executing revsets using user-specified
447 config options, such as revset aliases.
450 config options, such as revset aliases.
448
451
449 The revsets specified by ``specs`` will be executed via a chained ``OR``
452 The revsets specified by ``specs`` will be executed via a chained ``OR``
450 expression. If ``specs`` is empty, an empty result is returned.
453 expression. If ``specs`` is empty, an empty result is returned.
451
454
452 ``specs`` can contain integers, in which case they are assumed to be
455 ``specs`` can contain integers, in which case they are assumed to be
453 revision numbers.
456 revision numbers.
454
457
455 It is assumed the revsets are already formatted. If you have arguments
458 It is assumed the revsets are already formatted. If you have arguments
456 that need to be expanded in the revset, call ``revsetlang.formatspec()``
459 that need to be expanded in the revset, call ``revsetlang.formatspec()``
457 and pass the result as an element of ``specs``.
460 and pass the result as an element of ``specs``.
458
461
459 Specifying a single revset is allowed.
462 Specifying a single revset is allowed.
460
463
461 Returns a ``revset.abstractsmartset`` which is a list-like interface over
464 Returns a ``revset.abstractsmartset`` which is a list-like interface over
462 integer revisions.
465 integer revisions.
463 """
466 """
464 allspecs = []
467 allspecs = []
465 for spec in specs:
468 for spec in specs:
466 if isinstance(spec, int):
469 if isinstance(spec, int):
467 spec = revsetlang.formatspec('rev(%d)', spec)
470 spec = revsetlang.formatspec('rev(%d)', spec)
468 allspecs.append(spec)
471 allspecs.append(spec)
469 return repo.anyrevs(allspecs, user=True)
472 return repo.anyrevs(allspecs, user=True)
470
473
471 def meaningfulparents(repo, ctx):
474 def meaningfulparents(repo, ctx):
472 """Return list of meaningful (or all if debug) parentrevs for rev.
475 """Return list of meaningful (or all if debug) parentrevs for rev.
473
476
474 For merges (two non-nullrev revisions) both parents are meaningful.
477 For merges (two non-nullrev revisions) both parents are meaningful.
475 Otherwise the first parent revision is considered meaningful if it
478 Otherwise the first parent revision is considered meaningful if it
476 is not the preceding revision.
479 is not the preceding revision.
477 """
480 """
478 parents = ctx.parents()
481 parents = ctx.parents()
479 if len(parents) > 1:
482 if len(parents) > 1:
480 return parents
483 return parents
481 if repo.ui.debugflag:
484 if repo.ui.debugflag:
482 return [parents[0], repo['null']]
485 return [parents[0], repo['null']]
483 if parents[0].rev() >= intrev(ctx) - 1:
486 if parents[0].rev() >= intrev(ctx) - 1:
484 return []
487 return []
485 return parents
488 return parents
486
489
487 def expandpats(pats):
490 def expandpats(pats):
488 '''Expand bare globs when running on windows.
491 '''Expand bare globs when running on windows.
489 On posix we assume it already has already been done by sh.'''
492 On posix we assume it already has already been done by sh.'''
490 if not util.expandglobs:
493 if not util.expandglobs:
491 return list(pats)
494 return list(pats)
492 ret = []
495 ret = []
493 for kindpat in pats:
496 for kindpat in pats:
494 kind, pat = matchmod._patsplit(kindpat, None)
497 kind, pat = matchmod._patsplit(kindpat, None)
495 if kind is None:
498 if kind is None:
496 try:
499 try:
497 globbed = glob.glob(pat)
500 globbed = glob.glob(pat)
498 except re.error:
501 except re.error:
499 globbed = [pat]
502 globbed = [pat]
500 if globbed:
503 if globbed:
501 ret.extend(globbed)
504 ret.extend(globbed)
502 continue
505 continue
503 ret.append(kindpat)
506 ret.append(kindpat)
504 return ret
507 return ret
505
508
506 def matchandpats(ctx, pats=(), opts=None, globbed=False, default='relpath',
509 def matchandpats(ctx, pats=(), opts=None, globbed=False, default='relpath',
507 badfn=None):
510 badfn=None):
508 '''Return a matcher and the patterns that were used.
511 '''Return a matcher and the patterns that were used.
509 The matcher will warn about bad matches, unless an alternate badfn callback
512 The matcher will warn about bad matches, unless an alternate badfn callback
510 is provided.'''
513 is provided.'''
511 if pats == ("",):
514 if pats == ("",):
512 pats = []
515 pats = []
513 if opts is None:
516 if opts is None:
514 opts = {}
517 opts = {}
515 if not globbed and default == 'relpath':
518 if not globbed and default == 'relpath':
516 pats = expandpats(pats or [])
519 pats = expandpats(pats or [])
517
520
518 def bad(f, msg):
521 def bad(f, msg):
519 ctx.repo().ui.warn("%s: %s\n" % (m.rel(f), msg))
522 ctx.repo().ui.warn("%s: %s\n" % (m.rel(f), msg))
520
523
521 if badfn is None:
524 if badfn is None:
522 badfn = bad
525 badfn = bad
523
526
524 m = ctx.match(pats, opts.get('include'), opts.get('exclude'),
527 m = ctx.match(pats, opts.get('include'), opts.get('exclude'),
525 default, listsubrepos=opts.get('subrepos'), badfn=badfn)
528 default, listsubrepos=opts.get('subrepos'), badfn=badfn)
526
529
527 if m.always():
530 if m.always():
528 pats = []
531 pats = []
529 return m, pats
532 return m, pats
530
533
531 def match(ctx, pats=(), opts=None, globbed=False, default='relpath',
534 def match(ctx, pats=(), opts=None, globbed=False, default='relpath',
532 badfn=None):
535 badfn=None):
533 '''Return a matcher that will warn about bad matches.'''
536 '''Return a matcher that will warn about bad matches.'''
534 return matchandpats(ctx, pats, opts, globbed, default, badfn=badfn)[0]
537 return matchandpats(ctx, pats, opts, globbed, default, badfn=badfn)[0]
535
538
536 def matchall(repo):
539 def matchall(repo):
537 '''Return a matcher that will efficiently match everything.'''
540 '''Return a matcher that will efficiently match everything.'''
538 return matchmod.always(repo.root, repo.getcwd())
541 return matchmod.always(repo.root, repo.getcwd())
539
542
540 def matchfiles(repo, files, badfn=None):
543 def matchfiles(repo, files, badfn=None):
541 '''Return a matcher that will efficiently match exactly these files.'''
544 '''Return a matcher that will efficiently match exactly these files.'''
542 return matchmod.exact(repo.root, repo.getcwd(), files, badfn=badfn)
545 return matchmod.exact(repo.root, repo.getcwd(), files, badfn=badfn)
543
546
544 def origpath(ui, repo, filepath):
547 def origpath(ui, repo, filepath):
545 '''customize where .orig files are created
548 '''customize where .orig files are created
546
549
547 Fetch user defined path from config file: [ui] origbackuppath = <path>
550 Fetch user defined path from config file: [ui] origbackuppath = <path>
548 Fall back to default (filepath) if not specified
551 Fall back to default (filepath) if not specified
549 '''
552 '''
550 origbackuppath = ui.config('ui', 'origbackuppath', None)
553 origbackuppath = ui.config('ui', 'origbackuppath', None)
551 if origbackuppath is None:
554 if origbackuppath is None:
552 return filepath + ".orig"
555 return filepath + ".orig"
553
556
554 filepathfromroot = os.path.relpath(filepath, start=repo.root)
557 filepathfromroot = os.path.relpath(filepath, start=repo.root)
555 fullorigpath = repo.wjoin(origbackuppath, filepathfromroot)
558 fullorigpath = repo.wjoin(origbackuppath, filepathfromroot)
556
559
557 origbackupdir = repo.vfs.dirname(fullorigpath)
560 origbackupdir = repo.vfs.dirname(fullorigpath)
558 if not repo.vfs.exists(origbackupdir):
561 if not repo.vfs.exists(origbackupdir):
559 ui.note(_('creating directory: %s\n') % origbackupdir)
562 ui.note(_('creating directory: %s\n') % origbackupdir)
560 util.makedirs(origbackupdir)
563 util.makedirs(origbackupdir)
561
564
562 return fullorigpath + ".orig"
565 return fullorigpath + ".orig"
563
566
567 def cleanupnodes(repo, mapping, operation):
568 """do common cleanups when old nodes are replaced by new nodes
569
570 That includes writing obsmarkers or stripping nodes, and moving bookmarks.
571 (we might also want to move working directory parent in the future)
572
573 mapping is {oldnode: [newnode]} or a iterable of nodes if they do not have
574 replacements. operation is a string, like "rebase".
575 """
576 if not util.safehasattr(mapping, 'items'):
577 mapping = {n: () for n in mapping}
578
579 with repo.transaction('cleanup') as tr:
580 # Move bookmarks
581 bmarks = repo._bookmarks
582 bmarkchanged = False
583 for oldnode, newnodes in mapping.items():
584 oldbmarks = repo.nodebookmarks(oldnode)
585 if not oldbmarks:
586 continue
587 bmarkchanged = True
588 if len(newnodes) > 1:
589 heads = list(repo.set('heads(%ln)', newnodes))
590 if len(heads) != 1:
591 raise error.ProgrammingError(
592 'cannot figure out bookmark movement')
593 newnode = heads[0].node()
594 elif len(newnodes) == 0:
595 # move bookmark backwards
596 roots = list(repo.set('max((::%n) - %ln)', oldnode,
597 list(mapping)))
598 if roots:
599 newnode = roots[0].node()
600 else:
601 newnode = nullid
602 else:
603 newnode = newnodes[0]
604 repo.ui.debug('moving bookmarks %r from %s to %s\n' %
605 (oldbmarks, hex(oldnode), hex(newnode)))
606 for name in oldbmarks:
607 bmarks[name] = newnode
608 if bmarkchanged:
609 bmarks.recordchange(tr)
610
611 # Obsolete or strip nodes
612 if obsolete.isenabled(repo, obsolete.createmarkersopt):
613 # If a node is already obsoleted, and we want to obsolete it
614 # without a successor, skip that obssolete request since it's
615 # unnecessary. That's the "if s or not isobs(n)" check below.
616 # Also sort the node in topology order, that might be useful for
617 # some obsstore logic.
618 # NOTE: the filtering and sorting might belong to createmarkers.
619 isobs = repo.obsstore.successors.__contains__
620 sortfunc = lambda ns: repo.changelog.rev(ns[0])
621 rels = [(repo[n], (repo[m] for m in s))
622 for n, s in sorted(mapping.items(), key=sortfunc)
623 if s or not isobs(n)]
624 obsolete.createmarkers(repo, rels, operation=operation)
625 else:
626 from . import repair # avoid import cycle
627 repair.delayedstrip(repo.ui, repo, list(mapping), operation)
628
564 def addremove(repo, matcher, prefix, opts=None, dry_run=None, similarity=None):
629 def addremove(repo, matcher, prefix, opts=None, dry_run=None, similarity=None):
565 if opts is None:
630 if opts is None:
566 opts = {}
631 opts = {}
567 m = matcher
632 m = matcher
568 if dry_run is None:
633 if dry_run is None:
569 dry_run = opts.get('dry_run')
634 dry_run = opts.get('dry_run')
570 if similarity is None:
635 if similarity is None:
571 similarity = float(opts.get('similarity') or 0)
636 similarity = float(opts.get('similarity') or 0)
572
637
573 ret = 0
638 ret = 0
574 join = lambda f: os.path.join(prefix, f)
639 join = lambda f: os.path.join(prefix, f)
575
640
576 wctx = repo[None]
641 wctx = repo[None]
577 for subpath in sorted(wctx.substate):
642 for subpath in sorted(wctx.substate):
578 submatch = matchmod.subdirmatcher(subpath, m)
643 submatch = matchmod.subdirmatcher(subpath, m)
579 if opts.get('subrepos') or m.exact(subpath) or any(submatch.files()):
644 if opts.get('subrepos') or m.exact(subpath) or any(submatch.files()):
580 sub = wctx.sub(subpath)
645 sub = wctx.sub(subpath)
581 try:
646 try:
582 if sub.addremove(submatch, prefix, opts, dry_run, similarity):
647 if sub.addremove(submatch, prefix, opts, dry_run, similarity):
583 ret = 1
648 ret = 1
584 except error.LookupError:
649 except error.LookupError:
585 repo.ui.status(_("skipping missing subrepository: %s\n")
650 repo.ui.status(_("skipping missing subrepository: %s\n")
586 % join(subpath))
651 % join(subpath))
587
652
588 rejected = []
653 rejected = []
589 def badfn(f, msg):
654 def badfn(f, msg):
590 if f in m.files():
655 if f in m.files():
591 m.bad(f, msg)
656 m.bad(f, msg)
592 rejected.append(f)
657 rejected.append(f)
593
658
594 badmatch = matchmod.badmatch(m, badfn)
659 badmatch = matchmod.badmatch(m, badfn)
595 added, unknown, deleted, removed, forgotten = _interestingfiles(repo,
660 added, unknown, deleted, removed, forgotten = _interestingfiles(repo,
596 badmatch)
661 badmatch)
597
662
598 unknownset = set(unknown + forgotten)
663 unknownset = set(unknown + forgotten)
599 toprint = unknownset.copy()
664 toprint = unknownset.copy()
600 toprint.update(deleted)
665 toprint.update(deleted)
601 for abs in sorted(toprint):
666 for abs in sorted(toprint):
602 if repo.ui.verbose or not m.exact(abs):
667 if repo.ui.verbose or not m.exact(abs):
603 if abs in unknownset:
668 if abs in unknownset:
604 status = _('adding %s\n') % m.uipath(abs)
669 status = _('adding %s\n') % m.uipath(abs)
605 else:
670 else:
606 status = _('removing %s\n') % m.uipath(abs)
671 status = _('removing %s\n') % m.uipath(abs)
607 repo.ui.status(status)
672 repo.ui.status(status)
608
673
609 renames = _findrenames(repo, m, added + unknown, removed + deleted,
674 renames = _findrenames(repo, m, added + unknown, removed + deleted,
610 similarity)
675 similarity)
611
676
612 if not dry_run:
677 if not dry_run:
613 _markchanges(repo, unknown + forgotten, deleted, renames)
678 _markchanges(repo, unknown + forgotten, deleted, renames)
614
679
615 for f in rejected:
680 for f in rejected:
616 if f in m.files():
681 if f in m.files():
617 return 1
682 return 1
618 return ret
683 return ret
619
684
620 def marktouched(repo, files, similarity=0.0):
685 def marktouched(repo, files, similarity=0.0):
621 '''Assert that files have somehow been operated upon. files are relative to
686 '''Assert that files have somehow been operated upon. files are relative to
622 the repo root.'''
687 the repo root.'''
623 m = matchfiles(repo, files, badfn=lambda x, y: rejected.append(x))
688 m = matchfiles(repo, files, badfn=lambda x, y: rejected.append(x))
624 rejected = []
689 rejected = []
625
690
626 added, unknown, deleted, removed, forgotten = _interestingfiles(repo, m)
691 added, unknown, deleted, removed, forgotten = _interestingfiles(repo, m)
627
692
628 if repo.ui.verbose:
693 if repo.ui.verbose:
629 unknownset = set(unknown + forgotten)
694 unknownset = set(unknown + forgotten)
630 toprint = unknownset.copy()
695 toprint = unknownset.copy()
631 toprint.update(deleted)
696 toprint.update(deleted)
632 for abs in sorted(toprint):
697 for abs in sorted(toprint):
633 if abs in unknownset:
698 if abs in unknownset:
634 status = _('adding %s\n') % abs
699 status = _('adding %s\n') % abs
635 else:
700 else:
636 status = _('removing %s\n') % abs
701 status = _('removing %s\n') % abs
637 repo.ui.status(status)
702 repo.ui.status(status)
638
703
639 renames = _findrenames(repo, m, added + unknown, removed + deleted,
704 renames = _findrenames(repo, m, added + unknown, removed + deleted,
640 similarity)
705 similarity)
641
706
642 _markchanges(repo, unknown + forgotten, deleted, renames)
707 _markchanges(repo, unknown + forgotten, deleted, renames)
643
708
644 for f in rejected:
709 for f in rejected:
645 if f in m.files():
710 if f in m.files():
646 return 1
711 return 1
647 return 0
712 return 0
648
713
649 def _interestingfiles(repo, matcher):
714 def _interestingfiles(repo, matcher):
650 '''Walk dirstate with matcher, looking for files that addremove would care
715 '''Walk dirstate with matcher, looking for files that addremove would care
651 about.
716 about.
652
717
653 This is different from dirstate.status because it doesn't care about
718 This is different from dirstate.status because it doesn't care about
654 whether files are modified or clean.'''
719 whether files are modified or clean.'''
655 added, unknown, deleted, removed, forgotten = [], [], [], [], []
720 added, unknown, deleted, removed, forgotten = [], [], [], [], []
656 audit_path = pathutil.pathauditor(repo.root)
721 audit_path = pathutil.pathauditor(repo.root)
657
722
658 ctx = repo[None]
723 ctx = repo[None]
659 dirstate = repo.dirstate
724 dirstate = repo.dirstate
660 walkresults = dirstate.walk(matcher, sorted(ctx.substate), True, False,
725 walkresults = dirstate.walk(matcher, sorted(ctx.substate), True, False,
661 full=False)
726 full=False)
662 for abs, st in walkresults.iteritems():
727 for abs, st in walkresults.iteritems():
663 dstate = dirstate[abs]
728 dstate = dirstate[abs]
664 if dstate == '?' and audit_path.check(abs):
729 if dstate == '?' and audit_path.check(abs):
665 unknown.append(abs)
730 unknown.append(abs)
666 elif dstate != 'r' and not st:
731 elif dstate != 'r' and not st:
667 deleted.append(abs)
732 deleted.append(abs)
668 elif dstate == 'r' and st:
733 elif dstate == 'r' and st:
669 forgotten.append(abs)
734 forgotten.append(abs)
670 # for finding renames
735 # for finding renames
671 elif dstate == 'r' and not st:
736 elif dstate == 'r' and not st:
672 removed.append(abs)
737 removed.append(abs)
673 elif dstate == 'a':
738 elif dstate == 'a':
674 added.append(abs)
739 added.append(abs)
675
740
676 return added, unknown, deleted, removed, forgotten
741 return added, unknown, deleted, removed, forgotten
677
742
678 def _findrenames(repo, matcher, added, removed, similarity):
743 def _findrenames(repo, matcher, added, removed, similarity):
679 '''Find renames from removed files to added ones.'''
744 '''Find renames from removed files to added ones.'''
680 renames = {}
745 renames = {}
681 if similarity > 0:
746 if similarity > 0:
682 for old, new, score in similar.findrenames(repo, added, removed,
747 for old, new, score in similar.findrenames(repo, added, removed,
683 similarity):
748 similarity):
684 if (repo.ui.verbose or not matcher.exact(old)
749 if (repo.ui.verbose or not matcher.exact(old)
685 or not matcher.exact(new)):
750 or not matcher.exact(new)):
686 repo.ui.status(_('recording removal of %s as rename to %s '
751 repo.ui.status(_('recording removal of %s as rename to %s '
687 '(%d%% similar)\n') %
752 '(%d%% similar)\n') %
688 (matcher.rel(old), matcher.rel(new),
753 (matcher.rel(old), matcher.rel(new),
689 score * 100))
754 score * 100))
690 renames[new] = old
755 renames[new] = old
691 return renames
756 return renames
692
757
693 def _markchanges(repo, unknown, deleted, renames):
758 def _markchanges(repo, unknown, deleted, renames):
694 '''Marks the files in unknown as added, the files in deleted as removed,
759 '''Marks the files in unknown as added, the files in deleted as removed,
695 and the files in renames as copied.'''
760 and the files in renames as copied.'''
696 wctx = repo[None]
761 wctx = repo[None]
697 with repo.wlock():
762 with repo.wlock():
698 wctx.forget(deleted)
763 wctx.forget(deleted)
699 wctx.add(unknown)
764 wctx.add(unknown)
700 for new, old in renames.iteritems():
765 for new, old in renames.iteritems():
701 wctx.copy(old, new)
766 wctx.copy(old, new)
702
767
703 def dirstatecopy(ui, repo, wctx, src, dst, dryrun=False, cwd=None):
768 def dirstatecopy(ui, repo, wctx, src, dst, dryrun=False, cwd=None):
704 """Update the dirstate to reflect the intent of copying src to dst. For
769 """Update the dirstate to reflect the intent of copying src to dst. For
705 different reasons it might not end with dst being marked as copied from src.
770 different reasons it might not end with dst being marked as copied from src.
706 """
771 """
707 origsrc = repo.dirstate.copied(src) or src
772 origsrc = repo.dirstate.copied(src) or src
708 if dst == origsrc: # copying back a copy?
773 if dst == origsrc: # copying back a copy?
709 if repo.dirstate[dst] not in 'mn' and not dryrun:
774 if repo.dirstate[dst] not in 'mn' and not dryrun:
710 repo.dirstate.normallookup(dst)
775 repo.dirstate.normallookup(dst)
711 else:
776 else:
712 if repo.dirstate[origsrc] == 'a' and origsrc == src:
777 if repo.dirstate[origsrc] == 'a' and origsrc == src:
713 if not ui.quiet:
778 if not ui.quiet:
714 ui.warn(_("%s has not been committed yet, so no copy "
779 ui.warn(_("%s has not been committed yet, so no copy "
715 "data will be stored for %s.\n")
780 "data will be stored for %s.\n")
716 % (repo.pathto(origsrc, cwd), repo.pathto(dst, cwd)))
781 % (repo.pathto(origsrc, cwd), repo.pathto(dst, cwd)))
717 if repo.dirstate[dst] in '?r' and not dryrun:
782 if repo.dirstate[dst] in '?r' and not dryrun:
718 wctx.add([dst])
783 wctx.add([dst])
719 elif not dryrun:
784 elif not dryrun:
720 wctx.copy(origsrc, dst)
785 wctx.copy(origsrc, dst)
721
786
722 def readrequires(opener, supported):
787 def readrequires(opener, supported):
723 '''Reads and parses .hg/requires and checks if all entries found
788 '''Reads and parses .hg/requires and checks if all entries found
724 are in the list of supported features.'''
789 are in the list of supported features.'''
725 requirements = set(opener.read("requires").splitlines())
790 requirements = set(opener.read("requires").splitlines())
726 missings = []
791 missings = []
727 for r in requirements:
792 for r in requirements:
728 if r not in supported:
793 if r not in supported:
729 if not r or not r[0].isalnum():
794 if not r or not r[0].isalnum():
730 raise error.RequirementError(_(".hg/requires file is corrupt"))
795 raise error.RequirementError(_(".hg/requires file is corrupt"))
731 missings.append(r)
796 missings.append(r)
732 missings.sort()
797 missings.sort()
733 if missings:
798 if missings:
734 raise error.RequirementError(
799 raise error.RequirementError(
735 _("repository requires features unknown to this Mercurial: %s")
800 _("repository requires features unknown to this Mercurial: %s")
736 % " ".join(missings),
801 % " ".join(missings),
737 hint=_("see https://mercurial-scm.org/wiki/MissingRequirement"
802 hint=_("see https://mercurial-scm.org/wiki/MissingRequirement"
738 " for more information"))
803 " for more information"))
739 return requirements
804 return requirements
740
805
741 def writerequires(opener, requirements):
806 def writerequires(opener, requirements):
742 with opener('requires', 'w') as fp:
807 with opener('requires', 'w') as fp:
743 for r in sorted(requirements):
808 for r in sorted(requirements):
744 fp.write("%s\n" % r)
809 fp.write("%s\n" % r)
745
810
746 class filecachesubentry(object):
811 class filecachesubentry(object):
747 def __init__(self, path, stat):
812 def __init__(self, path, stat):
748 self.path = path
813 self.path = path
749 self.cachestat = None
814 self.cachestat = None
750 self._cacheable = None
815 self._cacheable = None
751
816
752 if stat:
817 if stat:
753 self.cachestat = filecachesubentry.stat(self.path)
818 self.cachestat = filecachesubentry.stat(self.path)
754
819
755 if self.cachestat:
820 if self.cachestat:
756 self._cacheable = self.cachestat.cacheable()
821 self._cacheable = self.cachestat.cacheable()
757 else:
822 else:
758 # None means we don't know yet
823 # None means we don't know yet
759 self._cacheable = None
824 self._cacheable = None
760
825
761 def refresh(self):
826 def refresh(self):
762 if self.cacheable():
827 if self.cacheable():
763 self.cachestat = filecachesubentry.stat(self.path)
828 self.cachestat = filecachesubentry.stat(self.path)
764
829
765 def cacheable(self):
830 def cacheable(self):
766 if self._cacheable is not None:
831 if self._cacheable is not None:
767 return self._cacheable
832 return self._cacheable
768
833
769 # we don't know yet, assume it is for now
834 # we don't know yet, assume it is for now
770 return True
835 return True
771
836
772 def changed(self):
837 def changed(self):
773 # no point in going further if we can't cache it
838 # no point in going further if we can't cache it
774 if not self.cacheable():
839 if not self.cacheable():
775 return True
840 return True
776
841
777 newstat = filecachesubentry.stat(self.path)
842 newstat = filecachesubentry.stat(self.path)
778
843
779 # we may not know if it's cacheable yet, check again now
844 # we may not know if it's cacheable yet, check again now
780 if newstat and self._cacheable is None:
845 if newstat and self._cacheable is None:
781 self._cacheable = newstat.cacheable()
846 self._cacheable = newstat.cacheable()
782
847
783 # check again
848 # check again
784 if not self._cacheable:
849 if not self._cacheable:
785 return True
850 return True
786
851
787 if self.cachestat != newstat:
852 if self.cachestat != newstat:
788 self.cachestat = newstat
853 self.cachestat = newstat
789 return True
854 return True
790 else:
855 else:
791 return False
856 return False
792
857
793 @staticmethod
858 @staticmethod
794 def stat(path):
859 def stat(path):
795 try:
860 try:
796 return util.cachestat(path)
861 return util.cachestat(path)
797 except OSError as e:
862 except OSError as e:
798 if e.errno != errno.ENOENT:
863 if e.errno != errno.ENOENT:
799 raise
864 raise
800
865
801 class filecacheentry(object):
866 class filecacheentry(object):
802 def __init__(self, paths, stat=True):
867 def __init__(self, paths, stat=True):
803 self._entries = []
868 self._entries = []
804 for path in paths:
869 for path in paths:
805 self._entries.append(filecachesubentry(path, stat))
870 self._entries.append(filecachesubentry(path, stat))
806
871
807 def changed(self):
872 def changed(self):
808 '''true if any entry has changed'''
873 '''true if any entry has changed'''
809 for entry in self._entries:
874 for entry in self._entries:
810 if entry.changed():
875 if entry.changed():
811 return True
876 return True
812 return False
877 return False
813
878
814 def refresh(self):
879 def refresh(self):
815 for entry in self._entries:
880 for entry in self._entries:
816 entry.refresh()
881 entry.refresh()
817
882
818 class filecache(object):
883 class filecache(object):
819 '''A property like decorator that tracks files under .hg/ for updates.
884 '''A property like decorator that tracks files under .hg/ for updates.
820
885
821 Records stat info when called in _filecache.
886 Records stat info when called in _filecache.
822
887
823 On subsequent calls, compares old stat info with new info, and recreates the
888 On subsequent calls, compares old stat info with new info, and recreates the
824 object when any of the files changes, updating the new stat info in
889 object when any of the files changes, updating the new stat info in
825 _filecache.
890 _filecache.
826
891
827 Mercurial either atomic renames or appends for files under .hg,
892 Mercurial either atomic renames or appends for files under .hg,
828 so to ensure the cache is reliable we need the filesystem to be able
893 so to ensure the cache is reliable we need the filesystem to be able
829 to tell us if a file has been replaced. If it can't, we fallback to
894 to tell us if a file has been replaced. If it can't, we fallback to
830 recreating the object on every call (essentially the same behavior as
895 recreating the object on every call (essentially the same behavior as
831 propertycache).
896 propertycache).
832
897
833 '''
898 '''
834 def __init__(self, *paths):
899 def __init__(self, *paths):
835 self.paths = paths
900 self.paths = paths
836
901
837 def join(self, obj, fname):
902 def join(self, obj, fname):
838 """Used to compute the runtime path of a cached file.
903 """Used to compute the runtime path of a cached file.
839
904
840 Users should subclass filecache and provide their own version of this
905 Users should subclass filecache and provide their own version of this
841 function to call the appropriate join function on 'obj' (an instance
906 function to call the appropriate join function on 'obj' (an instance
842 of the class that its member function was decorated).
907 of the class that its member function was decorated).
843 """
908 """
844 raise NotImplementedError
909 raise NotImplementedError
845
910
846 def __call__(self, func):
911 def __call__(self, func):
847 self.func = func
912 self.func = func
848 self.name = func.__name__.encode('ascii')
913 self.name = func.__name__.encode('ascii')
849 return self
914 return self
850
915
851 def __get__(self, obj, type=None):
916 def __get__(self, obj, type=None):
852 # if accessed on the class, return the descriptor itself.
917 # if accessed on the class, return the descriptor itself.
853 if obj is None:
918 if obj is None:
854 return self
919 return self
855 # do we need to check if the file changed?
920 # do we need to check if the file changed?
856 if self.name in obj.__dict__:
921 if self.name in obj.__dict__:
857 assert self.name in obj._filecache, self.name
922 assert self.name in obj._filecache, self.name
858 return obj.__dict__[self.name]
923 return obj.__dict__[self.name]
859
924
860 entry = obj._filecache.get(self.name)
925 entry = obj._filecache.get(self.name)
861
926
862 if entry:
927 if entry:
863 if entry.changed():
928 if entry.changed():
864 entry.obj = self.func(obj)
929 entry.obj = self.func(obj)
865 else:
930 else:
866 paths = [self.join(obj, path) for path in self.paths]
931 paths = [self.join(obj, path) for path in self.paths]
867
932
868 # We stat -before- creating the object so our cache doesn't lie if
933 # We stat -before- creating the object so our cache doesn't lie if
869 # a writer modified between the time we read and stat
934 # a writer modified between the time we read and stat
870 entry = filecacheentry(paths, True)
935 entry = filecacheentry(paths, True)
871 entry.obj = self.func(obj)
936 entry.obj = self.func(obj)
872
937
873 obj._filecache[self.name] = entry
938 obj._filecache[self.name] = entry
874
939
875 obj.__dict__[self.name] = entry.obj
940 obj.__dict__[self.name] = entry.obj
876 return entry.obj
941 return entry.obj
877
942
878 def __set__(self, obj, value):
943 def __set__(self, obj, value):
879 if self.name not in obj._filecache:
944 if self.name not in obj._filecache:
880 # we add an entry for the missing value because X in __dict__
945 # we add an entry for the missing value because X in __dict__
881 # implies X in _filecache
946 # implies X in _filecache
882 paths = [self.join(obj, path) for path in self.paths]
947 paths = [self.join(obj, path) for path in self.paths]
883 ce = filecacheentry(paths, False)
948 ce = filecacheentry(paths, False)
884 obj._filecache[self.name] = ce
949 obj._filecache[self.name] = ce
885 else:
950 else:
886 ce = obj._filecache[self.name]
951 ce = obj._filecache[self.name]
887
952
888 ce.obj = value # update cached copy
953 ce.obj = value # update cached copy
889 obj.__dict__[self.name] = value # update copy returned by obj.x
954 obj.__dict__[self.name] = value # update copy returned by obj.x
890
955
891 def __delete__(self, obj):
956 def __delete__(self, obj):
892 try:
957 try:
893 del obj.__dict__[self.name]
958 del obj.__dict__[self.name]
894 except KeyError:
959 except KeyError:
895 raise AttributeError(self.name)
960 raise AttributeError(self.name)
896
961
897 def _locksub(repo, lock, envvar, cmd, environ=None, *args, **kwargs):
962 def _locksub(repo, lock, envvar, cmd, environ=None, *args, **kwargs):
898 if lock is None:
963 if lock is None:
899 raise error.LockInheritanceContractViolation(
964 raise error.LockInheritanceContractViolation(
900 'lock can only be inherited while held')
965 'lock can only be inherited while held')
901 if environ is None:
966 if environ is None:
902 environ = {}
967 environ = {}
903 with lock.inherit() as locker:
968 with lock.inherit() as locker:
904 environ[envvar] = locker
969 environ[envvar] = locker
905 return repo.ui.system(cmd, environ=environ, *args, **kwargs)
970 return repo.ui.system(cmd, environ=environ, *args, **kwargs)
906
971
907 def wlocksub(repo, cmd, *args, **kwargs):
972 def wlocksub(repo, cmd, *args, **kwargs):
908 """run cmd as a subprocess that allows inheriting repo's wlock
973 """run cmd as a subprocess that allows inheriting repo's wlock
909
974
910 This can only be called while the wlock is held. This takes all the
975 This can only be called while the wlock is held. This takes all the
911 arguments that ui.system does, and returns the exit code of the
976 arguments that ui.system does, and returns the exit code of the
912 subprocess."""
977 subprocess."""
913 return _locksub(repo, repo.currentwlock(), 'HG_WLOCK_LOCKER', cmd, *args,
978 return _locksub(repo, repo.currentwlock(), 'HG_WLOCK_LOCKER', cmd, *args,
914 **kwargs)
979 **kwargs)
915
980
916 def gdinitconfig(ui):
981 def gdinitconfig(ui):
917 """helper function to know if a repo should be created as general delta
982 """helper function to know if a repo should be created as general delta
918 """
983 """
919 # experimental config: format.generaldelta
984 # experimental config: format.generaldelta
920 return (ui.configbool('format', 'generaldelta', False)
985 return (ui.configbool('format', 'generaldelta', False)
921 or ui.configbool('format', 'usegeneraldelta', True))
986 or ui.configbool('format', 'usegeneraldelta', True))
922
987
923 def gddeltaconfig(ui):
988 def gddeltaconfig(ui):
924 """helper function to know if incoming delta should be optimised
989 """helper function to know if incoming delta should be optimised
925 """
990 """
926 # experimental config: format.generaldelta
991 # experimental config: format.generaldelta
927 return ui.configbool('format', 'generaldelta', False)
992 return ui.configbool('format', 'generaldelta', False)
928
993
929 class simplekeyvaluefile(object):
994 class simplekeyvaluefile(object):
930 """A simple file with key=value lines
995 """A simple file with key=value lines
931
996
932 Keys must be alphanumerics and start with a letter, values must not
997 Keys must be alphanumerics and start with a letter, values must not
933 contain '\n' characters"""
998 contain '\n' characters"""
934 firstlinekey = '__firstline'
999 firstlinekey = '__firstline'
935
1000
936 def __init__(self, vfs, path, keys=None):
1001 def __init__(self, vfs, path, keys=None):
937 self.vfs = vfs
1002 self.vfs = vfs
938 self.path = path
1003 self.path = path
939
1004
940 def read(self, firstlinenonkeyval=False):
1005 def read(self, firstlinenonkeyval=False):
941 """Read the contents of a simple key-value file
1006 """Read the contents of a simple key-value file
942
1007
943 'firstlinenonkeyval' indicates whether the first line of file should
1008 'firstlinenonkeyval' indicates whether the first line of file should
944 be treated as a key-value pair or reuturned fully under the
1009 be treated as a key-value pair or reuturned fully under the
945 __firstline key."""
1010 __firstline key."""
946 lines = self.vfs.readlines(self.path)
1011 lines = self.vfs.readlines(self.path)
947 d = {}
1012 d = {}
948 if firstlinenonkeyval:
1013 if firstlinenonkeyval:
949 if not lines:
1014 if not lines:
950 e = _("empty simplekeyvalue file")
1015 e = _("empty simplekeyvalue file")
951 raise error.CorruptedState(e)
1016 raise error.CorruptedState(e)
952 # we don't want to include '\n' in the __firstline
1017 # we don't want to include '\n' in the __firstline
953 d[self.firstlinekey] = lines[0][:-1]
1018 d[self.firstlinekey] = lines[0][:-1]
954 del lines[0]
1019 del lines[0]
955
1020
956 try:
1021 try:
957 # the 'if line.strip()' part prevents us from failing on empty
1022 # the 'if line.strip()' part prevents us from failing on empty
958 # lines which only contain '\n' therefore are not skipped
1023 # lines which only contain '\n' therefore are not skipped
959 # by 'if line'
1024 # by 'if line'
960 updatedict = dict(line[:-1].split('=', 1) for line in lines
1025 updatedict = dict(line[:-1].split('=', 1) for line in lines
961 if line.strip())
1026 if line.strip())
962 if self.firstlinekey in updatedict:
1027 if self.firstlinekey in updatedict:
963 e = _("%r can't be used as a key")
1028 e = _("%r can't be used as a key")
964 raise error.CorruptedState(e % self.firstlinekey)
1029 raise error.CorruptedState(e % self.firstlinekey)
965 d.update(updatedict)
1030 d.update(updatedict)
966 except ValueError as e:
1031 except ValueError as e:
967 raise error.CorruptedState(str(e))
1032 raise error.CorruptedState(str(e))
968 return d
1033 return d
969
1034
970 def write(self, data, firstline=None):
1035 def write(self, data, firstline=None):
971 """Write key=>value mapping to a file
1036 """Write key=>value mapping to a file
972 data is a dict. Keys must be alphanumerical and start with a letter.
1037 data is a dict. Keys must be alphanumerical and start with a letter.
973 Values must not contain newline characters.
1038 Values must not contain newline characters.
974
1039
975 If 'firstline' is not None, it is written to file before
1040 If 'firstline' is not None, it is written to file before
976 everything else, as it is, not in a key=value form"""
1041 everything else, as it is, not in a key=value form"""
977 lines = []
1042 lines = []
978 if firstline is not None:
1043 if firstline is not None:
979 lines.append('%s\n' % firstline)
1044 lines.append('%s\n' % firstline)
980
1045
981 for k, v in data.items():
1046 for k, v in data.items():
982 if k == self.firstlinekey:
1047 if k == self.firstlinekey:
983 e = "key name '%s' is reserved" % self.firstlinekey
1048 e = "key name '%s' is reserved" % self.firstlinekey
984 raise error.ProgrammingError(e)
1049 raise error.ProgrammingError(e)
985 if not k[0].isalpha():
1050 if not k[0].isalpha():
986 e = "keys must start with a letter in a key-value file"
1051 e = "keys must start with a letter in a key-value file"
987 raise error.ProgrammingError(e)
1052 raise error.ProgrammingError(e)
988 if not k.isalnum():
1053 if not k.isalnum():
989 e = "invalid key name in a simple key-value file"
1054 e = "invalid key name in a simple key-value file"
990 raise error.ProgrammingError(e)
1055 raise error.ProgrammingError(e)
991 if '\n' in v:
1056 if '\n' in v:
992 e = "invalid value in a simple key-value file"
1057 e = "invalid value in a simple key-value file"
993 raise error.ProgrammingError(e)
1058 raise error.ProgrammingError(e)
994 lines.append("%s=%s\n" % (k, v))
1059 lines.append("%s=%s\n" % (k, v))
995 with self.vfs(self.path, mode='wb', atomictemp=True) as fp:
1060 with self.vfs(self.path, mode='wb', atomictemp=True) as fp:
996 fp.write(''.join(lines))
1061 fp.write(''.join(lines))
@@ -1,992 +1,1094 b''
1 $ echo "[format]" >> $HGRCPATH
1 $ echo "[format]" >> $HGRCPATH
2 $ echo "usegeneraldelta=yes" >> $HGRCPATH
2 $ echo "usegeneraldelta=yes" >> $HGRCPATH
3 $ echo "[extensions]" >> $HGRCPATH
3 $ echo "[extensions]" >> $HGRCPATH
4 $ echo "strip=" >> $HGRCPATH
4 $ echo "strip=" >> $HGRCPATH
5 $ echo "drawdag=$TESTDIR/drawdag.py" >> $HGRCPATH
5 $ echo "drawdag=$TESTDIR/drawdag.py" >> $HGRCPATH
6
6
7 $ restore() {
7 $ restore() {
8 > hg unbundle -q .hg/strip-backup/*
8 > hg unbundle -q .hg/strip-backup/*
9 > rm .hg/strip-backup/*
9 > rm .hg/strip-backup/*
10 > }
10 > }
11 $ teststrip() {
11 $ teststrip() {
12 > hg up -C $1
12 > hg up -C $1
13 > echo % before update $1, strip $2
13 > echo % before update $1, strip $2
14 > hg parents
14 > hg parents
15 > hg --traceback strip $2
15 > hg --traceback strip $2
16 > echo % after update $1, strip $2
16 > echo % after update $1, strip $2
17 > hg parents
17 > hg parents
18 > restore
18 > restore
19 > }
19 > }
20
20
21 $ hg init test
21 $ hg init test
22 $ cd test
22 $ cd test
23
23
24 $ echo foo > bar
24 $ echo foo > bar
25 $ hg ci -Ama
25 $ hg ci -Ama
26 adding bar
26 adding bar
27
27
28 $ echo more >> bar
28 $ echo more >> bar
29 $ hg ci -Amb
29 $ hg ci -Amb
30
30
31 $ echo blah >> bar
31 $ echo blah >> bar
32 $ hg ci -Amc
32 $ hg ci -Amc
33
33
34 $ hg up 1
34 $ hg up 1
35 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
35 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
36 $ echo blah >> bar
36 $ echo blah >> bar
37 $ hg ci -Amd
37 $ hg ci -Amd
38 created new head
38 created new head
39
39
40 $ echo final >> bar
40 $ echo final >> bar
41 $ hg ci -Ame
41 $ hg ci -Ame
42
42
43 $ hg log
43 $ hg log
44 changeset: 4:443431ffac4f
44 changeset: 4:443431ffac4f
45 tag: tip
45 tag: tip
46 user: test
46 user: test
47 date: Thu Jan 01 00:00:00 1970 +0000
47 date: Thu Jan 01 00:00:00 1970 +0000
48 summary: e
48 summary: e
49
49
50 changeset: 3:65bd5f99a4a3
50 changeset: 3:65bd5f99a4a3
51 parent: 1:ef3a871183d7
51 parent: 1:ef3a871183d7
52 user: test
52 user: test
53 date: Thu Jan 01 00:00:00 1970 +0000
53 date: Thu Jan 01 00:00:00 1970 +0000
54 summary: d
54 summary: d
55
55
56 changeset: 2:264128213d29
56 changeset: 2:264128213d29
57 user: test
57 user: test
58 date: Thu Jan 01 00:00:00 1970 +0000
58 date: Thu Jan 01 00:00:00 1970 +0000
59 summary: c
59 summary: c
60
60
61 changeset: 1:ef3a871183d7
61 changeset: 1:ef3a871183d7
62 user: test
62 user: test
63 date: Thu Jan 01 00:00:00 1970 +0000
63 date: Thu Jan 01 00:00:00 1970 +0000
64 summary: b
64 summary: b
65
65
66 changeset: 0:9ab35a2d17cb
66 changeset: 0:9ab35a2d17cb
67 user: test
67 user: test
68 date: Thu Jan 01 00:00:00 1970 +0000
68 date: Thu Jan 01 00:00:00 1970 +0000
69 summary: a
69 summary: a
70
70
71
71
72 $ teststrip 4 4
72 $ teststrip 4 4
73 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
73 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
74 % before update 4, strip 4
74 % before update 4, strip 4
75 changeset: 4:443431ffac4f
75 changeset: 4:443431ffac4f
76 tag: tip
76 tag: tip
77 user: test
77 user: test
78 date: Thu Jan 01 00:00:00 1970 +0000
78 date: Thu Jan 01 00:00:00 1970 +0000
79 summary: e
79 summary: e
80
80
81 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
81 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
82 saved backup bundle to $TESTTMP/test/.hg/strip-backup/*-backup.hg (glob)
82 saved backup bundle to $TESTTMP/test/.hg/strip-backup/*-backup.hg (glob)
83 % after update 4, strip 4
83 % after update 4, strip 4
84 changeset: 3:65bd5f99a4a3
84 changeset: 3:65bd5f99a4a3
85 tag: tip
85 tag: tip
86 parent: 1:ef3a871183d7
86 parent: 1:ef3a871183d7
87 user: test
87 user: test
88 date: Thu Jan 01 00:00:00 1970 +0000
88 date: Thu Jan 01 00:00:00 1970 +0000
89 summary: d
89 summary: d
90
90
91 $ teststrip 4 3
91 $ teststrip 4 3
92 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
92 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
93 % before update 4, strip 3
93 % before update 4, strip 3
94 changeset: 4:443431ffac4f
94 changeset: 4:443431ffac4f
95 tag: tip
95 tag: tip
96 user: test
96 user: test
97 date: Thu Jan 01 00:00:00 1970 +0000
97 date: Thu Jan 01 00:00:00 1970 +0000
98 summary: e
98 summary: e
99
99
100 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
100 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
101 saved backup bundle to $TESTTMP/test/.hg/strip-backup/*-backup.hg (glob)
101 saved backup bundle to $TESTTMP/test/.hg/strip-backup/*-backup.hg (glob)
102 % after update 4, strip 3
102 % after update 4, strip 3
103 changeset: 1:ef3a871183d7
103 changeset: 1:ef3a871183d7
104 user: test
104 user: test
105 date: Thu Jan 01 00:00:00 1970 +0000
105 date: Thu Jan 01 00:00:00 1970 +0000
106 summary: b
106 summary: b
107
107
108 $ teststrip 1 4
108 $ teststrip 1 4
109 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
109 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
110 % before update 1, strip 4
110 % before update 1, strip 4
111 changeset: 1:ef3a871183d7
111 changeset: 1:ef3a871183d7
112 user: test
112 user: test
113 date: Thu Jan 01 00:00:00 1970 +0000
113 date: Thu Jan 01 00:00:00 1970 +0000
114 summary: b
114 summary: b
115
115
116 saved backup bundle to $TESTTMP/test/.hg/strip-backup/*-backup.hg (glob)
116 saved backup bundle to $TESTTMP/test/.hg/strip-backup/*-backup.hg (glob)
117 % after update 1, strip 4
117 % after update 1, strip 4
118 changeset: 1:ef3a871183d7
118 changeset: 1:ef3a871183d7
119 user: test
119 user: test
120 date: Thu Jan 01 00:00:00 1970 +0000
120 date: Thu Jan 01 00:00:00 1970 +0000
121 summary: b
121 summary: b
122
122
123 $ teststrip 4 2
123 $ teststrip 4 2
124 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
124 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
125 % before update 4, strip 2
125 % before update 4, strip 2
126 changeset: 4:443431ffac4f
126 changeset: 4:443431ffac4f
127 tag: tip
127 tag: tip
128 user: test
128 user: test
129 date: Thu Jan 01 00:00:00 1970 +0000
129 date: Thu Jan 01 00:00:00 1970 +0000
130 summary: e
130 summary: e
131
131
132 saved backup bundle to $TESTTMP/test/.hg/strip-backup/*-backup.hg (glob)
132 saved backup bundle to $TESTTMP/test/.hg/strip-backup/*-backup.hg (glob)
133 % after update 4, strip 2
133 % after update 4, strip 2
134 changeset: 3:443431ffac4f
134 changeset: 3:443431ffac4f
135 tag: tip
135 tag: tip
136 user: test
136 user: test
137 date: Thu Jan 01 00:00:00 1970 +0000
137 date: Thu Jan 01 00:00:00 1970 +0000
138 summary: e
138 summary: e
139
139
140 $ teststrip 4 1
140 $ teststrip 4 1
141 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
141 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
142 % before update 4, strip 1
142 % before update 4, strip 1
143 changeset: 4:264128213d29
143 changeset: 4:264128213d29
144 tag: tip
144 tag: tip
145 parent: 1:ef3a871183d7
145 parent: 1:ef3a871183d7
146 user: test
146 user: test
147 date: Thu Jan 01 00:00:00 1970 +0000
147 date: Thu Jan 01 00:00:00 1970 +0000
148 summary: c
148 summary: c
149
149
150 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
150 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
151 saved backup bundle to $TESTTMP/test/.hg/strip-backup/*-backup.hg (glob)
151 saved backup bundle to $TESTTMP/test/.hg/strip-backup/*-backup.hg (glob)
152 % after update 4, strip 1
152 % after update 4, strip 1
153 changeset: 0:9ab35a2d17cb
153 changeset: 0:9ab35a2d17cb
154 tag: tip
154 tag: tip
155 user: test
155 user: test
156 date: Thu Jan 01 00:00:00 1970 +0000
156 date: Thu Jan 01 00:00:00 1970 +0000
157 summary: a
157 summary: a
158
158
159 $ teststrip null 4
159 $ teststrip null 4
160 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
160 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
161 % before update null, strip 4
161 % before update null, strip 4
162 saved backup bundle to $TESTTMP/test/.hg/strip-backup/*-backup.hg (glob)
162 saved backup bundle to $TESTTMP/test/.hg/strip-backup/*-backup.hg (glob)
163 % after update null, strip 4
163 % after update null, strip 4
164
164
165 $ hg log
165 $ hg log
166 changeset: 4:264128213d29
166 changeset: 4:264128213d29
167 tag: tip
167 tag: tip
168 parent: 1:ef3a871183d7
168 parent: 1:ef3a871183d7
169 user: test
169 user: test
170 date: Thu Jan 01 00:00:00 1970 +0000
170 date: Thu Jan 01 00:00:00 1970 +0000
171 summary: c
171 summary: c
172
172
173 changeset: 3:443431ffac4f
173 changeset: 3:443431ffac4f
174 user: test
174 user: test
175 date: Thu Jan 01 00:00:00 1970 +0000
175 date: Thu Jan 01 00:00:00 1970 +0000
176 summary: e
176 summary: e
177
177
178 changeset: 2:65bd5f99a4a3
178 changeset: 2:65bd5f99a4a3
179 user: test
179 user: test
180 date: Thu Jan 01 00:00:00 1970 +0000
180 date: Thu Jan 01 00:00:00 1970 +0000
181 summary: d
181 summary: d
182
182
183 changeset: 1:ef3a871183d7
183 changeset: 1:ef3a871183d7
184 user: test
184 user: test
185 date: Thu Jan 01 00:00:00 1970 +0000
185 date: Thu Jan 01 00:00:00 1970 +0000
186 summary: b
186 summary: b
187
187
188 changeset: 0:9ab35a2d17cb
188 changeset: 0:9ab35a2d17cb
189 user: test
189 user: test
190 date: Thu Jan 01 00:00:00 1970 +0000
190 date: Thu Jan 01 00:00:00 1970 +0000
191 summary: a
191 summary: a
192
192
193 $ hg up -C 4
193 $ hg up -C 4
194 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
194 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
195 $ hg parents
195 $ hg parents
196 changeset: 4:264128213d29
196 changeset: 4:264128213d29
197 tag: tip
197 tag: tip
198 parent: 1:ef3a871183d7
198 parent: 1:ef3a871183d7
199 user: test
199 user: test
200 date: Thu Jan 01 00:00:00 1970 +0000
200 date: Thu Jan 01 00:00:00 1970 +0000
201 summary: c
201 summary: c
202
202
203
203
204 $ hg --traceback strip 4
204 $ hg --traceback strip 4
205 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
205 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
206 saved backup bundle to $TESTTMP/test/.hg/strip-backup/264128213d29-0b39d6bf-backup.hg (glob)
206 saved backup bundle to $TESTTMP/test/.hg/strip-backup/264128213d29-0b39d6bf-backup.hg (glob)
207 $ hg parents
207 $ hg parents
208 changeset: 1:ef3a871183d7
208 changeset: 1:ef3a871183d7
209 user: test
209 user: test
210 date: Thu Jan 01 00:00:00 1970 +0000
210 date: Thu Jan 01 00:00:00 1970 +0000
211 summary: b
211 summary: b
212
212
213 $ hg debugbundle .hg/strip-backup/*
213 $ hg debugbundle .hg/strip-backup/*
214 Stream params: sortdict([('Compression', 'BZ')])
214 Stream params: sortdict([('Compression', 'BZ')])
215 changegroup -- "sortdict([('version', '02'), ('nbchanges', '1')])"
215 changegroup -- "sortdict([('version', '02'), ('nbchanges', '1')])"
216 264128213d290d868c54642d13aeaa3675551a78
216 264128213d290d868c54642d13aeaa3675551a78
217 phase-heads -- 'sortdict()'
217 phase-heads -- 'sortdict()'
218 264128213d290d868c54642d13aeaa3675551a78 draft
218 264128213d290d868c54642d13aeaa3675551a78 draft
219 $ hg pull .hg/strip-backup/*
219 $ hg pull .hg/strip-backup/*
220 pulling from .hg/strip-backup/264128213d29-0b39d6bf-backup.hg
220 pulling from .hg/strip-backup/264128213d29-0b39d6bf-backup.hg
221 searching for changes
221 searching for changes
222 adding changesets
222 adding changesets
223 adding manifests
223 adding manifests
224 adding file changes
224 adding file changes
225 added 1 changesets with 0 changes to 0 files (+1 heads)
225 added 1 changesets with 0 changes to 0 files (+1 heads)
226 (run 'hg heads' to see heads, 'hg merge' to merge)
226 (run 'hg heads' to see heads, 'hg merge' to merge)
227 $ rm .hg/strip-backup/*
227 $ rm .hg/strip-backup/*
228 $ hg log --graph
228 $ hg log --graph
229 o changeset: 4:264128213d29
229 o changeset: 4:264128213d29
230 | tag: tip
230 | tag: tip
231 | parent: 1:ef3a871183d7
231 | parent: 1:ef3a871183d7
232 | user: test
232 | user: test
233 | date: Thu Jan 01 00:00:00 1970 +0000
233 | date: Thu Jan 01 00:00:00 1970 +0000
234 | summary: c
234 | summary: c
235 |
235 |
236 | o changeset: 3:443431ffac4f
236 | o changeset: 3:443431ffac4f
237 | | user: test
237 | | user: test
238 | | date: Thu Jan 01 00:00:00 1970 +0000
238 | | date: Thu Jan 01 00:00:00 1970 +0000
239 | | summary: e
239 | | summary: e
240 | |
240 | |
241 | o changeset: 2:65bd5f99a4a3
241 | o changeset: 2:65bd5f99a4a3
242 |/ user: test
242 |/ user: test
243 | date: Thu Jan 01 00:00:00 1970 +0000
243 | date: Thu Jan 01 00:00:00 1970 +0000
244 | summary: d
244 | summary: d
245 |
245 |
246 @ changeset: 1:ef3a871183d7
246 @ changeset: 1:ef3a871183d7
247 | user: test
247 | user: test
248 | date: Thu Jan 01 00:00:00 1970 +0000
248 | date: Thu Jan 01 00:00:00 1970 +0000
249 | summary: b
249 | summary: b
250 |
250 |
251 o changeset: 0:9ab35a2d17cb
251 o changeset: 0:9ab35a2d17cb
252 user: test
252 user: test
253 date: Thu Jan 01 00:00:00 1970 +0000
253 date: Thu Jan 01 00:00:00 1970 +0000
254 summary: a
254 summary: a
255
255
256 $ hg up -C 2
256 $ hg up -C 2
257 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
257 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
258 $ hg merge 4
258 $ hg merge 4
259 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
259 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
260 (branch merge, don't forget to commit)
260 (branch merge, don't forget to commit)
261
261
262 before strip of merge parent
262 before strip of merge parent
263
263
264 $ hg parents
264 $ hg parents
265 changeset: 2:65bd5f99a4a3
265 changeset: 2:65bd5f99a4a3
266 user: test
266 user: test
267 date: Thu Jan 01 00:00:00 1970 +0000
267 date: Thu Jan 01 00:00:00 1970 +0000
268 summary: d
268 summary: d
269
269
270 changeset: 4:264128213d29
270 changeset: 4:264128213d29
271 tag: tip
271 tag: tip
272 parent: 1:ef3a871183d7
272 parent: 1:ef3a871183d7
273 user: test
273 user: test
274 date: Thu Jan 01 00:00:00 1970 +0000
274 date: Thu Jan 01 00:00:00 1970 +0000
275 summary: c
275 summary: c
276
276
277 $ hg strip 4
277 $ hg strip 4
278 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
278 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
279 saved backup bundle to $TESTTMP/test/.hg/strip-backup/*-backup.hg (glob)
279 saved backup bundle to $TESTTMP/test/.hg/strip-backup/*-backup.hg (glob)
280
280
281 after strip of merge parent
281 after strip of merge parent
282
282
283 $ hg parents
283 $ hg parents
284 changeset: 1:ef3a871183d7
284 changeset: 1:ef3a871183d7
285 user: test
285 user: test
286 date: Thu Jan 01 00:00:00 1970 +0000
286 date: Thu Jan 01 00:00:00 1970 +0000
287 summary: b
287 summary: b
288
288
289 $ restore
289 $ restore
290
290
291 $ hg up
291 $ hg up
292 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
292 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
293 updated to "264128213d29: c"
293 updated to "264128213d29: c"
294 1 other heads for branch "default"
294 1 other heads for branch "default"
295 $ hg log -G
295 $ hg log -G
296 @ changeset: 4:264128213d29
296 @ changeset: 4:264128213d29
297 | tag: tip
297 | tag: tip
298 | parent: 1:ef3a871183d7
298 | parent: 1:ef3a871183d7
299 | user: test
299 | user: test
300 | date: Thu Jan 01 00:00:00 1970 +0000
300 | date: Thu Jan 01 00:00:00 1970 +0000
301 | summary: c
301 | summary: c
302 |
302 |
303 | o changeset: 3:443431ffac4f
303 | o changeset: 3:443431ffac4f
304 | | user: test
304 | | user: test
305 | | date: Thu Jan 01 00:00:00 1970 +0000
305 | | date: Thu Jan 01 00:00:00 1970 +0000
306 | | summary: e
306 | | summary: e
307 | |
307 | |
308 | o changeset: 2:65bd5f99a4a3
308 | o changeset: 2:65bd5f99a4a3
309 |/ user: test
309 |/ user: test
310 | date: Thu Jan 01 00:00:00 1970 +0000
310 | date: Thu Jan 01 00:00:00 1970 +0000
311 | summary: d
311 | summary: d
312 |
312 |
313 o changeset: 1:ef3a871183d7
313 o changeset: 1:ef3a871183d7
314 | user: test
314 | user: test
315 | date: Thu Jan 01 00:00:00 1970 +0000
315 | date: Thu Jan 01 00:00:00 1970 +0000
316 | summary: b
316 | summary: b
317 |
317 |
318 o changeset: 0:9ab35a2d17cb
318 o changeset: 0:9ab35a2d17cb
319 user: test
319 user: test
320 date: Thu Jan 01 00:00:00 1970 +0000
320 date: Thu Jan 01 00:00:00 1970 +0000
321 summary: a
321 summary: a
322
322
323
323
324 2 is parent of 3, only one strip should happen
324 2 is parent of 3, only one strip should happen
325
325
326 $ hg strip "roots(2)" 3
326 $ hg strip "roots(2)" 3
327 saved backup bundle to $TESTTMP/test/.hg/strip-backup/*-backup.hg (glob)
327 saved backup bundle to $TESTTMP/test/.hg/strip-backup/*-backup.hg (glob)
328 $ hg log -G
328 $ hg log -G
329 @ changeset: 2:264128213d29
329 @ changeset: 2:264128213d29
330 | tag: tip
330 | tag: tip
331 | user: test
331 | user: test
332 | date: Thu Jan 01 00:00:00 1970 +0000
332 | date: Thu Jan 01 00:00:00 1970 +0000
333 | summary: c
333 | summary: c
334 |
334 |
335 o changeset: 1:ef3a871183d7
335 o changeset: 1:ef3a871183d7
336 | user: test
336 | user: test
337 | date: Thu Jan 01 00:00:00 1970 +0000
337 | date: Thu Jan 01 00:00:00 1970 +0000
338 | summary: b
338 | summary: b
339 |
339 |
340 o changeset: 0:9ab35a2d17cb
340 o changeset: 0:9ab35a2d17cb
341 user: test
341 user: test
342 date: Thu Jan 01 00:00:00 1970 +0000
342 date: Thu Jan 01 00:00:00 1970 +0000
343 summary: a
343 summary: a
344
344
345 $ restore
345 $ restore
346 $ hg log -G
346 $ hg log -G
347 o changeset: 4:443431ffac4f
347 o changeset: 4:443431ffac4f
348 | tag: tip
348 | tag: tip
349 | user: test
349 | user: test
350 | date: Thu Jan 01 00:00:00 1970 +0000
350 | date: Thu Jan 01 00:00:00 1970 +0000
351 | summary: e
351 | summary: e
352 |
352 |
353 o changeset: 3:65bd5f99a4a3
353 o changeset: 3:65bd5f99a4a3
354 | parent: 1:ef3a871183d7
354 | parent: 1:ef3a871183d7
355 | user: test
355 | user: test
356 | date: Thu Jan 01 00:00:00 1970 +0000
356 | date: Thu Jan 01 00:00:00 1970 +0000
357 | summary: d
357 | summary: d
358 |
358 |
359 | @ changeset: 2:264128213d29
359 | @ changeset: 2:264128213d29
360 |/ user: test
360 |/ user: test
361 | date: Thu Jan 01 00:00:00 1970 +0000
361 | date: Thu Jan 01 00:00:00 1970 +0000
362 | summary: c
362 | summary: c
363 |
363 |
364 o changeset: 1:ef3a871183d7
364 o changeset: 1:ef3a871183d7
365 | user: test
365 | user: test
366 | date: Thu Jan 01 00:00:00 1970 +0000
366 | date: Thu Jan 01 00:00:00 1970 +0000
367 | summary: b
367 | summary: b
368 |
368 |
369 o changeset: 0:9ab35a2d17cb
369 o changeset: 0:9ab35a2d17cb
370 user: test
370 user: test
371 date: Thu Jan 01 00:00:00 1970 +0000
371 date: Thu Jan 01 00:00:00 1970 +0000
372 summary: a
372 summary: a
373
373
374 Failed hook while applying "saveheads" bundle.
374 Failed hook while applying "saveheads" bundle.
375
375
376 $ hg strip 2 --config hooks.pretxnchangegroup.bad=false
376 $ hg strip 2 --config hooks.pretxnchangegroup.bad=false
377 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
377 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
378 saved backup bundle to $TESTTMP/test/.hg/strip-backup/*-backup.hg (glob)
378 saved backup bundle to $TESTTMP/test/.hg/strip-backup/*-backup.hg (glob)
379 transaction abort!
379 transaction abort!
380 rollback completed
380 rollback completed
381 strip failed, backup bundle stored in '$TESTTMP/test/.hg/strip-backup/*-backup.hg' (glob)
381 strip failed, backup bundle stored in '$TESTTMP/test/.hg/strip-backup/*-backup.hg' (glob)
382 strip failed, unrecovered changes stored in '$TESTTMP/test/.hg/strip-backup/*-temp.hg' (glob)
382 strip failed, unrecovered changes stored in '$TESTTMP/test/.hg/strip-backup/*-temp.hg' (glob)
383 (fix the problem, then recover the changesets with "hg unbundle '$TESTTMP/test/.hg/strip-backup/*-temp.hg'") (glob)
383 (fix the problem, then recover the changesets with "hg unbundle '$TESTTMP/test/.hg/strip-backup/*-temp.hg'") (glob)
384 abort: pretxnchangegroup.bad hook exited with status 1
384 abort: pretxnchangegroup.bad hook exited with status 1
385 [255]
385 [255]
386 $ restore
386 $ restore
387 $ hg log -G
387 $ hg log -G
388 o changeset: 4:443431ffac4f
388 o changeset: 4:443431ffac4f
389 | tag: tip
389 | tag: tip
390 | user: test
390 | user: test
391 | date: Thu Jan 01 00:00:00 1970 +0000
391 | date: Thu Jan 01 00:00:00 1970 +0000
392 | summary: e
392 | summary: e
393 |
393 |
394 o changeset: 3:65bd5f99a4a3
394 o changeset: 3:65bd5f99a4a3
395 | parent: 1:ef3a871183d7
395 | parent: 1:ef3a871183d7
396 | user: test
396 | user: test
397 | date: Thu Jan 01 00:00:00 1970 +0000
397 | date: Thu Jan 01 00:00:00 1970 +0000
398 | summary: d
398 | summary: d
399 |
399 |
400 | o changeset: 2:264128213d29
400 | o changeset: 2:264128213d29
401 |/ user: test
401 |/ user: test
402 | date: Thu Jan 01 00:00:00 1970 +0000
402 | date: Thu Jan 01 00:00:00 1970 +0000
403 | summary: c
403 | summary: c
404 |
404 |
405 @ changeset: 1:ef3a871183d7
405 @ changeset: 1:ef3a871183d7
406 | user: test
406 | user: test
407 | date: Thu Jan 01 00:00:00 1970 +0000
407 | date: Thu Jan 01 00:00:00 1970 +0000
408 | summary: b
408 | summary: b
409 |
409 |
410 o changeset: 0:9ab35a2d17cb
410 o changeset: 0:9ab35a2d17cb
411 user: test
411 user: test
412 date: Thu Jan 01 00:00:00 1970 +0000
412 date: Thu Jan 01 00:00:00 1970 +0000
413 summary: a
413 summary: a
414
414
415
415
416 2 different branches: 2 strips
416 2 different branches: 2 strips
417
417
418 $ hg strip 2 4
418 $ hg strip 2 4
419 saved backup bundle to $TESTTMP/test/.hg/strip-backup/*-backup.hg (glob)
419 saved backup bundle to $TESTTMP/test/.hg/strip-backup/*-backup.hg (glob)
420 $ hg log -G
420 $ hg log -G
421 o changeset: 2:65bd5f99a4a3
421 o changeset: 2:65bd5f99a4a3
422 | tag: tip
422 | tag: tip
423 | user: test
423 | user: test
424 | date: Thu Jan 01 00:00:00 1970 +0000
424 | date: Thu Jan 01 00:00:00 1970 +0000
425 | summary: d
425 | summary: d
426 |
426 |
427 @ changeset: 1:ef3a871183d7
427 @ changeset: 1:ef3a871183d7
428 | user: test
428 | user: test
429 | date: Thu Jan 01 00:00:00 1970 +0000
429 | date: Thu Jan 01 00:00:00 1970 +0000
430 | summary: b
430 | summary: b
431 |
431 |
432 o changeset: 0:9ab35a2d17cb
432 o changeset: 0:9ab35a2d17cb
433 user: test
433 user: test
434 date: Thu Jan 01 00:00:00 1970 +0000
434 date: Thu Jan 01 00:00:00 1970 +0000
435 summary: a
435 summary: a
436
436
437 $ restore
437 $ restore
438
438
439 2 different branches and a common ancestor: 1 strip
439 2 different branches and a common ancestor: 1 strip
440
440
441 $ hg strip 1 "2|4"
441 $ hg strip 1 "2|4"
442 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
442 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
443 saved backup bundle to $TESTTMP/test/.hg/strip-backup/*-backup.hg (glob)
443 saved backup bundle to $TESTTMP/test/.hg/strip-backup/*-backup.hg (glob)
444 $ restore
444 $ restore
445
445
446 verify fncache is kept up-to-date
446 verify fncache is kept up-to-date
447
447
448 $ touch a
448 $ touch a
449 $ hg ci -qAm a
449 $ hg ci -qAm a
450 $ cat .hg/store/fncache | sort
450 $ cat .hg/store/fncache | sort
451 data/a.i
451 data/a.i
452 data/bar.i
452 data/bar.i
453 $ hg strip tip
453 $ hg strip tip
454 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
454 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
455 saved backup bundle to $TESTTMP/test/.hg/strip-backup/*-backup.hg (glob)
455 saved backup bundle to $TESTTMP/test/.hg/strip-backup/*-backup.hg (glob)
456 $ cat .hg/store/fncache
456 $ cat .hg/store/fncache
457 data/bar.i
457 data/bar.i
458
458
459 stripping an empty revset
459 stripping an empty revset
460
460
461 $ hg strip "1 and not 1"
461 $ hg strip "1 and not 1"
462 abort: empty revision set
462 abort: empty revision set
463 [255]
463 [255]
464
464
465 remove branchy history for qimport tests
465 remove branchy history for qimport tests
466
466
467 $ hg strip 3
467 $ hg strip 3
468 saved backup bundle to $TESTTMP/test/.hg/strip-backup/*-backup.hg (glob)
468 saved backup bundle to $TESTTMP/test/.hg/strip-backup/*-backup.hg (glob)
469
469
470
470
471 strip of applied mq should cleanup status file
471 strip of applied mq should cleanup status file
472
472
473 $ echo "mq=" >> $HGRCPATH
473 $ echo "mq=" >> $HGRCPATH
474 $ hg up -C 3
474 $ hg up -C 3
475 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
475 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
476 $ echo fooagain >> bar
476 $ echo fooagain >> bar
477 $ hg ci -mf
477 $ hg ci -mf
478 $ hg qimport -r tip:2
478 $ hg qimport -r tip:2
479
479
480 applied patches before strip
480 applied patches before strip
481
481
482 $ hg qapplied
482 $ hg qapplied
483 d
483 d
484 e
484 e
485 f
485 f
486
486
487 stripping revision in queue
487 stripping revision in queue
488
488
489 $ hg strip 3
489 $ hg strip 3
490 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
490 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
491 saved backup bundle to $TESTTMP/test/.hg/strip-backup/*-backup.hg (glob)
491 saved backup bundle to $TESTTMP/test/.hg/strip-backup/*-backup.hg (glob)
492
492
493 applied patches after stripping rev in queue
493 applied patches after stripping rev in queue
494
494
495 $ hg qapplied
495 $ hg qapplied
496 d
496 d
497
497
498 stripping ancestor of queue
498 stripping ancestor of queue
499
499
500 $ hg strip 1
500 $ hg strip 1
501 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
501 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
502 saved backup bundle to $TESTTMP/test/.hg/strip-backup/*-backup.hg (glob)
502 saved backup bundle to $TESTTMP/test/.hg/strip-backup/*-backup.hg (glob)
503
503
504 applied patches after stripping ancestor of queue
504 applied patches after stripping ancestor of queue
505
505
506 $ hg qapplied
506 $ hg qapplied
507
507
508 Verify strip protects against stripping wc parent when there are uncommitted mods
508 Verify strip protects against stripping wc parent when there are uncommitted mods
509
509
510 $ echo b > b
510 $ echo b > b
511 $ echo bb > bar
511 $ echo bb > bar
512 $ hg add b
512 $ hg add b
513 $ hg ci -m 'b'
513 $ hg ci -m 'b'
514 $ hg log --graph
514 $ hg log --graph
515 @ changeset: 1:76dcf9fab855
515 @ changeset: 1:76dcf9fab855
516 | tag: tip
516 | tag: tip
517 | user: test
517 | user: test
518 | date: Thu Jan 01 00:00:00 1970 +0000
518 | date: Thu Jan 01 00:00:00 1970 +0000
519 | summary: b
519 | summary: b
520 |
520 |
521 o changeset: 0:9ab35a2d17cb
521 o changeset: 0:9ab35a2d17cb
522 user: test
522 user: test
523 date: Thu Jan 01 00:00:00 1970 +0000
523 date: Thu Jan 01 00:00:00 1970 +0000
524 summary: a
524 summary: a
525
525
526 $ hg up 0
526 $ hg up 0
527 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
527 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
528 $ echo c > bar
528 $ echo c > bar
529 $ hg up -t false
529 $ hg up -t false
530 merging bar
530 merging bar
531 merging bar failed!
531 merging bar failed!
532 1 files updated, 0 files merged, 0 files removed, 1 files unresolved
532 1 files updated, 0 files merged, 0 files removed, 1 files unresolved
533 use 'hg resolve' to retry unresolved file merges
533 use 'hg resolve' to retry unresolved file merges
534 [1]
534 [1]
535 $ hg sum
535 $ hg sum
536 parent: 1:76dcf9fab855 tip
536 parent: 1:76dcf9fab855 tip
537 b
537 b
538 branch: default
538 branch: default
539 commit: 1 modified, 1 unknown, 1 unresolved
539 commit: 1 modified, 1 unknown, 1 unresolved
540 update: (current)
540 update: (current)
541 phases: 2 draft
541 phases: 2 draft
542 mq: 3 unapplied
542 mq: 3 unapplied
543
543
544 $ echo c > b
544 $ echo c > b
545 $ hg strip tip
545 $ hg strip tip
546 abort: local changes found
546 abort: local changes found
547 [255]
547 [255]
548 $ hg strip tip --keep
548 $ hg strip tip --keep
549 saved backup bundle to $TESTTMP/test/.hg/strip-backup/*-backup.hg (glob)
549 saved backup bundle to $TESTTMP/test/.hg/strip-backup/*-backup.hg (glob)
550 $ hg log --graph
550 $ hg log --graph
551 @ changeset: 0:9ab35a2d17cb
551 @ changeset: 0:9ab35a2d17cb
552 tag: tip
552 tag: tip
553 user: test
553 user: test
554 date: Thu Jan 01 00:00:00 1970 +0000
554 date: Thu Jan 01 00:00:00 1970 +0000
555 summary: a
555 summary: a
556
556
557 $ hg status
557 $ hg status
558 M bar
558 M bar
559 ? b
559 ? b
560 ? bar.orig
560 ? bar.orig
561
561
562 $ rm bar.orig
562 $ rm bar.orig
563 $ hg sum
563 $ hg sum
564 parent: 0:9ab35a2d17cb tip
564 parent: 0:9ab35a2d17cb tip
565 a
565 a
566 branch: default
566 branch: default
567 commit: 1 modified, 1 unknown
567 commit: 1 modified, 1 unknown
568 update: (current)
568 update: (current)
569 phases: 1 draft
569 phases: 1 draft
570 mq: 3 unapplied
570 mq: 3 unapplied
571
571
572 Strip adds, removes, modifies with --keep
572 Strip adds, removes, modifies with --keep
573
573
574 $ touch b
574 $ touch b
575 $ hg add b
575 $ hg add b
576 $ hg commit -mb
576 $ hg commit -mb
577 $ touch c
577 $ touch c
578
578
579 ... with a clean working dir
579 ... with a clean working dir
580
580
581 $ hg add c
581 $ hg add c
582 $ hg rm bar
582 $ hg rm bar
583 $ hg commit -mc
583 $ hg commit -mc
584 $ hg status
584 $ hg status
585 $ hg strip --keep tip
585 $ hg strip --keep tip
586 saved backup bundle to $TESTTMP/test/.hg/strip-backup/*-backup.hg (glob)
586 saved backup bundle to $TESTTMP/test/.hg/strip-backup/*-backup.hg (glob)
587 $ hg status
587 $ hg status
588 ! bar
588 ! bar
589 ? c
589 ? c
590
590
591 ... with a dirty working dir
591 ... with a dirty working dir
592
592
593 $ hg add c
593 $ hg add c
594 $ hg rm bar
594 $ hg rm bar
595 $ hg commit -mc
595 $ hg commit -mc
596 $ hg status
596 $ hg status
597 $ echo b > b
597 $ echo b > b
598 $ echo d > d
598 $ echo d > d
599 $ hg strip --keep tip
599 $ hg strip --keep tip
600 saved backup bundle to $TESTTMP/test/.hg/strip-backup/*-backup.hg (glob)
600 saved backup bundle to $TESTTMP/test/.hg/strip-backup/*-backup.hg (glob)
601 $ hg status
601 $ hg status
602 M b
602 M b
603 ! bar
603 ! bar
604 ? c
604 ? c
605 ? d
605 ? d
606
606
607 ... after updating the dirstate
607 ... after updating the dirstate
608 $ hg add c
608 $ hg add c
609 $ hg commit -mc
609 $ hg commit -mc
610 $ hg rm c
610 $ hg rm c
611 $ hg commit -mc
611 $ hg commit -mc
612 $ hg strip --keep '.^' -q
612 $ hg strip --keep '.^' -q
613 $ cd ..
613 $ cd ..
614
614
615 stripping many nodes on a complex graph (issue3299)
615 stripping many nodes on a complex graph (issue3299)
616
616
617 $ hg init issue3299
617 $ hg init issue3299
618 $ cd issue3299
618 $ cd issue3299
619 $ hg debugbuilddag '@a.:a@b.:b.:x<a@a.:a<b@b.:b<a@a.:a'
619 $ hg debugbuilddag '@a.:a@b.:b.:x<a@a.:a<b@b.:b<a@a.:a'
620 $ hg strip 'not ancestors(x)'
620 $ hg strip 'not ancestors(x)'
621 saved backup bundle to $TESTTMP/issue3299/.hg/strip-backup/*-backup.hg (glob)
621 saved backup bundle to $TESTTMP/issue3299/.hg/strip-backup/*-backup.hg (glob)
622
622
623 test hg strip -B bookmark
623 test hg strip -B bookmark
624
624
625 $ cd ..
625 $ cd ..
626 $ hg init bookmarks
626 $ hg init bookmarks
627 $ cd bookmarks
627 $ cd bookmarks
628 $ hg debugbuilddag '..<2.*1/2:m<2+3:c<m+3:a<2.:b<m+2:d<2.:e<m+1:f'
628 $ hg debugbuilddag '..<2.*1/2:m<2+3:c<m+3:a<2.:b<m+2:d<2.:e<m+1:f'
629 $ hg bookmark -r 'a' 'todelete'
629 $ hg bookmark -r 'a' 'todelete'
630 $ hg bookmark -r 'b' 'B'
630 $ hg bookmark -r 'b' 'B'
631 $ hg bookmark -r 'b' 'nostrip'
631 $ hg bookmark -r 'b' 'nostrip'
632 $ hg bookmark -r 'c' 'delete'
632 $ hg bookmark -r 'c' 'delete'
633 $ hg bookmark -r 'd' 'multipledelete1'
633 $ hg bookmark -r 'd' 'multipledelete1'
634 $ hg bookmark -r 'e' 'multipledelete2'
634 $ hg bookmark -r 'e' 'multipledelete2'
635 $ hg bookmark -r 'f' 'singlenode1'
635 $ hg bookmark -r 'f' 'singlenode1'
636 $ hg bookmark -r 'f' 'singlenode2'
636 $ hg bookmark -r 'f' 'singlenode2'
637 $ hg up -C todelete
637 $ hg up -C todelete
638 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
638 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
639 (activating bookmark todelete)
639 (activating bookmark todelete)
640 $ hg strip -B nostrip
640 $ hg strip -B nostrip
641 bookmark 'nostrip' deleted
641 bookmark 'nostrip' deleted
642 abort: empty revision set
642 abort: empty revision set
643 [255]
643 [255]
644 $ hg strip -B todelete
644 $ hg strip -B todelete
645 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
645 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
646 saved backup bundle to $TESTTMP/bookmarks/.hg/strip-backup/*-backup.hg (glob)
646 saved backup bundle to $TESTTMP/bookmarks/.hg/strip-backup/*-backup.hg (glob)
647 bookmark 'todelete' deleted
647 bookmark 'todelete' deleted
648 $ hg id -ir dcbb326fdec2
648 $ hg id -ir dcbb326fdec2
649 abort: unknown revision 'dcbb326fdec2'!
649 abort: unknown revision 'dcbb326fdec2'!
650 [255]
650 [255]
651 $ hg id -ir d62d843c9a01
651 $ hg id -ir d62d843c9a01
652 d62d843c9a01
652 d62d843c9a01
653 $ hg bookmarks
653 $ hg bookmarks
654 B 9:ff43616e5d0f
654 B 9:ff43616e5d0f
655 delete 6:2702dd0c91e7
655 delete 6:2702dd0c91e7
656 multipledelete1 11:e46a4836065c
656 multipledelete1 11:e46a4836065c
657 multipledelete2 12:b4594d867745
657 multipledelete2 12:b4594d867745
658 singlenode1 13:43227190fef8
658 singlenode1 13:43227190fef8
659 singlenode2 13:43227190fef8
659 singlenode2 13:43227190fef8
660 $ hg strip -B multipledelete1 -B multipledelete2
660 $ hg strip -B multipledelete1 -B multipledelete2
661 saved backup bundle to $TESTTMP/bookmarks/.hg/strip-backup/e46a4836065c-89ec65c2-backup.hg (glob)
661 saved backup bundle to $TESTTMP/bookmarks/.hg/strip-backup/e46a4836065c-89ec65c2-backup.hg (glob)
662 bookmark 'multipledelete1' deleted
662 bookmark 'multipledelete1' deleted
663 bookmark 'multipledelete2' deleted
663 bookmark 'multipledelete2' deleted
664 $ hg id -ir e46a4836065c
664 $ hg id -ir e46a4836065c
665 abort: unknown revision 'e46a4836065c'!
665 abort: unknown revision 'e46a4836065c'!
666 [255]
666 [255]
667 $ hg id -ir b4594d867745
667 $ hg id -ir b4594d867745
668 abort: unknown revision 'b4594d867745'!
668 abort: unknown revision 'b4594d867745'!
669 [255]
669 [255]
670 $ hg strip -B singlenode1 -B singlenode2
670 $ hg strip -B singlenode1 -B singlenode2
671 saved backup bundle to $TESTTMP/bookmarks/.hg/strip-backup/43227190fef8-8da858f2-backup.hg (glob)
671 saved backup bundle to $TESTTMP/bookmarks/.hg/strip-backup/43227190fef8-8da858f2-backup.hg (glob)
672 bookmark 'singlenode1' deleted
672 bookmark 'singlenode1' deleted
673 bookmark 'singlenode2' deleted
673 bookmark 'singlenode2' deleted
674 $ hg id -ir 43227190fef8
674 $ hg id -ir 43227190fef8
675 abort: unknown revision '43227190fef8'!
675 abort: unknown revision '43227190fef8'!
676 [255]
676 [255]
677 $ hg strip -B unknownbookmark
677 $ hg strip -B unknownbookmark
678 abort: bookmark 'unknownbookmark' not found
678 abort: bookmark 'unknownbookmark' not found
679 [255]
679 [255]
680 $ hg strip -B unknownbookmark1 -B unknownbookmark2
680 $ hg strip -B unknownbookmark1 -B unknownbookmark2
681 abort: bookmark 'unknownbookmark1,unknownbookmark2' not found
681 abort: bookmark 'unknownbookmark1,unknownbookmark2' not found
682 [255]
682 [255]
683 $ hg strip -B delete -B unknownbookmark
683 $ hg strip -B delete -B unknownbookmark
684 abort: bookmark 'unknownbookmark' not found
684 abort: bookmark 'unknownbookmark' not found
685 [255]
685 [255]
686 $ hg strip -B delete
686 $ hg strip -B delete
687 saved backup bundle to $TESTTMP/bookmarks/.hg/strip-backup/*-backup.hg (glob)
687 saved backup bundle to $TESTTMP/bookmarks/.hg/strip-backup/*-backup.hg (glob)
688 bookmark 'delete' deleted
688 bookmark 'delete' deleted
689 $ hg id -ir 6:2702dd0c91e7
689 $ hg id -ir 6:2702dd0c91e7
690 abort: unknown revision '2702dd0c91e7'!
690 abort: unknown revision '2702dd0c91e7'!
691 [255]
691 [255]
692 $ hg update B
692 $ hg update B
693 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
693 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
694 (activating bookmark B)
694 (activating bookmark B)
695 $ echo a > a
695 $ echo a > a
696 $ hg add a
696 $ hg add a
697 $ hg strip -B B
697 $ hg strip -B B
698 abort: local changes found
698 abort: local changes found
699 [255]
699 [255]
700 $ hg bookmarks
700 $ hg bookmarks
701 * B 6:ff43616e5d0f
701 * B 6:ff43616e5d0f
702
702
703 Make sure no one adds back a -b option:
703 Make sure no one adds back a -b option:
704
704
705 $ hg strip -b tip
705 $ hg strip -b tip
706 hg strip: option -b not recognized
706 hg strip: option -b not recognized
707 hg strip [-k] [-f] [-B bookmark] [-r] REV...
707 hg strip [-k] [-f] [-B bookmark] [-r] REV...
708
708
709 strip changesets and all their descendants from the repository
709 strip changesets and all their descendants from the repository
710
710
711 (use 'hg help -e strip' to show help for the strip extension)
711 (use 'hg help -e strip' to show help for the strip extension)
712
712
713 options ([+] can be repeated):
713 options ([+] can be repeated):
714
714
715 -r --rev REV [+] strip specified revision (optional, can specify
715 -r --rev REV [+] strip specified revision (optional, can specify
716 revisions without this option)
716 revisions without this option)
717 -f --force force removal of changesets, discard uncommitted
717 -f --force force removal of changesets, discard uncommitted
718 changes (no backup)
718 changes (no backup)
719 --no-backup no backups
719 --no-backup no backups
720 -k --keep do not modify working directory during strip
720 -k --keep do not modify working directory during strip
721 -B --bookmark VALUE [+] remove revs only reachable from given bookmark
721 -B --bookmark VALUE [+] remove revs only reachable from given bookmark
722 --mq operate on patch repository
722 --mq operate on patch repository
723
723
724 (use 'hg strip -h' to show more help)
724 (use 'hg strip -h' to show more help)
725 [255]
725 [255]
726
726
727 $ cd ..
727 $ cd ..
728
728
729 Verify bundles don't get overwritten:
729 Verify bundles don't get overwritten:
730
730
731 $ hg init doublebundle
731 $ hg init doublebundle
732 $ cd doublebundle
732 $ cd doublebundle
733 $ touch a
733 $ touch a
734 $ hg commit -Aqm a
734 $ hg commit -Aqm a
735 $ touch b
735 $ touch b
736 $ hg commit -Aqm b
736 $ hg commit -Aqm b
737 $ hg strip -r 0
737 $ hg strip -r 0
738 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
738 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
739 saved backup bundle to $TESTTMP/doublebundle/.hg/strip-backup/3903775176ed-e68910bd-backup.hg (glob)
739 saved backup bundle to $TESTTMP/doublebundle/.hg/strip-backup/3903775176ed-e68910bd-backup.hg (glob)
740 $ ls .hg/strip-backup
740 $ ls .hg/strip-backup
741 3903775176ed-e68910bd-backup.hg
741 3903775176ed-e68910bd-backup.hg
742 $ hg pull -q -r 3903775176ed .hg/strip-backup/3903775176ed-e68910bd-backup.hg
742 $ hg pull -q -r 3903775176ed .hg/strip-backup/3903775176ed-e68910bd-backup.hg
743 $ hg strip -r 0
743 $ hg strip -r 0
744 saved backup bundle to $TESTTMP/doublebundle/.hg/strip-backup/3903775176ed-54390173-backup.hg (glob)
744 saved backup bundle to $TESTTMP/doublebundle/.hg/strip-backup/3903775176ed-54390173-backup.hg (glob)
745 $ ls .hg/strip-backup
745 $ ls .hg/strip-backup
746 3903775176ed-54390173-backup.hg
746 3903775176ed-54390173-backup.hg
747 3903775176ed-e68910bd-backup.hg
747 3903775176ed-e68910bd-backup.hg
748 $ cd ..
748 $ cd ..
749
749
750 Test that we only bundle the stripped changesets (issue4736)
750 Test that we only bundle the stripped changesets (issue4736)
751 ------------------------------------------------------------
751 ------------------------------------------------------------
752
752
753 initialization (previous repo is empty anyway)
753 initialization (previous repo is empty anyway)
754
754
755 $ hg init issue4736
755 $ hg init issue4736
756 $ cd issue4736
756 $ cd issue4736
757 $ echo a > a
757 $ echo a > a
758 $ hg add a
758 $ hg add a
759 $ hg commit -m commitA
759 $ hg commit -m commitA
760 $ echo b > b
760 $ echo b > b
761 $ hg add b
761 $ hg add b
762 $ hg commit -m commitB
762 $ hg commit -m commitB
763 $ echo c > c
763 $ echo c > c
764 $ hg add c
764 $ hg add c
765 $ hg commit -m commitC
765 $ hg commit -m commitC
766 $ hg up 'desc(commitB)'
766 $ hg up 'desc(commitB)'
767 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
767 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
768 $ echo d > d
768 $ echo d > d
769 $ hg add d
769 $ hg add d
770 $ hg commit -m commitD
770 $ hg commit -m commitD
771 created new head
771 created new head
772 $ hg up 'desc(commitC)'
772 $ hg up 'desc(commitC)'
773 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
773 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
774 $ hg merge 'desc(commitD)'
774 $ hg merge 'desc(commitD)'
775 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
775 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
776 (branch merge, don't forget to commit)
776 (branch merge, don't forget to commit)
777 $ hg ci -m 'mergeCD'
777 $ hg ci -m 'mergeCD'
778 $ hg log -G
778 $ hg log -G
779 @ changeset: 4:d8db9d137221
779 @ changeset: 4:d8db9d137221
780 |\ tag: tip
780 |\ tag: tip
781 | | parent: 2:5c51d8d6557d
781 | | parent: 2:5c51d8d6557d
782 | | parent: 3:6625a5168474
782 | | parent: 3:6625a5168474
783 | | user: test
783 | | user: test
784 | | date: Thu Jan 01 00:00:00 1970 +0000
784 | | date: Thu Jan 01 00:00:00 1970 +0000
785 | | summary: mergeCD
785 | | summary: mergeCD
786 | |
786 | |
787 | o changeset: 3:6625a5168474
787 | o changeset: 3:6625a5168474
788 | | parent: 1:eca11cf91c71
788 | | parent: 1:eca11cf91c71
789 | | user: test
789 | | user: test
790 | | date: Thu Jan 01 00:00:00 1970 +0000
790 | | date: Thu Jan 01 00:00:00 1970 +0000
791 | | summary: commitD
791 | | summary: commitD
792 | |
792 | |
793 o | changeset: 2:5c51d8d6557d
793 o | changeset: 2:5c51d8d6557d
794 |/ user: test
794 |/ user: test
795 | date: Thu Jan 01 00:00:00 1970 +0000
795 | date: Thu Jan 01 00:00:00 1970 +0000
796 | summary: commitC
796 | summary: commitC
797 |
797 |
798 o changeset: 1:eca11cf91c71
798 o changeset: 1:eca11cf91c71
799 | user: test
799 | user: test
800 | date: Thu Jan 01 00:00:00 1970 +0000
800 | date: Thu Jan 01 00:00:00 1970 +0000
801 | summary: commitB
801 | summary: commitB
802 |
802 |
803 o changeset: 0:105141ef12d0
803 o changeset: 0:105141ef12d0
804 user: test
804 user: test
805 date: Thu Jan 01 00:00:00 1970 +0000
805 date: Thu Jan 01 00:00:00 1970 +0000
806 summary: commitA
806 summary: commitA
807
807
808
808
809 Check bundle behavior:
809 Check bundle behavior:
810
810
811 $ hg bundle -r 'desc(mergeCD)' --base 'desc(commitC)' ../issue4736.hg
811 $ hg bundle -r 'desc(mergeCD)' --base 'desc(commitC)' ../issue4736.hg
812 2 changesets found
812 2 changesets found
813 $ hg log -r 'bundle()' -R ../issue4736.hg
813 $ hg log -r 'bundle()' -R ../issue4736.hg
814 changeset: 3:6625a5168474
814 changeset: 3:6625a5168474
815 parent: 1:eca11cf91c71
815 parent: 1:eca11cf91c71
816 user: test
816 user: test
817 date: Thu Jan 01 00:00:00 1970 +0000
817 date: Thu Jan 01 00:00:00 1970 +0000
818 summary: commitD
818 summary: commitD
819
819
820 changeset: 4:d8db9d137221
820 changeset: 4:d8db9d137221
821 tag: tip
821 tag: tip
822 parent: 2:5c51d8d6557d
822 parent: 2:5c51d8d6557d
823 parent: 3:6625a5168474
823 parent: 3:6625a5168474
824 user: test
824 user: test
825 date: Thu Jan 01 00:00:00 1970 +0000
825 date: Thu Jan 01 00:00:00 1970 +0000
826 summary: mergeCD
826 summary: mergeCD
827
827
828
828
829 check strip behavior
829 check strip behavior
830
830
831 $ hg --config extensions.strip= strip 'desc(commitD)' --debug
831 $ hg --config extensions.strip= strip 'desc(commitD)' --debug
832 resolving manifests
832 resolving manifests
833 branchmerge: False, force: True, partial: False
833 branchmerge: False, force: True, partial: False
834 ancestor: d8db9d137221+, local: d8db9d137221+, remote: eca11cf91c71
834 ancestor: d8db9d137221+, local: d8db9d137221+, remote: eca11cf91c71
835 c: other deleted -> r
835 c: other deleted -> r
836 removing c
836 removing c
837 d: other deleted -> r
837 d: other deleted -> r
838 removing d
838 removing d
839 starting 4 threads for background file closing (?)
839 starting 4 threads for background file closing (?)
840 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
840 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
841 2 changesets found
841 2 changesets found
842 list of changesets:
842 list of changesets:
843 6625a516847449b6f0fa3737b9ba56e9f0f3032c
843 6625a516847449b6f0fa3737b9ba56e9f0f3032c
844 d8db9d1372214336d2b5570f20ee468d2c72fa8b
844 d8db9d1372214336d2b5570f20ee468d2c72fa8b
845 bundle2-output-bundle: "HG20", (1 params) 2 parts total
845 bundle2-output-bundle: "HG20", (1 params) 2 parts total
846 bundle2-output-part: "changegroup" (params: 1 mandatory 1 advisory) streamed payload
846 bundle2-output-part: "changegroup" (params: 1 mandatory 1 advisory) streamed payload
847 bundle2-output-part: "phase-heads" 24 bytes payload
847 bundle2-output-part: "phase-heads" 24 bytes payload
848 saved backup bundle to $TESTTMP/issue4736/.hg/strip-backup/6625a5168474-345bb43d-backup.hg (glob)
848 saved backup bundle to $TESTTMP/issue4736/.hg/strip-backup/6625a5168474-345bb43d-backup.hg (glob)
849 updating the branch cache
849 updating the branch cache
850 invalid branchheads cache (served): tip differs
850 invalid branchheads cache (served): tip differs
851 truncating cache/rbc-revs-v1 to 24
851 truncating cache/rbc-revs-v1 to 24
852 $ hg log -G
852 $ hg log -G
853 o changeset: 2:5c51d8d6557d
853 o changeset: 2:5c51d8d6557d
854 | tag: tip
854 | tag: tip
855 | user: test
855 | user: test
856 | date: Thu Jan 01 00:00:00 1970 +0000
856 | date: Thu Jan 01 00:00:00 1970 +0000
857 | summary: commitC
857 | summary: commitC
858 |
858 |
859 @ changeset: 1:eca11cf91c71
859 @ changeset: 1:eca11cf91c71
860 | user: test
860 | user: test
861 | date: Thu Jan 01 00:00:00 1970 +0000
861 | date: Thu Jan 01 00:00:00 1970 +0000
862 | summary: commitB
862 | summary: commitB
863 |
863 |
864 o changeset: 0:105141ef12d0
864 o changeset: 0:105141ef12d0
865 user: test
865 user: test
866 date: Thu Jan 01 00:00:00 1970 +0000
866 date: Thu Jan 01 00:00:00 1970 +0000
867 summary: commitA
867 summary: commitA
868
868
869
869
870 strip backup content
870 strip backup content
871
871
872 $ hg log -r 'bundle()' -R .hg/strip-backup/6625a5168474-*-backup.hg
872 $ hg log -r 'bundle()' -R .hg/strip-backup/6625a5168474-*-backup.hg
873 changeset: 3:6625a5168474
873 changeset: 3:6625a5168474
874 parent: 1:eca11cf91c71
874 parent: 1:eca11cf91c71
875 user: test
875 user: test
876 date: Thu Jan 01 00:00:00 1970 +0000
876 date: Thu Jan 01 00:00:00 1970 +0000
877 summary: commitD
877 summary: commitD
878
878
879 changeset: 4:d8db9d137221
879 changeset: 4:d8db9d137221
880 tag: tip
880 tag: tip
881 parent: 2:5c51d8d6557d
881 parent: 2:5c51d8d6557d
882 parent: 3:6625a5168474
882 parent: 3:6625a5168474
883 user: test
883 user: test
884 date: Thu Jan 01 00:00:00 1970 +0000
884 date: Thu Jan 01 00:00:00 1970 +0000
885 summary: mergeCD
885 summary: mergeCD
886
886
887 Check that the phase cache is properly invalidated after a strip with bookmark.
887 Check that the phase cache is properly invalidated after a strip with bookmark.
888
888
889 $ cat > ../stripstalephasecache.py << EOF
889 $ cat > ../stripstalephasecache.py << EOF
890 > from mercurial import extensions, localrepo
890 > from mercurial import extensions, localrepo
891 > def transactioncallback(orig, repo, desc, *args, **kwargs):
891 > def transactioncallback(orig, repo, desc, *args, **kwargs):
892 > def test(transaction):
892 > def test(transaction):
893 > # observe cache inconsistency
893 > # observe cache inconsistency
894 > try:
894 > try:
895 > [repo.changelog.node(r) for r in repo.revs("not public()")]
895 > [repo.changelog.node(r) for r in repo.revs("not public()")]
896 > except IndexError:
896 > except IndexError:
897 > repo.ui.status("Index error!\n")
897 > repo.ui.status("Index error!\n")
898 > transaction = orig(repo, desc, *args, **kwargs)
898 > transaction = orig(repo, desc, *args, **kwargs)
899 > # warm up the phase cache
899 > # warm up the phase cache
900 > list(repo.revs("not public()"))
900 > list(repo.revs("not public()"))
901 > if desc != 'strip':
901 > if desc != 'strip':
902 > transaction.addpostclose("phase invalidation test", test)
902 > transaction.addpostclose("phase invalidation test", test)
903 > return transaction
903 > return transaction
904 > def extsetup(ui):
904 > def extsetup(ui):
905 > extensions.wrapfunction(localrepo.localrepository, "transaction",
905 > extensions.wrapfunction(localrepo.localrepository, "transaction",
906 > transactioncallback)
906 > transactioncallback)
907 > EOF
907 > EOF
908 $ hg up -C 2
908 $ hg up -C 2
909 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
909 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
910 $ echo k > k
910 $ echo k > k
911 $ hg add k
911 $ hg add k
912 $ hg commit -m commitK
912 $ hg commit -m commitK
913 $ echo l > l
913 $ echo l > l
914 $ hg add l
914 $ hg add l
915 $ hg commit -m commitL
915 $ hg commit -m commitL
916 $ hg book -r tip blah
916 $ hg book -r tip blah
917 $ hg strip ".^" --config extensions.crash=$TESTTMP/stripstalephasecache.py
917 $ hg strip ".^" --config extensions.crash=$TESTTMP/stripstalephasecache.py
918 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
918 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
919 saved backup bundle to $TESTTMP/issue4736/.hg/strip-backup/8f0b4384875c-4fa10deb-backup.hg (glob)
919 saved backup bundle to $TESTTMP/issue4736/.hg/strip-backup/8f0b4384875c-4fa10deb-backup.hg (glob)
920 $ hg up -C 1
920 $ hg up -C 1
921 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
921 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
922
922
923 Error during post-close callback of the strip transaction
923 Error during post-close callback of the strip transaction
924 (They should be gracefully handled and reported)
924 (They should be gracefully handled and reported)
925
925
926 $ cat > ../crashstrip.py << EOF
926 $ cat > ../crashstrip.py << EOF
927 > from mercurial import error
927 > from mercurial import error
928 > def reposetup(ui, repo):
928 > def reposetup(ui, repo):
929 > class crashstriprepo(repo.__class__):
929 > class crashstriprepo(repo.__class__):
930 > def transaction(self, desc, *args, **kwargs):
930 > def transaction(self, desc, *args, **kwargs):
931 > tr = super(crashstriprepo, self).transaction(self, desc, *args, **kwargs)
931 > tr = super(crashstriprepo, self).transaction(self, desc, *args, **kwargs)
932 > if desc == 'strip':
932 > if desc == 'strip':
933 > def crash(tra): raise error.Abort('boom')
933 > def crash(tra): raise error.Abort('boom')
934 > tr.addpostclose('crash', crash)
934 > tr.addpostclose('crash', crash)
935 > return tr
935 > return tr
936 > repo.__class__ = crashstriprepo
936 > repo.__class__ = crashstriprepo
937 > EOF
937 > EOF
938 $ hg strip tip --config extensions.crash=$TESTTMP/crashstrip.py
938 $ hg strip tip --config extensions.crash=$TESTTMP/crashstrip.py
939 saved backup bundle to $TESTTMP/issue4736/.hg/strip-backup/5c51d8d6557d-70daef06-backup.hg (glob)
939 saved backup bundle to $TESTTMP/issue4736/.hg/strip-backup/5c51d8d6557d-70daef06-backup.hg (glob)
940 strip failed, backup bundle stored in '$TESTTMP/issue4736/.hg/strip-backup/5c51d8d6557d-70daef06-backup.hg' (glob)
940 strip failed, backup bundle stored in '$TESTTMP/issue4736/.hg/strip-backup/5c51d8d6557d-70daef06-backup.hg' (glob)
941 abort: boom
941 abort: boom
942 [255]
942 [255]
943
943
944 Use delayedstrip to strip inside a transaction
944 Use delayedstrip to strip inside a transaction
945
945
946 $ cd $TESTTMP
946 $ cd $TESTTMP
947 $ hg init delayedstrip
947 $ hg init delayedstrip
948 $ cd delayedstrip
948 $ cd delayedstrip
949 $ hg debugdrawdag <<'EOS'
949 $ hg debugdrawdag <<'EOS'
950 > D
950 > D
951 > |
951 > |
952 > C F H # Commit on top of "I",
952 > C F H # Commit on top of "I",
953 > | |/| # Strip B+D+I+E+G+H+Z
953 > | |/| # Strip B+D+I+E+G+H+Z
954 > I B E G
954 > I B E G
955 > \|/
955 > \|/
956 > A Z
956 > A Z
957 > EOS
957 > EOS
958 $ cp -R . ../scmutilcleanup
958
959
959 $ hg up -C I
960 $ hg up -C I
960 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
961 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
961 $ echo 3 >> I
962 $ echo 3 >> I
962 $ cat > $TESTTMP/delayedstrip.py <<EOF
963 $ cat > $TESTTMP/delayedstrip.py <<EOF
963 > from mercurial import repair, commands
964 > from mercurial import repair, commands
964 > def reposetup(ui, repo):
965 > def reposetup(ui, repo):
965 > def getnodes(expr):
966 > def getnodes(expr):
966 > return [repo.changelog.node(r) for r in repo.revs(expr)]
967 > return [repo.changelog.node(r) for r in repo.revs(expr)]
967 > with repo.wlock():
968 > with repo.wlock():
968 > with repo.lock():
969 > with repo.lock():
969 > with repo.transaction('delayedstrip'):
970 > with repo.transaction('delayedstrip'):
970 > repair.delayedstrip(ui, repo, getnodes('B+I+Z+D+E'), 'J')
971 > repair.delayedstrip(ui, repo, getnodes('B+I+Z+D+E'), 'J')
971 > repair.delayedstrip(ui, repo, getnodes('G+H+Z'), 'I')
972 > repair.delayedstrip(ui, repo, getnodes('G+H+Z'), 'I')
972 > commands.commit(ui, repo, message='J', date='0 0')
973 > commands.commit(ui, repo, message='J', date='0 0')
973 > EOF
974 > EOF
974 $ hg log -r . -T '\n' --config extensions.t=$TESTTMP/delayedstrip.py
975 $ hg log -r . -T '\n' --config extensions.t=$TESTTMP/delayedstrip.py
975 warning: orphaned descendants detected, not stripping 08ebfeb61bac, 112478962961, 7fb047a69f22
976 warning: orphaned descendants detected, not stripping 08ebfeb61bac, 112478962961, 7fb047a69f22
976 saved backup bundle to $TESTTMP/delayedstrip/.hg/strip-backup/f585351a92f8-81fa23b0-I.hg (glob)
977 saved backup bundle to $TESTTMP/delayedstrip/.hg/strip-backup/f585351a92f8-81fa23b0-I.hg (glob)
977
978
978 $ hg log -G -T '{rev}:{node|short} {desc}' -r 'sort(all(), topo)'
979 $ hg log -G -T '{rev}:{node|short} {desc}' -r 'sort(all(), topo)'
979 @ 6:2f2d51af6205 J
980 @ 6:2f2d51af6205 J
980 |
981 |
981 o 3:08ebfeb61bac I
982 o 3:08ebfeb61bac I
982 |
983 |
983 | o 5:64a8289d2492 F
984 | o 5:64a8289d2492 F
984 | |
985 | |
985 | o 2:7fb047a69f22 E
986 | o 2:7fb047a69f22 E
986 |/
987 |/
987 | o 4:26805aba1e60 C
988 | o 4:26805aba1e60 C
988 | |
989 | |
989 | o 1:112478962961 B
990 | o 1:112478962961 B
990 |/
991 |/
991 o 0:426bada5c675 A
992 o 0:426bada5c675 A
992
993
994 Test high-level scmutil.cleanupnodes API
995
996 $ cd $TESTTMP/scmutilcleanup
997 $ hg debugdrawdag <<'EOS'
998 > D2 F2 G2 # D2, F2, G2 are replacements for D, F, G
999 > | | |
1000 > C H G
1001 > EOS
1002 $ for i in B C D F G I Z; do
1003 > hg bookmark -i -r $i b-$i
1004 > done
1005 $ cp -R . ../scmutilcleanup.obsstore
1006
1007 $ cat > $TESTTMP/scmutilcleanup.py <<EOF
1008 > from mercurial import scmutil
1009 > def reposetup(ui, repo):
1010 > def nodes(expr):
1011 > return [repo.changelog.node(r) for r in repo.revs(expr)]
1012 > def node(expr):
1013 > return nodes(expr)[0]
1014 > with repo.wlock():
1015 > with repo.lock():
1016 > with repo.transaction('delayedstrip'):
1017 > mapping = {node('F'): [node('F2')],
1018 > node('D'): [node('D2')],
1019 > node('G'): [node('G2')]}
1020 > scmutil.cleanupnodes(repo, mapping, 'replace')
1021 > scmutil.cleanupnodes(repo, nodes('((B::)+I+Z)-D2'), 'replace')
1022 > EOF
1023 $ hg log -r . -T '\n' --config extensions.t=$TESTTMP/scmutilcleanup.py
1024 warning: orphaned descendants detected, not stripping 112478962961, 1fc8102cda62, 26805aba1e60
1025 saved backup bundle to $TESTTMP/scmutilcleanup/.hg/strip-backup/f585351a92f8-73fb7c03-replace.hg (glob)
1026
1027 $ hg log -G -T '{rev}:{node|short} {desc} {bookmarks}' -r 'sort(all(), topo)'
1028 o 8:1473d4b996d1 G2 b-G
1029 |
1030 | o 7:d94e89b773b6 F2 b-F
1031 | |
1032 | o 5:7fe5bac4c918 H
1033 |/|
1034 | o 3:7fb047a69f22 E
1035 | |
1036 | | o 6:7c78f703e465 D2 b-D
1037 | | |
1038 | | o 4:26805aba1e60 C
1039 | | |
1040 | | o 2:112478962961 B
1041 | |/
1042 o | 1:1fc8102cda62 G
1043 /
1044 o 0:426bada5c675 A b-B b-C b-I
1045
1046 $ hg bookmark
1047 b-B 0:426bada5c675
1048 b-C 0:426bada5c675
1049 b-D 6:7c78f703e465
1050 b-F 7:d94e89b773b6
1051 b-G 8:1473d4b996d1
1052 b-I 0:426bada5c675
1053 b-Z -1:000000000000
1054
1055 Test the above using obsstore "by the way". Not directly related to strip, but
1056 we have reusable code here
1057
1058 $ cd $TESTTMP/scmutilcleanup.obsstore
1059 $ cat >> .hg/hgrc <<EOF
1060 > [experimental]
1061 > evolution=all
1062 > evolution.track-operation=1
1063 > EOF
1064
1065 $ hg log -r . -T '\n' --config extensions.t=$TESTTMP/scmutilcleanup.py
1066
1067 $ rm .hg/localtags
1068 $ hg log -G -T '{rev}:{node|short} {desc} {bookmarks}' -r 'sort(all(), topo)'
1069 o 12:1473d4b996d1 G2 b-G
1070 |
1071 | o 11:d94e89b773b6 F2 b-F
1072 | |
1073 | o 8:7fe5bac4c918 H
1074 |/|
1075 | o 4:7fb047a69f22 E
1076 | |
1077 | | o 10:7c78f703e465 D2 b-D
1078 | | |
1079 | | x 6:26805aba1e60 C
1080 | | |
1081 | | x 3:112478962961 B
1082 | |/
1083 x | 1:1fc8102cda62 G
1084 /
1085 o 0:426bada5c675 A b-B b-C b-I
1086
1087 $ hg debugobsolete
1088 1fc8102cda6204549f031015641606ccf5513ec3 1473d4b996d1d1b121de6b39fab6a04fbf9d873e 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'replace', 'user': 'test'}
1089 64a8289d249234b9886244d379f15e6b650b28e3 d94e89b773b67e72642a931159ada8d1a9246998 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'replace', 'user': 'test'}
1090 f585351a92f85104bff7c284233c338b10eb1df7 7c78f703e465d73102cc8780667ce269c5208a40 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'replace', 'user': 'test'}
1091 48b9aae0607f43ff110d84e6883c151942add5ab 0 {0000000000000000000000000000000000000000} (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'replace', 'user': 'test'}
1092 112478962961147124edd43549aedd1a335e44bf 0 {426bada5c67598ca65036d57d9e4b64b0c1ce7a0} (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'replace', 'user': 'test'}
1093 08ebfeb61bac6e3f12079de774d285a0d6689eba 0 {426bada5c67598ca65036d57d9e4b64b0c1ce7a0} (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'replace', 'user': 'test'}
1094 26805aba1e600a82e93661149f2313866a221a7b 0 {112478962961147124edd43549aedd1a335e44bf} (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'replace', 'user': 'test'}
General Comments 0
You need to be logged in to leave comments. Login now