##// END OF EJS Templates
sidedatacopies: deal with upgrading and downgrading to that format...
marmoute -
r43418:843da183 default
parent child Browse files
Show More
@@ -1,957 +1,1009 b''
1 # copies.py - copy detection for Mercurial
1 # copies.py - copy detection for Mercurial
2 #
2 #
3 # Copyright 2008 Matt Mackall <mpm@selenic.com>
3 # Copyright 2008 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 collections
10 import collections
11 import heapq
11 import heapq
12 import os
12 import os
13
13
14 from .i18n import _
14 from .i18n import _
15
15
16
17 from .revlogutils.flagutil import REVIDX_SIDEDATA
18
16 from . import (
19 from . import (
17 error,
20 error,
18 match as matchmod,
21 match as matchmod,
19 node,
22 node,
20 pathutil,
23 pathutil,
21 pycompat,
24 pycompat,
22 util,
25 util,
23 )
26 )
27
28 from .revlogutils import sidedata as sidedatamod
29
24 from .utils import stringutil
30 from .utils import stringutil
25
31
26
32
27 def _findlimit(repo, ctxa, ctxb):
33 def _findlimit(repo, ctxa, ctxb):
28 """
34 """
29 Find the last revision that needs to be checked to ensure that a full
35 Find the last revision that needs to be checked to ensure that a full
30 transitive closure for file copies can be properly calculated.
36 transitive closure for file copies can be properly calculated.
31 Generally, this means finding the earliest revision number that's an
37 Generally, this means finding the earliest revision number that's an
32 ancestor of a or b but not both, except when a or b is a direct descendent
38 ancestor of a or b but not both, except when a or b is a direct descendent
33 of the other, in which case we can return the minimum revnum of a and b.
39 of the other, in which case we can return the minimum revnum of a and b.
34 """
40 """
35
41
36 # basic idea:
42 # basic idea:
37 # - mark a and b with different sides
43 # - mark a and b with different sides
38 # - if a parent's children are all on the same side, the parent is
44 # - if a parent's children are all on the same side, the parent is
39 # on that side, otherwise it is on no side
45 # on that side, otherwise it is on no side
40 # - walk the graph in topological order with the help of a heap;
46 # - walk the graph in topological order with the help of a heap;
41 # - add unseen parents to side map
47 # - add unseen parents to side map
42 # - clear side of any parent that has children on different sides
48 # - clear side of any parent that has children on different sides
43 # - track number of interesting revs that might still be on a side
49 # - track number of interesting revs that might still be on a side
44 # - track the lowest interesting rev seen
50 # - track the lowest interesting rev seen
45 # - quit when interesting revs is zero
51 # - quit when interesting revs is zero
46
52
47 cl = repo.changelog
53 cl = repo.changelog
48 wdirparents = None
54 wdirparents = None
49 a = ctxa.rev()
55 a = ctxa.rev()
50 b = ctxb.rev()
56 b = ctxb.rev()
51 if a is None:
57 if a is None:
52 wdirparents = (ctxa.p1(), ctxa.p2())
58 wdirparents = (ctxa.p1(), ctxa.p2())
53 a = node.wdirrev
59 a = node.wdirrev
54 if b is None:
60 if b is None:
55 assert not wdirparents
61 assert not wdirparents
56 wdirparents = (ctxb.p1(), ctxb.p2())
62 wdirparents = (ctxb.p1(), ctxb.p2())
57 b = node.wdirrev
63 b = node.wdirrev
58
64
59 side = {a: -1, b: 1}
65 side = {a: -1, b: 1}
60 visit = [-a, -b]
66 visit = [-a, -b]
61 heapq.heapify(visit)
67 heapq.heapify(visit)
62 interesting = len(visit)
68 interesting = len(visit)
63 limit = node.wdirrev
69 limit = node.wdirrev
64
70
65 while interesting:
71 while interesting:
66 r = -(heapq.heappop(visit))
72 r = -(heapq.heappop(visit))
67 if r == node.wdirrev:
73 if r == node.wdirrev:
68 parents = [pctx.rev() for pctx in wdirparents]
74 parents = [pctx.rev() for pctx in wdirparents]
69 else:
75 else:
70 parents = cl.parentrevs(r)
76 parents = cl.parentrevs(r)
71 if parents[1] == node.nullrev:
77 if parents[1] == node.nullrev:
72 parents = parents[:1]
78 parents = parents[:1]
73 for p in parents:
79 for p in parents:
74 if p not in side:
80 if p not in side:
75 # first time we see p; add it to visit
81 # first time we see p; add it to visit
76 side[p] = side[r]
82 side[p] = side[r]
77 if side[p]:
83 if side[p]:
78 interesting += 1
84 interesting += 1
79 heapq.heappush(visit, -p)
85 heapq.heappush(visit, -p)
80 elif side[p] and side[p] != side[r]:
86 elif side[p] and side[p] != side[r]:
81 # p was interesting but now we know better
87 # p was interesting but now we know better
82 side[p] = 0
88 side[p] = 0
83 interesting -= 1
89 interesting -= 1
84 if side[r]:
90 if side[r]:
85 limit = r # lowest rev visited
91 limit = r # lowest rev visited
86 interesting -= 1
92 interesting -= 1
87
93
88 # Consider the following flow (see test-commit-amend.t under issue4405):
94 # Consider the following flow (see test-commit-amend.t under issue4405):
89 # 1/ File 'a0' committed
95 # 1/ File 'a0' committed
90 # 2/ File renamed from 'a0' to 'a1' in a new commit (call it 'a1')
96 # 2/ File renamed from 'a0' to 'a1' in a new commit (call it 'a1')
91 # 3/ Move back to first commit
97 # 3/ Move back to first commit
92 # 4/ Create a new commit via revert to contents of 'a1' (call it 'a1-amend')
98 # 4/ Create a new commit via revert to contents of 'a1' (call it 'a1-amend')
93 # 5/ Rename file from 'a1' to 'a2' and commit --amend 'a1-msg'
99 # 5/ Rename file from 'a1' to 'a2' and commit --amend 'a1-msg'
94 #
100 #
95 # During the amend in step five, we will be in this state:
101 # During the amend in step five, we will be in this state:
96 #
102 #
97 # @ 3 temporary amend commit for a1-amend
103 # @ 3 temporary amend commit for a1-amend
98 # |
104 # |
99 # o 2 a1-amend
105 # o 2 a1-amend
100 # |
106 # |
101 # | o 1 a1
107 # | o 1 a1
102 # |/
108 # |/
103 # o 0 a0
109 # o 0 a0
104 #
110 #
105 # When _findlimit is called, a and b are revs 3 and 0, so limit will be 2,
111 # When _findlimit is called, a and b are revs 3 and 0, so limit will be 2,
106 # yet the filelog has the copy information in rev 1 and we will not look
112 # yet the filelog has the copy information in rev 1 and we will not look
107 # back far enough unless we also look at the a and b as candidates.
113 # back far enough unless we also look at the a and b as candidates.
108 # This only occurs when a is a descendent of b or visa-versa.
114 # This only occurs when a is a descendent of b or visa-versa.
109 return min(limit, a, b)
115 return min(limit, a, b)
110
116
111
117
112 def _filter(src, dst, t):
118 def _filter(src, dst, t):
113 """filters out invalid copies after chaining"""
119 """filters out invalid copies after chaining"""
114
120
115 # When _chain()'ing copies in 'a' (from 'src' via some other commit 'mid')
121 # When _chain()'ing copies in 'a' (from 'src' via some other commit 'mid')
116 # with copies in 'b' (from 'mid' to 'dst'), we can get the different cases
122 # with copies in 'b' (from 'mid' to 'dst'), we can get the different cases
117 # in the following table (not including trivial cases). For example, case 2
123 # in the following table (not including trivial cases). For example, case 2
118 # is where a file existed in 'src' and remained under that name in 'mid' and
124 # is where a file existed in 'src' and remained under that name in 'mid' and
119 # then was renamed between 'mid' and 'dst'.
125 # then was renamed between 'mid' and 'dst'.
120 #
126 #
121 # case src mid dst result
127 # case src mid dst result
122 # 1 x y - -
128 # 1 x y - -
123 # 2 x y y x->y
129 # 2 x y y x->y
124 # 3 x y x -
130 # 3 x y x -
125 # 4 x y z x->z
131 # 4 x y z x->z
126 # 5 - x y -
132 # 5 - x y -
127 # 6 x x y x->y
133 # 6 x x y x->y
128 #
134 #
129 # _chain() takes care of chaining the copies in 'a' and 'b', but it
135 # _chain() takes care of chaining the copies in 'a' and 'b', but it
130 # cannot tell the difference between cases 1 and 2, between 3 and 4, or
136 # cannot tell the difference between cases 1 and 2, between 3 and 4, or
131 # between 5 and 6, so it includes all cases in its result.
137 # between 5 and 6, so it includes all cases in its result.
132 # Cases 1, 3, and 5 are then removed by _filter().
138 # Cases 1, 3, and 5 are then removed by _filter().
133
139
134 for k, v in list(t.items()):
140 for k, v in list(t.items()):
135 # remove copies from files that didn't exist
141 # remove copies from files that didn't exist
136 if v not in src:
142 if v not in src:
137 del t[k]
143 del t[k]
138 # remove criss-crossed copies
144 # remove criss-crossed copies
139 elif k in src and v in dst:
145 elif k in src and v in dst:
140 del t[k]
146 del t[k]
141 # remove copies to files that were then removed
147 # remove copies to files that were then removed
142 elif k not in dst:
148 elif k not in dst:
143 del t[k]
149 del t[k]
144
150
145
151
146 def _chain(a, b):
152 def _chain(a, b):
147 """chain two sets of copies 'a' and 'b'"""
153 """chain two sets of copies 'a' and 'b'"""
148 t = a.copy()
154 t = a.copy()
149 for k, v in pycompat.iteritems(b):
155 for k, v in pycompat.iteritems(b):
150 if v in t:
156 if v in t:
151 t[k] = t[v]
157 t[k] = t[v]
152 else:
158 else:
153 t[k] = v
159 t[k] = v
154 return t
160 return t
155
161
156
162
157 def _tracefile(fctx, am, basemf, limit):
163 def _tracefile(fctx, am, basemf, limit):
158 """return file context that is the ancestor of fctx present in ancestor
164 """return file context that is the ancestor of fctx present in ancestor
159 manifest am, stopping after the first ancestor lower than limit"""
165 manifest am, stopping after the first ancestor lower than limit"""
160
166
161 for f in fctx.ancestors():
167 for f in fctx.ancestors():
162 path = f.path()
168 path = f.path()
163 if am.get(path, None) == f.filenode():
169 if am.get(path, None) == f.filenode():
164 return path
170 return path
165 if basemf and basemf.get(path, None) == f.filenode():
171 if basemf and basemf.get(path, None) == f.filenode():
166 return path
172 return path
167 if not f.isintroducedafter(limit):
173 if not f.isintroducedafter(limit):
168 return None
174 return None
169
175
170
176
171 def _dirstatecopies(repo, match=None):
177 def _dirstatecopies(repo, match=None):
172 ds = repo.dirstate
178 ds = repo.dirstate
173 c = ds.copies().copy()
179 c = ds.copies().copy()
174 for k in list(c):
180 for k in list(c):
175 if ds[k] not in b'anm' or (match and not match(k)):
181 if ds[k] not in b'anm' or (match and not match(k)):
176 del c[k]
182 del c[k]
177 return c
183 return c
178
184
179
185
180 def _computeforwardmissing(a, b, match=None):
186 def _computeforwardmissing(a, b, match=None):
181 """Computes which files are in b but not a.
187 """Computes which files are in b but not a.
182 This is its own function so extensions can easily wrap this call to see what
188 This is its own function so extensions can easily wrap this call to see what
183 files _forwardcopies is about to process.
189 files _forwardcopies is about to process.
184 """
190 """
185 ma = a.manifest()
191 ma = a.manifest()
186 mb = b.manifest()
192 mb = b.manifest()
187 return mb.filesnotin(ma, match=match)
193 return mb.filesnotin(ma, match=match)
188
194
189
195
190 def usechangesetcentricalgo(repo):
196 def usechangesetcentricalgo(repo):
191 """Checks if we should use changeset-centric copy algorithms"""
197 """Checks if we should use changeset-centric copy algorithms"""
192 if repo.filecopiesmode == b'changeset-sidedata':
198 if repo.filecopiesmode == b'changeset-sidedata':
193 return True
199 return True
194 readfrom = repo.ui.config(b'experimental', b'copies.read-from')
200 readfrom = repo.ui.config(b'experimental', b'copies.read-from')
195 changesetsource = (b'changeset-only', b'compatibility')
201 changesetsource = (b'changeset-only', b'compatibility')
196 return readfrom in changesetsource
202 return readfrom in changesetsource
197
203
198
204
199 def _committedforwardcopies(a, b, base, match):
205 def _committedforwardcopies(a, b, base, match):
200 """Like _forwardcopies(), but b.rev() cannot be None (working copy)"""
206 """Like _forwardcopies(), but b.rev() cannot be None (working copy)"""
201 # files might have to be traced back to the fctx parent of the last
207 # files might have to be traced back to the fctx parent of the last
202 # one-side-only changeset, but not further back than that
208 # one-side-only changeset, but not further back than that
203 repo = a._repo
209 repo = a._repo
204
210
205 if usechangesetcentricalgo(repo):
211 if usechangesetcentricalgo(repo):
206 return _changesetforwardcopies(a, b, match)
212 return _changesetforwardcopies(a, b, match)
207
213
208 debug = repo.ui.debugflag and repo.ui.configbool(b'devel', b'debug.copies')
214 debug = repo.ui.debugflag and repo.ui.configbool(b'devel', b'debug.copies')
209 dbg = repo.ui.debug
215 dbg = repo.ui.debug
210 if debug:
216 if debug:
211 dbg(b'debug.copies: looking into rename from %s to %s\n' % (a, b))
217 dbg(b'debug.copies: looking into rename from %s to %s\n' % (a, b))
212 limit = _findlimit(repo, a, b)
218 limit = _findlimit(repo, a, b)
213 if debug:
219 if debug:
214 dbg(b'debug.copies: search limit: %d\n' % limit)
220 dbg(b'debug.copies: search limit: %d\n' % limit)
215 am = a.manifest()
221 am = a.manifest()
216 basemf = None if base is None else base.manifest()
222 basemf = None if base is None else base.manifest()
217
223
218 # find where new files came from
224 # find where new files came from
219 # we currently don't try to find where old files went, too expensive
225 # we currently don't try to find where old files went, too expensive
220 # this means we can miss a case like 'hg rm b; hg cp a b'
226 # this means we can miss a case like 'hg rm b; hg cp a b'
221 cm = {}
227 cm = {}
222
228
223 # Computing the forward missing is quite expensive on large manifests, since
229 # Computing the forward missing is quite expensive on large manifests, since
224 # it compares the entire manifests. We can optimize it in the common use
230 # it compares the entire manifests. We can optimize it in the common use
225 # case of computing what copies are in a commit versus its parent (like
231 # case of computing what copies are in a commit versus its parent (like
226 # during a rebase or histedit). Note, we exclude merge commits from this
232 # during a rebase or histedit). Note, we exclude merge commits from this
227 # optimization, since the ctx.files() for a merge commit is not correct for
233 # optimization, since the ctx.files() for a merge commit is not correct for
228 # this comparison.
234 # this comparison.
229 forwardmissingmatch = match
235 forwardmissingmatch = match
230 if b.p1() == a and b.p2().node() == node.nullid:
236 if b.p1() == a and b.p2().node() == node.nullid:
231 filesmatcher = matchmod.exact(b.files())
237 filesmatcher = matchmod.exact(b.files())
232 forwardmissingmatch = matchmod.intersectmatchers(match, filesmatcher)
238 forwardmissingmatch = matchmod.intersectmatchers(match, filesmatcher)
233 missing = _computeforwardmissing(a, b, match=forwardmissingmatch)
239 missing = _computeforwardmissing(a, b, match=forwardmissingmatch)
234
240
235 ancestrycontext = a._repo.changelog.ancestors([b.rev()], inclusive=True)
241 ancestrycontext = a._repo.changelog.ancestors([b.rev()], inclusive=True)
236
242
237 if debug:
243 if debug:
238 dbg(b'debug.copies: missing files to search: %d\n' % len(missing))
244 dbg(b'debug.copies: missing files to search: %d\n' % len(missing))
239
245
240 for f in sorted(missing):
246 for f in sorted(missing):
241 if debug:
247 if debug:
242 dbg(b'debug.copies: tracing file: %s\n' % f)
248 dbg(b'debug.copies: tracing file: %s\n' % f)
243 fctx = b[f]
249 fctx = b[f]
244 fctx._ancestrycontext = ancestrycontext
250 fctx._ancestrycontext = ancestrycontext
245
251
246 if debug:
252 if debug:
247 start = util.timer()
253 start = util.timer()
248 opath = _tracefile(fctx, am, basemf, limit)
254 opath = _tracefile(fctx, am, basemf, limit)
249 if opath:
255 if opath:
250 if debug:
256 if debug:
251 dbg(b'debug.copies: rename of: %s\n' % opath)
257 dbg(b'debug.copies: rename of: %s\n' % opath)
252 cm[f] = opath
258 cm[f] = opath
253 if debug:
259 if debug:
254 dbg(
260 dbg(
255 b'debug.copies: time: %f seconds\n'
261 b'debug.copies: time: %f seconds\n'
256 % (util.timer() - start)
262 % (util.timer() - start)
257 )
263 )
258 return cm
264 return cm
259
265
260
266
261 def _changesetforwardcopies(a, b, match):
267 def _changesetforwardcopies(a, b, match):
262 if a.rev() in (node.nullrev, b.rev()):
268 if a.rev() in (node.nullrev, b.rev()):
263 return {}
269 return {}
264
270
265 repo = a.repo()
271 repo = a.repo()
266 children = {}
272 children = {}
267 cl = repo.changelog
273 cl = repo.changelog
268 missingrevs = cl.findmissingrevs(common=[a.rev()], heads=[b.rev()])
274 missingrevs = cl.findmissingrevs(common=[a.rev()], heads=[b.rev()])
269 for r in missingrevs:
275 for r in missingrevs:
270 for p in cl.parentrevs(r):
276 for p in cl.parentrevs(r):
271 if p == node.nullrev:
277 if p == node.nullrev:
272 continue
278 continue
273 if p not in children:
279 if p not in children:
274 children[p] = [r]
280 children[p] = [r]
275 else:
281 else:
276 children[p].append(r)
282 children[p].append(r)
277
283
278 roots = set(children) - set(missingrevs)
284 roots = set(children) - set(missingrevs)
279 # 'work' contains 3-tuples of a (revision number, parent number, copies).
285 # 'work' contains 3-tuples of a (revision number, parent number, copies).
280 # The parent number is only used for knowing which parent the copies dict
286 # The parent number is only used for knowing which parent the copies dict
281 # came from.
287 # came from.
282 # NOTE: To reduce costly copying the 'copies' dicts, we reuse the same
288 # NOTE: To reduce costly copying the 'copies' dicts, we reuse the same
283 # instance for *one* of the child nodes (the last one). Once an instance
289 # instance for *one* of the child nodes (the last one). Once an instance
284 # has been put on the queue, it is thus no longer safe to modify it.
290 # has been put on the queue, it is thus no longer safe to modify it.
285 # Conversely, it *is* safe to modify an instance popped off the queue.
291 # Conversely, it *is* safe to modify an instance popped off the queue.
286 work = [(r, 1, {}) for r in roots]
292 work = [(r, 1, {}) for r in roots]
287 heapq.heapify(work)
293 heapq.heapify(work)
288 alwaysmatch = match.always()
294 alwaysmatch = match.always()
289 while work:
295 while work:
290 r, i1, copies = heapq.heappop(work)
296 r, i1, copies = heapq.heappop(work)
291 if work and work[0][0] == r:
297 if work and work[0][0] == r:
292 # We are tracing copies from both parents
298 # We are tracing copies from both parents
293 r, i2, copies2 = heapq.heappop(work)
299 r, i2, copies2 = heapq.heappop(work)
294 for dst, src in copies2.items():
300 for dst, src in copies2.items():
295 # Unlike when copies are stored in the filelog, we consider
301 # Unlike when copies are stored in the filelog, we consider
296 # it a copy even if the destination already existed on the
302 # it a copy even if the destination already existed on the
297 # other branch. It's simply too expensive to check if the
303 # other branch. It's simply too expensive to check if the
298 # file existed in the manifest.
304 # file existed in the manifest.
299 if dst not in copies:
305 if dst not in copies:
300 # If it was copied on the p1 side, leave it as copied from
306 # If it was copied on the p1 side, leave it as copied from
301 # that side, even if it was also copied on the p2 side.
307 # that side, even if it was also copied on the p2 side.
302 copies[dst] = copies2[dst]
308 copies[dst] = copies2[dst]
303 if r == b.rev():
309 if r == b.rev():
304 return copies
310 return copies
305 for i, c in enumerate(children[r]):
311 for i, c in enumerate(children[r]):
306 childctx = repo[c]
312 childctx = repo[c]
307 if r == childctx.p1().rev():
313 if r == childctx.p1().rev():
308 parent = 1
314 parent = 1
309 childcopies = childctx.p1copies()
315 childcopies = childctx.p1copies()
310 else:
316 else:
311 assert r == childctx.p2().rev()
317 assert r == childctx.p2().rev()
312 parent = 2
318 parent = 2
313 childcopies = childctx.p2copies()
319 childcopies = childctx.p2copies()
314 if not alwaysmatch:
320 if not alwaysmatch:
315 childcopies = {
321 childcopies = {
316 dst: src for dst, src in childcopies.items() if match(dst)
322 dst: src for dst, src in childcopies.items() if match(dst)
317 }
323 }
318 # Copy the dict only if later iterations will also need it
324 # Copy the dict only if later iterations will also need it
319 if i != len(children[r]) - 1:
325 if i != len(children[r]) - 1:
320 newcopies = copies.copy()
326 newcopies = copies.copy()
321 else:
327 else:
322 newcopies = copies
328 newcopies = copies
323 if childcopies:
329 if childcopies:
324 newcopies = _chain(newcopies, childcopies)
330 newcopies = _chain(newcopies, childcopies)
325 for f in childctx.filesremoved():
331 for f in childctx.filesremoved():
326 if f in newcopies:
332 if f in newcopies:
327 del newcopies[f]
333 del newcopies[f]
328 heapq.heappush(work, (c, parent, newcopies))
334 heapq.heappush(work, (c, parent, newcopies))
329 assert False
335 assert False
330
336
331
337
332 def _forwardcopies(a, b, base=None, match=None):
338 def _forwardcopies(a, b, base=None, match=None):
333 """find {dst@b: src@a} copy mapping where a is an ancestor of b"""
339 """find {dst@b: src@a} copy mapping where a is an ancestor of b"""
334
340
335 if base is None:
341 if base is None:
336 base = a
342 base = a
337 match = a.repo().narrowmatch(match)
343 match = a.repo().narrowmatch(match)
338 # check for working copy
344 # check for working copy
339 if b.rev() is None:
345 if b.rev() is None:
340 cm = _committedforwardcopies(a, b.p1(), base, match)
346 cm = _committedforwardcopies(a, b.p1(), base, match)
341 # combine copies from dirstate if necessary
347 # combine copies from dirstate if necessary
342 copies = _chain(cm, _dirstatecopies(b._repo, match))
348 copies = _chain(cm, _dirstatecopies(b._repo, match))
343 else:
349 else:
344 copies = _committedforwardcopies(a, b, base, match)
350 copies = _committedforwardcopies(a, b, base, match)
345 return copies
351 return copies
346
352
347
353
348 def _backwardrenames(a, b, match):
354 def _backwardrenames(a, b, match):
349 if a._repo.ui.config(b'experimental', b'copytrace') == b'off':
355 if a._repo.ui.config(b'experimental', b'copytrace') == b'off':
350 return {}
356 return {}
351
357
352 # Even though we're not taking copies into account, 1:n rename situations
358 # Even though we're not taking copies into account, 1:n rename situations
353 # can still exist (e.g. hg cp a b; hg mv a c). In those cases we
359 # can still exist (e.g. hg cp a b; hg mv a c). In those cases we
354 # arbitrarily pick one of the renames.
360 # arbitrarily pick one of the renames.
355 # We don't want to pass in "match" here, since that would filter
361 # We don't want to pass in "match" here, since that would filter
356 # the destination by it. Since we're reversing the copies, we want
362 # the destination by it. Since we're reversing the copies, we want
357 # to filter the source instead.
363 # to filter the source instead.
358 f = _forwardcopies(b, a)
364 f = _forwardcopies(b, a)
359 r = {}
365 r = {}
360 for k, v in sorted(pycompat.iteritems(f)):
366 for k, v in sorted(pycompat.iteritems(f)):
361 if match and not match(v):
367 if match and not match(v):
362 continue
368 continue
363 # remove copies
369 # remove copies
364 if v in a:
370 if v in a:
365 continue
371 continue
366 r[v] = k
372 r[v] = k
367 return r
373 return r
368
374
369
375
370 def pathcopies(x, y, match=None):
376 def pathcopies(x, y, match=None):
371 """find {dst@y: src@x} copy mapping for directed compare"""
377 """find {dst@y: src@x} copy mapping for directed compare"""
372 repo = x._repo
378 repo = x._repo
373 debug = repo.ui.debugflag and repo.ui.configbool(b'devel', b'debug.copies')
379 debug = repo.ui.debugflag and repo.ui.configbool(b'devel', b'debug.copies')
374 if debug:
380 if debug:
375 repo.ui.debug(
381 repo.ui.debug(
376 b'debug.copies: searching copies from %s to %s\n' % (x, y)
382 b'debug.copies: searching copies from %s to %s\n' % (x, y)
377 )
383 )
378 if x == y or not x or not y:
384 if x == y or not x or not y:
379 return {}
385 return {}
380 a = y.ancestor(x)
386 a = y.ancestor(x)
381 if a == x:
387 if a == x:
382 if debug:
388 if debug:
383 repo.ui.debug(b'debug.copies: search mode: forward\n')
389 repo.ui.debug(b'debug.copies: search mode: forward\n')
384 if y.rev() is None and x == y.p1():
390 if y.rev() is None and x == y.p1():
385 # short-circuit to avoid issues with merge states
391 # short-circuit to avoid issues with merge states
386 return _dirstatecopies(repo, match)
392 return _dirstatecopies(repo, match)
387 copies = _forwardcopies(x, y, match=match)
393 copies = _forwardcopies(x, y, match=match)
388 elif a == y:
394 elif a == y:
389 if debug:
395 if debug:
390 repo.ui.debug(b'debug.copies: search mode: backward\n')
396 repo.ui.debug(b'debug.copies: search mode: backward\n')
391 copies = _backwardrenames(x, y, match=match)
397 copies = _backwardrenames(x, y, match=match)
392 else:
398 else:
393 if debug:
399 if debug:
394 repo.ui.debug(b'debug.copies: search mode: combined\n')
400 repo.ui.debug(b'debug.copies: search mode: combined\n')
395 base = None
401 base = None
396 if a.rev() != node.nullrev:
402 if a.rev() != node.nullrev:
397 base = x
403 base = x
398 copies = _chain(
404 copies = _chain(
399 _backwardrenames(x, a, match=match),
405 _backwardrenames(x, a, match=match),
400 _forwardcopies(a, y, base, match=match),
406 _forwardcopies(a, y, base, match=match),
401 )
407 )
402 _filter(x, y, copies)
408 _filter(x, y, copies)
403 return copies
409 return copies
404
410
405
411
406 def mergecopies(repo, c1, c2, base):
412 def mergecopies(repo, c1, c2, base):
407 """
413 """
408 Finds moves and copies between context c1 and c2 that are relevant for
414 Finds moves and copies between context c1 and c2 that are relevant for
409 merging. 'base' will be used as the merge base.
415 merging. 'base' will be used as the merge base.
410
416
411 Copytracing is used in commands like rebase, merge, unshelve, etc to merge
417 Copytracing is used in commands like rebase, merge, unshelve, etc to merge
412 files that were moved/ copied in one merge parent and modified in another.
418 files that were moved/ copied in one merge parent and modified in another.
413 For example:
419 For example:
414
420
415 o ---> 4 another commit
421 o ---> 4 another commit
416 |
422 |
417 | o ---> 3 commit that modifies a.txt
423 | o ---> 3 commit that modifies a.txt
418 | /
424 | /
419 o / ---> 2 commit that moves a.txt to b.txt
425 o / ---> 2 commit that moves a.txt to b.txt
420 |/
426 |/
421 o ---> 1 merge base
427 o ---> 1 merge base
422
428
423 If we try to rebase revision 3 on revision 4, since there is no a.txt in
429 If we try to rebase revision 3 on revision 4, since there is no a.txt in
424 revision 4, and if user have copytrace disabled, we prints the following
430 revision 4, and if user have copytrace disabled, we prints the following
425 message:
431 message:
426
432
427 ```other changed <file> which local deleted```
433 ```other changed <file> which local deleted```
428
434
429 Returns five dicts: "copy", "movewithdir", "diverge", "renamedelete" and
435 Returns five dicts: "copy", "movewithdir", "diverge", "renamedelete" and
430 "dirmove".
436 "dirmove".
431
437
432 "copy" is a mapping from destination name -> source name,
438 "copy" is a mapping from destination name -> source name,
433 where source is in c1 and destination is in c2 or vice-versa.
439 where source is in c1 and destination is in c2 or vice-versa.
434
440
435 "movewithdir" is a mapping from source name -> destination name,
441 "movewithdir" is a mapping from source name -> destination name,
436 where the file at source present in one context but not the other
442 where the file at source present in one context but not the other
437 needs to be moved to destination by the merge process, because the
443 needs to be moved to destination by the merge process, because the
438 other context moved the directory it is in.
444 other context moved the directory it is in.
439
445
440 "diverge" is a mapping of source name -> list of destination names
446 "diverge" is a mapping of source name -> list of destination names
441 for divergent renames.
447 for divergent renames.
442
448
443 "renamedelete" is a mapping of source name -> list of destination
449 "renamedelete" is a mapping of source name -> list of destination
444 names for files deleted in c1 that were renamed in c2 or vice-versa.
450 names for files deleted in c1 that were renamed in c2 or vice-versa.
445
451
446 "dirmove" is a mapping of detected source dir -> destination dir renames.
452 "dirmove" is a mapping of detected source dir -> destination dir renames.
447 This is needed for handling changes to new files previously grafted into
453 This is needed for handling changes to new files previously grafted into
448 renamed directories.
454 renamed directories.
449
455
450 This function calls different copytracing algorithms based on config.
456 This function calls different copytracing algorithms based on config.
451 """
457 """
452 # avoid silly behavior for update from empty dir
458 # avoid silly behavior for update from empty dir
453 if not c1 or not c2 or c1 == c2:
459 if not c1 or not c2 or c1 == c2:
454 return {}, {}, {}, {}, {}
460 return {}, {}, {}, {}, {}
455
461
456 narrowmatch = c1.repo().narrowmatch()
462 narrowmatch = c1.repo().narrowmatch()
457
463
458 # avoid silly behavior for parent -> working dir
464 # avoid silly behavior for parent -> working dir
459 if c2.node() is None and c1.node() == repo.dirstate.p1():
465 if c2.node() is None and c1.node() == repo.dirstate.p1():
460 return _dirstatecopies(repo, narrowmatch), {}, {}, {}, {}
466 return _dirstatecopies(repo, narrowmatch), {}, {}, {}, {}
461
467
462 copytracing = repo.ui.config(b'experimental', b'copytrace')
468 copytracing = repo.ui.config(b'experimental', b'copytrace')
463 if stringutil.parsebool(copytracing) is False:
469 if stringutil.parsebool(copytracing) is False:
464 # stringutil.parsebool() returns None when it is unable to parse the
470 # stringutil.parsebool() returns None when it is unable to parse the
465 # value, so we should rely on making sure copytracing is on such cases
471 # value, so we should rely on making sure copytracing is on such cases
466 return {}, {}, {}, {}, {}
472 return {}, {}, {}, {}, {}
467
473
468 if usechangesetcentricalgo(repo):
474 if usechangesetcentricalgo(repo):
469 # The heuristics don't make sense when we need changeset-centric algos
475 # The heuristics don't make sense when we need changeset-centric algos
470 return _fullcopytracing(repo, c1, c2, base)
476 return _fullcopytracing(repo, c1, c2, base)
471
477
472 # Copy trace disabling is explicitly below the node == p1 logic above
478 # Copy trace disabling is explicitly below the node == p1 logic above
473 # because the logic above is required for a simple copy to be kept across a
479 # because the logic above is required for a simple copy to be kept across a
474 # rebase.
480 # rebase.
475 if copytracing == b'heuristics':
481 if copytracing == b'heuristics':
476 # Do full copytracing if only non-public revisions are involved as
482 # Do full copytracing if only non-public revisions are involved as
477 # that will be fast enough and will also cover the copies which could
483 # that will be fast enough and will also cover the copies which could
478 # be missed by heuristics
484 # be missed by heuristics
479 if _isfullcopytraceable(repo, c1, base):
485 if _isfullcopytraceable(repo, c1, base):
480 return _fullcopytracing(repo, c1, c2, base)
486 return _fullcopytracing(repo, c1, c2, base)
481 return _heuristicscopytracing(repo, c1, c2, base)
487 return _heuristicscopytracing(repo, c1, c2, base)
482 else:
488 else:
483 return _fullcopytracing(repo, c1, c2, base)
489 return _fullcopytracing(repo, c1, c2, base)
484
490
485
491
486 def _isfullcopytraceable(repo, c1, base):
492 def _isfullcopytraceable(repo, c1, base):
487 """ Checks that if base, source and destination are all no-public branches,
493 """ Checks that if base, source and destination are all no-public branches,
488 if yes let's use the full copytrace algorithm for increased capabilities
494 if yes let's use the full copytrace algorithm for increased capabilities
489 since it will be fast enough.
495 since it will be fast enough.
490
496
491 `experimental.copytrace.sourcecommitlimit` can be used to set a limit for
497 `experimental.copytrace.sourcecommitlimit` can be used to set a limit for
492 number of changesets from c1 to base such that if number of changesets are
498 number of changesets from c1 to base such that if number of changesets are
493 more than the limit, full copytracing algorithm won't be used.
499 more than the limit, full copytracing algorithm won't be used.
494 """
500 """
495 if c1.rev() is None:
501 if c1.rev() is None:
496 c1 = c1.p1()
502 c1 = c1.p1()
497 if c1.mutable() and base.mutable():
503 if c1.mutable() and base.mutable():
498 sourcecommitlimit = repo.ui.configint(
504 sourcecommitlimit = repo.ui.configint(
499 b'experimental', b'copytrace.sourcecommitlimit'
505 b'experimental', b'copytrace.sourcecommitlimit'
500 )
506 )
501 commits = len(repo.revs(b'%d::%d', base.rev(), c1.rev()))
507 commits = len(repo.revs(b'%d::%d', base.rev(), c1.rev()))
502 return commits < sourcecommitlimit
508 return commits < sourcecommitlimit
503 return False
509 return False
504
510
505
511
506 def _checksinglesidecopies(
512 def _checksinglesidecopies(
507 src, dsts1, m1, m2, mb, c2, base, copy, renamedelete
513 src, dsts1, m1, m2, mb, c2, base, copy, renamedelete
508 ):
514 ):
509 if src not in m2:
515 if src not in m2:
510 # deleted on side 2
516 # deleted on side 2
511 if src not in m1:
517 if src not in m1:
512 # renamed on side 1, deleted on side 2
518 # renamed on side 1, deleted on side 2
513 renamedelete[src] = dsts1
519 renamedelete[src] = dsts1
514 elif m2[src] != mb[src]:
520 elif m2[src] != mb[src]:
515 if not _related(c2[src], base[src]):
521 if not _related(c2[src], base[src]):
516 return
522 return
517 # modified on side 2
523 # modified on side 2
518 for dst in dsts1:
524 for dst in dsts1:
519 if dst not in m2:
525 if dst not in m2:
520 # dst not added on side 2 (handle as regular
526 # dst not added on side 2 (handle as regular
521 # "both created" case in manifestmerge otherwise)
527 # "both created" case in manifestmerge otherwise)
522 copy[dst] = src
528 copy[dst] = src
523
529
524
530
525 def _fullcopytracing(repo, c1, c2, base):
531 def _fullcopytracing(repo, c1, c2, base):
526 """ The full copytracing algorithm which finds all the new files that were
532 """ The full copytracing algorithm which finds all the new files that were
527 added from merge base up to the top commit and for each file it checks if
533 added from merge base up to the top commit and for each file it checks if
528 this file was copied from another file.
534 this file was copied from another file.
529
535
530 This is pretty slow when a lot of changesets are involved but will track all
536 This is pretty slow when a lot of changesets are involved but will track all
531 the copies.
537 the copies.
532 """
538 """
533 m1 = c1.manifest()
539 m1 = c1.manifest()
534 m2 = c2.manifest()
540 m2 = c2.manifest()
535 mb = base.manifest()
541 mb = base.manifest()
536
542
537 copies1 = pathcopies(base, c1)
543 copies1 = pathcopies(base, c1)
538 copies2 = pathcopies(base, c2)
544 copies2 = pathcopies(base, c2)
539
545
540 inversecopies1 = {}
546 inversecopies1 = {}
541 inversecopies2 = {}
547 inversecopies2 = {}
542 for dst, src in copies1.items():
548 for dst, src in copies1.items():
543 inversecopies1.setdefault(src, []).append(dst)
549 inversecopies1.setdefault(src, []).append(dst)
544 for dst, src in copies2.items():
550 for dst, src in copies2.items():
545 inversecopies2.setdefault(src, []).append(dst)
551 inversecopies2.setdefault(src, []).append(dst)
546
552
547 copy = {}
553 copy = {}
548 diverge = {}
554 diverge = {}
549 renamedelete = {}
555 renamedelete = {}
550 allsources = set(inversecopies1) | set(inversecopies2)
556 allsources = set(inversecopies1) | set(inversecopies2)
551 for src in allsources:
557 for src in allsources:
552 dsts1 = inversecopies1.get(src)
558 dsts1 = inversecopies1.get(src)
553 dsts2 = inversecopies2.get(src)
559 dsts2 = inversecopies2.get(src)
554 if dsts1 and dsts2:
560 if dsts1 and dsts2:
555 # copied/renamed on both sides
561 # copied/renamed on both sides
556 if src not in m1 and src not in m2:
562 if src not in m1 and src not in m2:
557 # renamed on both sides
563 # renamed on both sides
558 dsts1 = set(dsts1)
564 dsts1 = set(dsts1)
559 dsts2 = set(dsts2)
565 dsts2 = set(dsts2)
560 # If there's some overlap in the rename destinations, we
566 # If there's some overlap in the rename destinations, we
561 # consider it not divergent. For example, if side 1 copies 'a'
567 # consider it not divergent. For example, if side 1 copies 'a'
562 # to 'b' and 'c' and deletes 'a', and side 2 copies 'a' to 'c'
568 # to 'b' and 'c' and deletes 'a', and side 2 copies 'a' to 'c'
563 # and 'd' and deletes 'a'.
569 # and 'd' and deletes 'a'.
564 if dsts1 & dsts2:
570 if dsts1 & dsts2:
565 for dst in dsts1 & dsts2:
571 for dst in dsts1 & dsts2:
566 copy[dst] = src
572 copy[dst] = src
567 else:
573 else:
568 diverge[src] = sorted(dsts1 | dsts2)
574 diverge[src] = sorted(dsts1 | dsts2)
569 elif src in m1 and src in m2:
575 elif src in m1 and src in m2:
570 # copied on both sides
576 # copied on both sides
571 dsts1 = set(dsts1)
577 dsts1 = set(dsts1)
572 dsts2 = set(dsts2)
578 dsts2 = set(dsts2)
573 for dst in dsts1 & dsts2:
579 for dst in dsts1 & dsts2:
574 copy[dst] = src
580 copy[dst] = src
575 # TODO: Handle cases where it was renamed on one side and copied
581 # TODO: Handle cases where it was renamed on one side and copied
576 # on the other side
582 # on the other side
577 elif dsts1:
583 elif dsts1:
578 # copied/renamed only on side 1
584 # copied/renamed only on side 1
579 _checksinglesidecopies(
585 _checksinglesidecopies(
580 src, dsts1, m1, m2, mb, c2, base, copy, renamedelete
586 src, dsts1, m1, m2, mb, c2, base, copy, renamedelete
581 )
587 )
582 elif dsts2:
588 elif dsts2:
583 # copied/renamed only on side 2
589 # copied/renamed only on side 2
584 _checksinglesidecopies(
590 _checksinglesidecopies(
585 src, dsts2, m2, m1, mb, c1, base, copy, renamedelete
591 src, dsts2, m2, m1, mb, c1, base, copy, renamedelete
586 )
592 )
587
593
588 renamedeleteset = set()
594 renamedeleteset = set()
589 divergeset = set()
595 divergeset = set()
590 for dsts in diverge.values():
596 for dsts in diverge.values():
591 divergeset.update(dsts)
597 divergeset.update(dsts)
592 for dsts in renamedelete.values():
598 for dsts in renamedelete.values():
593 renamedeleteset.update(dsts)
599 renamedeleteset.update(dsts)
594
600
595 # find interesting file sets from manifests
601 # find interesting file sets from manifests
596 addedinm1 = m1.filesnotin(mb, repo.narrowmatch())
602 addedinm1 = m1.filesnotin(mb, repo.narrowmatch())
597 addedinm2 = m2.filesnotin(mb, repo.narrowmatch())
603 addedinm2 = m2.filesnotin(mb, repo.narrowmatch())
598 u1 = sorted(addedinm1 - addedinm2)
604 u1 = sorted(addedinm1 - addedinm2)
599 u2 = sorted(addedinm2 - addedinm1)
605 u2 = sorted(addedinm2 - addedinm1)
600
606
601 header = b" unmatched files in %s"
607 header = b" unmatched files in %s"
602 if u1:
608 if u1:
603 repo.ui.debug(b"%s:\n %s\n" % (header % b'local', b"\n ".join(u1)))
609 repo.ui.debug(b"%s:\n %s\n" % (header % b'local', b"\n ".join(u1)))
604 if u2:
610 if u2:
605 repo.ui.debug(b"%s:\n %s\n" % (header % b'other', b"\n ".join(u2)))
611 repo.ui.debug(b"%s:\n %s\n" % (header % b'other', b"\n ".join(u2)))
606
612
607 fullcopy = copies1.copy()
613 fullcopy = copies1.copy()
608 fullcopy.update(copies2)
614 fullcopy.update(copies2)
609 if not fullcopy:
615 if not fullcopy:
610 return copy, {}, diverge, renamedelete, {}
616 return copy, {}, diverge, renamedelete, {}
611
617
612 if repo.ui.debugflag:
618 if repo.ui.debugflag:
613 repo.ui.debug(
619 repo.ui.debug(
614 b" all copies found (* = to merge, ! = divergent, "
620 b" all copies found (* = to merge, ! = divergent, "
615 b"% = renamed and deleted):\n"
621 b"% = renamed and deleted):\n"
616 )
622 )
617 for f in sorted(fullcopy):
623 for f in sorted(fullcopy):
618 note = b""
624 note = b""
619 if f in copy:
625 if f in copy:
620 note += b"*"
626 note += b"*"
621 if f in divergeset:
627 if f in divergeset:
622 note += b"!"
628 note += b"!"
623 if f in renamedeleteset:
629 if f in renamedeleteset:
624 note += b"%"
630 note += b"%"
625 repo.ui.debug(
631 repo.ui.debug(
626 b" src: '%s' -> dst: '%s' %s\n" % (fullcopy[f], f, note)
632 b" src: '%s' -> dst: '%s' %s\n" % (fullcopy[f], f, note)
627 )
633 )
628 del divergeset
634 del divergeset
629
635
630 repo.ui.debug(b" checking for directory renames\n")
636 repo.ui.debug(b" checking for directory renames\n")
631
637
632 # generate a directory move map
638 # generate a directory move map
633 d1, d2 = c1.dirs(), c2.dirs()
639 d1, d2 = c1.dirs(), c2.dirs()
634 invalid = set()
640 invalid = set()
635 dirmove = {}
641 dirmove = {}
636
642
637 # examine each file copy for a potential directory move, which is
643 # examine each file copy for a potential directory move, which is
638 # when all the files in a directory are moved to a new directory
644 # when all the files in a directory are moved to a new directory
639 for dst, src in pycompat.iteritems(fullcopy):
645 for dst, src in pycompat.iteritems(fullcopy):
640 dsrc, ddst = pathutil.dirname(src), pathutil.dirname(dst)
646 dsrc, ddst = pathutil.dirname(src), pathutil.dirname(dst)
641 if dsrc in invalid:
647 if dsrc in invalid:
642 # already seen to be uninteresting
648 # already seen to be uninteresting
643 continue
649 continue
644 elif dsrc in d1 and ddst in d1:
650 elif dsrc in d1 and ddst in d1:
645 # directory wasn't entirely moved locally
651 # directory wasn't entirely moved locally
646 invalid.add(dsrc)
652 invalid.add(dsrc)
647 elif dsrc in d2 and ddst in d2:
653 elif dsrc in d2 and ddst in d2:
648 # directory wasn't entirely moved remotely
654 # directory wasn't entirely moved remotely
649 invalid.add(dsrc)
655 invalid.add(dsrc)
650 elif dsrc in dirmove and dirmove[dsrc] != ddst:
656 elif dsrc in dirmove and dirmove[dsrc] != ddst:
651 # files from the same directory moved to two different places
657 # files from the same directory moved to two different places
652 invalid.add(dsrc)
658 invalid.add(dsrc)
653 else:
659 else:
654 # looks good so far
660 # looks good so far
655 dirmove[dsrc] = ddst
661 dirmove[dsrc] = ddst
656
662
657 for i in invalid:
663 for i in invalid:
658 if i in dirmove:
664 if i in dirmove:
659 del dirmove[i]
665 del dirmove[i]
660 del d1, d2, invalid
666 del d1, d2, invalid
661
667
662 if not dirmove:
668 if not dirmove:
663 return copy, {}, diverge, renamedelete, {}
669 return copy, {}, diverge, renamedelete, {}
664
670
665 dirmove = {k + b"/": v + b"/" for k, v in pycompat.iteritems(dirmove)}
671 dirmove = {k + b"/": v + b"/" for k, v in pycompat.iteritems(dirmove)}
666
672
667 for d in dirmove:
673 for d in dirmove:
668 repo.ui.debug(
674 repo.ui.debug(
669 b" discovered dir src: '%s' -> dst: '%s'\n" % (d, dirmove[d])
675 b" discovered dir src: '%s' -> dst: '%s'\n" % (d, dirmove[d])
670 )
676 )
671
677
672 movewithdir = {}
678 movewithdir = {}
673 # check unaccounted nonoverlapping files against directory moves
679 # check unaccounted nonoverlapping files against directory moves
674 for f in u1 + u2:
680 for f in u1 + u2:
675 if f not in fullcopy:
681 if f not in fullcopy:
676 for d in dirmove:
682 for d in dirmove:
677 if f.startswith(d):
683 if f.startswith(d):
678 # new file added in a directory that was moved, move it
684 # new file added in a directory that was moved, move it
679 df = dirmove[d] + f[len(d) :]
685 df = dirmove[d] + f[len(d) :]
680 if df not in copy:
686 if df not in copy:
681 movewithdir[f] = df
687 movewithdir[f] = df
682 repo.ui.debug(
688 repo.ui.debug(
683 b" pending file src: '%s' -> dst: '%s'\n"
689 b" pending file src: '%s' -> dst: '%s'\n"
684 % (f, df)
690 % (f, df)
685 )
691 )
686 break
692 break
687
693
688 return copy, movewithdir, diverge, renamedelete, dirmove
694 return copy, movewithdir, diverge, renamedelete, dirmove
689
695
690
696
691 def _heuristicscopytracing(repo, c1, c2, base):
697 def _heuristicscopytracing(repo, c1, c2, base):
692 """ Fast copytracing using filename heuristics
698 """ Fast copytracing using filename heuristics
693
699
694 Assumes that moves or renames are of following two types:
700 Assumes that moves or renames are of following two types:
695
701
696 1) Inside a directory only (same directory name but different filenames)
702 1) Inside a directory only (same directory name but different filenames)
697 2) Move from one directory to another
703 2) Move from one directory to another
698 (same filenames but different directory names)
704 (same filenames but different directory names)
699
705
700 Works only when there are no merge commits in the "source branch".
706 Works only when there are no merge commits in the "source branch".
701 Source branch is commits from base up to c2 not including base.
707 Source branch is commits from base up to c2 not including base.
702
708
703 If merge is involved it fallbacks to _fullcopytracing().
709 If merge is involved it fallbacks to _fullcopytracing().
704
710
705 Can be used by setting the following config:
711 Can be used by setting the following config:
706
712
707 [experimental]
713 [experimental]
708 copytrace = heuristics
714 copytrace = heuristics
709
715
710 In some cases the copy/move candidates found by heuristics can be very large
716 In some cases the copy/move candidates found by heuristics can be very large
711 in number and that will make the algorithm slow. The number of possible
717 in number and that will make the algorithm slow. The number of possible
712 candidates to check can be limited by using the config
718 candidates to check can be limited by using the config
713 `experimental.copytrace.movecandidateslimit` which defaults to 100.
719 `experimental.copytrace.movecandidateslimit` which defaults to 100.
714 """
720 """
715
721
716 if c1.rev() is None:
722 if c1.rev() is None:
717 c1 = c1.p1()
723 c1 = c1.p1()
718 if c2.rev() is None:
724 if c2.rev() is None:
719 c2 = c2.p1()
725 c2 = c2.p1()
720
726
721 copies = {}
727 copies = {}
722
728
723 changedfiles = set()
729 changedfiles = set()
724 m1 = c1.manifest()
730 m1 = c1.manifest()
725 if not repo.revs(b'%d::%d', base.rev(), c2.rev()):
731 if not repo.revs(b'%d::%d', base.rev(), c2.rev()):
726 # If base is not in c2 branch, we switch to fullcopytracing
732 # If base is not in c2 branch, we switch to fullcopytracing
727 repo.ui.debug(
733 repo.ui.debug(
728 b"switching to full copytracing as base is not "
734 b"switching to full copytracing as base is not "
729 b"an ancestor of c2\n"
735 b"an ancestor of c2\n"
730 )
736 )
731 return _fullcopytracing(repo, c1, c2, base)
737 return _fullcopytracing(repo, c1, c2, base)
732
738
733 ctx = c2
739 ctx = c2
734 while ctx != base:
740 while ctx != base:
735 if len(ctx.parents()) == 2:
741 if len(ctx.parents()) == 2:
736 # To keep things simple let's not handle merges
742 # To keep things simple let's not handle merges
737 repo.ui.debug(b"switching to full copytracing because of merges\n")
743 repo.ui.debug(b"switching to full copytracing because of merges\n")
738 return _fullcopytracing(repo, c1, c2, base)
744 return _fullcopytracing(repo, c1, c2, base)
739 changedfiles.update(ctx.files())
745 changedfiles.update(ctx.files())
740 ctx = ctx.p1()
746 ctx = ctx.p1()
741
747
742 cp = _forwardcopies(base, c2)
748 cp = _forwardcopies(base, c2)
743 for dst, src in pycompat.iteritems(cp):
749 for dst, src in pycompat.iteritems(cp):
744 if src in m1:
750 if src in m1:
745 copies[dst] = src
751 copies[dst] = src
746
752
747 # file is missing if it isn't present in the destination, but is present in
753 # file is missing if it isn't present in the destination, but is present in
748 # the base and present in the source.
754 # the base and present in the source.
749 # Presence in the base is important to exclude added files, presence in the
755 # Presence in the base is important to exclude added files, presence in the
750 # source is important to exclude removed files.
756 # source is important to exclude removed files.
751 filt = lambda f: f not in m1 and f in base and f in c2
757 filt = lambda f: f not in m1 and f in base and f in c2
752 missingfiles = [f for f in changedfiles if filt(f)]
758 missingfiles = [f for f in changedfiles if filt(f)]
753
759
754 if missingfiles:
760 if missingfiles:
755 basenametofilename = collections.defaultdict(list)
761 basenametofilename = collections.defaultdict(list)
756 dirnametofilename = collections.defaultdict(list)
762 dirnametofilename = collections.defaultdict(list)
757
763
758 for f in m1.filesnotin(base.manifest()):
764 for f in m1.filesnotin(base.manifest()):
759 basename = os.path.basename(f)
765 basename = os.path.basename(f)
760 dirname = os.path.dirname(f)
766 dirname = os.path.dirname(f)
761 basenametofilename[basename].append(f)
767 basenametofilename[basename].append(f)
762 dirnametofilename[dirname].append(f)
768 dirnametofilename[dirname].append(f)
763
769
764 for f in missingfiles:
770 for f in missingfiles:
765 basename = os.path.basename(f)
771 basename = os.path.basename(f)
766 dirname = os.path.dirname(f)
772 dirname = os.path.dirname(f)
767 samebasename = basenametofilename[basename]
773 samebasename = basenametofilename[basename]
768 samedirname = dirnametofilename[dirname]
774 samedirname = dirnametofilename[dirname]
769 movecandidates = samebasename + samedirname
775 movecandidates = samebasename + samedirname
770 # f is guaranteed to be present in c2, that's why
776 # f is guaranteed to be present in c2, that's why
771 # c2.filectx(f) won't fail
777 # c2.filectx(f) won't fail
772 f2 = c2.filectx(f)
778 f2 = c2.filectx(f)
773 # we can have a lot of candidates which can slow down the heuristics
779 # we can have a lot of candidates which can slow down the heuristics
774 # config value to limit the number of candidates moves to check
780 # config value to limit the number of candidates moves to check
775 maxcandidates = repo.ui.configint(
781 maxcandidates = repo.ui.configint(
776 b'experimental', b'copytrace.movecandidateslimit'
782 b'experimental', b'copytrace.movecandidateslimit'
777 )
783 )
778
784
779 if len(movecandidates) > maxcandidates:
785 if len(movecandidates) > maxcandidates:
780 repo.ui.status(
786 repo.ui.status(
781 _(
787 _(
782 b"skipping copytracing for '%s', more "
788 b"skipping copytracing for '%s', more "
783 b"candidates than the limit: %d\n"
789 b"candidates than the limit: %d\n"
784 )
790 )
785 % (f, len(movecandidates))
791 % (f, len(movecandidates))
786 )
792 )
787 continue
793 continue
788
794
789 for candidate in movecandidates:
795 for candidate in movecandidates:
790 f1 = c1.filectx(candidate)
796 f1 = c1.filectx(candidate)
791 if _related(f1, f2):
797 if _related(f1, f2):
792 # if there are a few related copies then we'll merge
798 # if there are a few related copies then we'll merge
793 # changes into all of them. This matches the behaviour
799 # changes into all of them. This matches the behaviour
794 # of upstream copytracing
800 # of upstream copytracing
795 copies[candidate] = f
801 copies[candidate] = f
796
802
797 return copies, {}, {}, {}, {}
803 return copies, {}, {}, {}, {}
798
804
799
805
800 def _related(f1, f2):
806 def _related(f1, f2):
801 """return True if f1 and f2 filectx have a common ancestor
807 """return True if f1 and f2 filectx have a common ancestor
802
808
803 Walk back to common ancestor to see if the two files originate
809 Walk back to common ancestor to see if the two files originate
804 from the same file. Since workingfilectx's rev() is None it messes
810 from the same file. Since workingfilectx's rev() is None it messes
805 up the integer comparison logic, hence the pre-step check for
811 up the integer comparison logic, hence the pre-step check for
806 None (f1 and f2 can only be workingfilectx's initially).
812 None (f1 and f2 can only be workingfilectx's initially).
807 """
813 """
808
814
809 if f1 == f2:
815 if f1 == f2:
810 return True # a match
816 return True # a match
811
817
812 g1, g2 = f1.ancestors(), f2.ancestors()
818 g1, g2 = f1.ancestors(), f2.ancestors()
813 try:
819 try:
814 f1r, f2r = f1.linkrev(), f2.linkrev()
820 f1r, f2r = f1.linkrev(), f2.linkrev()
815
821
816 if f1r is None:
822 if f1r is None:
817 f1 = next(g1)
823 f1 = next(g1)
818 if f2r is None:
824 if f2r is None:
819 f2 = next(g2)
825 f2 = next(g2)
820
826
821 while True:
827 while True:
822 f1r, f2r = f1.linkrev(), f2.linkrev()
828 f1r, f2r = f1.linkrev(), f2.linkrev()
823 if f1r > f2r:
829 if f1r > f2r:
824 f1 = next(g1)
830 f1 = next(g1)
825 elif f2r > f1r:
831 elif f2r > f1r:
826 f2 = next(g2)
832 f2 = next(g2)
827 else: # f1 and f2 point to files in the same linkrev
833 else: # f1 and f2 point to files in the same linkrev
828 return f1 == f2 # true if they point to the same file
834 return f1 == f2 # true if they point to the same file
829 except StopIteration:
835 except StopIteration:
830 return False
836 return False
831
837
832
838
833 def duplicatecopies(repo, wctx, rev, fromrev, skiprev=None):
839 def duplicatecopies(repo, wctx, rev, fromrev, skiprev=None):
834 """reproduce copies from fromrev to rev in the dirstate
840 """reproduce copies from fromrev to rev in the dirstate
835
841
836 If skiprev is specified, it's a revision that should be used to
842 If skiprev is specified, it's a revision that should be used to
837 filter copy records. Any copies that occur between fromrev and
843 filter copy records. Any copies that occur between fromrev and
838 skiprev will not be duplicated, even if they appear in the set of
844 skiprev will not be duplicated, even if they appear in the set of
839 copies between fromrev and rev.
845 copies between fromrev and rev.
840 """
846 """
841 exclude = {}
847 exclude = {}
842 ctraceconfig = repo.ui.config(b'experimental', b'copytrace')
848 ctraceconfig = repo.ui.config(b'experimental', b'copytrace')
843 bctrace = stringutil.parsebool(ctraceconfig)
849 bctrace = stringutil.parsebool(ctraceconfig)
844 if skiprev is not None and (
850 if skiprev is not None and (
845 ctraceconfig == b'heuristics' or bctrace or bctrace is None
851 ctraceconfig == b'heuristics' or bctrace or bctrace is None
846 ):
852 ):
847 # copytrace='off' skips this line, but not the entire function because
853 # copytrace='off' skips this line, but not the entire function because
848 # the line below is O(size of the repo) during a rebase, while the rest
854 # the line below is O(size of the repo) during a rebase, while the rest
849 # of the function is much faster (and is required for carrying copy
855 # of the function is much faster (and is required for carrying copy
850 # metadata across the rebase anyway).
856 # metadata across the rebase anyway).
851 exclude = pathcopies(repo[fromrev], repo[skiprev])
857 exclude = pathcopies(repo[fromrev], repo[skiprev])
852 for dst, src in pycompat.iteritems(pathcopies(repo[fromrev], repo[rev])):
858 for dst, src in pycompat.iteritems(pathcopies(repo[fromrev], repo[rev])):
853 if dst in exclude:
859 if dst in exclude:
854 continue
860 continue
855 if dst in wctx:
861 if dst in wctx:
856 wctx[dst].markcopied(src)
862 wctx[dst].markcopied(src)
857
863
858
864
859 def computechangesetfilesadded(ctx):
865 def computechangesetfilesadded(ctx):
860 """return the list of files added in a changeset
866 """return the list of files added in a changeset
861 """
867 """
862 added = []
868 added = []
863 for f in ctx.files():
869 for f in ctx.files():
864 if not any(f in p for p in ctx.parents()):
870 if not any(f in p for p in ctx.parents()):
865 added.append(f)
871 added.append(f)
866 return added
872 return added
867
873
868
874
869 def computechangesetfilesremoved(ctx):
875 def computechangesetfilesremoved(ctx):
870 """return the list of files removed in a changeset
876 """return the list of files removed in a changeset
871 """
877 """
872 removed = []
878 removed = []
873 for f in ctx.files():
879 for f in ctx.files():
874 if f not in ctx:
880 if f not in ctx:
875 removed.append(f)
881 removed.append(f)
876 return removed
882 return removed
877
883
878
884
879 def computechangesetcopies(ctx):
885 def computechangesetcopies(ctx):
880 """return the copies data for a changeset
886 """return the copies data for a changeset
881
887
882 The copies data are returned as a pair of dictionnary (p1copies, p2copies).
888 The copies data are returned as a pair of dictionnary (p1copies, p2copies).
883
889
884 Each dictionnary are in the form: `{newname: oldname}`
890 Each dictionnary are in the form: `{newname: oldname}`
885 """
891 """
886 p1copies = {}
892 p1copies = {}
887 p2copies = {}
893 p2copies = {}
888 p1 = ctx.p1()
894 p1 = ctx.p1()
889 p2 = ctx.p2()
895 p2 = ctx.p2()
890 narrowmatch = ctx._repo.narrowmatch()
896 narrowmatch = ctx._repo.narrowmatch()
891 for dst in ctx.files():
897 for dst in ctx.files():
892 if not narrowmatch(dst) or dst not in ctx:
898 if not narrowmatch(dst) or dst not in ctx:
893 continue
899 continue
894 copied = ctx[dst].renamed()
900 copied = ctx[dst].renamed()
895 if not copied:
901 if not copied:
896 continue
902 continue
897 src, srcnode = copied
903 src, srcnode = copied
898 if src in p1 and p1[src].filenode() == srcnode:
904 if src in p1 and p1[src].filenode() == srcnode:
899 p1copies[dst] = src
905 p1copies[dst] = src
900 elif src in p2 and p2[src].filenode() == srcnode:
906 elif src in p2 and p2[src].filenode() == srcnode:
901 p2copies[dst] = src
907 p2copies[dst] = src
902 return p1copies, p2copies
908 return p1copies, p2copies
903
909
904
910
905 def encodecopies(files, copies):
911 def encodecopies(files, copies):
906 items = []
912 items = []
907 for i, dst in enumerate(files):
913 for i, dst in enumerate(files):
908 if dst in copies:
914 if dst in copies:
909 items.append(b'%d\0%s' % (i, copies[dst]))
915 items.append(b'%d\0%s' % (i, copies[dst]))
910 if len(items) != len(copies):
916 if len(items) != len(copies):
911 raise error.ProgrammingError(
917 raise error.ProgrammingError(
912 b'some copy targets missing from file list'
918 b'some copy targets missing from file list'
913 )
919 )
914 return b"\n".join(items)
920 return b"\n".join(items)
915
921
916
922
917 def decodecopies(files, data):
923 def decodecopies(files, data):
918 try:
924 try:
919 copies = {}
925 copies = {}
920 if not data:
926 if not data:
921 return copies
927 return copies
922 for l in data.split(b'\n'):
928 for l in data.split(b'\n'):
923 strindex, src = l.split(b'\0')
929 strindex, src = l.split(b'\0')
924 i = int(strindex)
930 i = int(strindex)
925 dst = files[i]
931 dst = files[i]
926 copies[dst] = src
932 copies[dst] = src
927 return copies
933 return copies
928 except (ValueError, IndexError):
934 except (ValueError, IndexError):
929 # Perhaps someone had chosen the same key name (e.g. "p1copies") and
935 # Perhaps someone had chosen the same key name (e.g. "p1copies") and
930 # used different syntax for the value.
936 # used different syntax for the value.
931 return None
937 return None
932
938
933
939
934 def encodefileindices(files, subset):
940 def encodefileindices(files, subset):
935 subset = set(subset)
941 subset = set(subset)
936 indices = []
942 indices = []
937 for i, f in enumerate(files):
943 for i, f in enumerate(files):
938 if f in subset:
944 if f in subset:
939 indices.append(b'%d' % i)
945 indices.append(b'%d' % i)
940 return b'\n'.join(indices)
946 return b'\n'.join(indices)
941
947
942
948
943 def decodefileindices(files, data):
949 def decodefileindices(files, data):
944 try:
950 try:
945 subset = []
951 subset = []
946 if not data:
952 if not data:
947 return subset
953 return subset
948 for strindex in data.split(b'\n'):
954 for strindex in data.split(b'\n'):
949 i = int(strindex)
955 i = int(strindex)
950 if i < 0 or i >= len(files):
956 if i < 0 or i >= len(files):
951 return None
957 return None
952 subset.append(files[i])
958 subset.append(files[i])
953 return subset
959 return subset
954 except (ValueError, IndexError):
960 except (ValueError, IndexError):
955 # Perhaps someone had chosen the same key name (e.g. "added") and
961 # Perhaps someone had chosen the same key name (e.g. "added") and
956 # used different syntax for the value.
962 # used different syntax for the value.
957 return None
963 return None
964
965
966 def _getsidedata(srcrepo, rev):
967 ctx = srcrepo[rev]
968 filescopies = computechangesetcopies(ctx)
969 filesadded = computechangesetfilesadded(ctx)
970 filesremoved = computechangesetfilesremoved(ctx)
971 sidedata = {}
972 if any([filescopies, filesadded, filesremoved]):
973 sortedfiles = sorted(ctx.files())
974 p1copies, p2copies = filescopies
975 p1copies = encodecopies(sortedfiles, p1copies)
976 p2copies = encodecopies(sortedfiles, p2copies)
977 filesadded = encodefileindices(sortedfiles, filesadded)
978 filesremoved = encodefileindices(sortedfiles, filesremoved)
979 sidedata[sidedatamod.SD_P1COPIES] = p1copies
980 sidedata[sidedatamod.SD_P2COPIES] = p2copies
981 sidedata[sidedatamod.SD_FILESADDED] = filesadded
982 sidedata[sidedatamod.SD_FILESREMOVED] = filesremoved
983 return sidedata
984
985
986 def getsidedataadder(srcrepo, destrepo):
987 def sidedatacompanion(revlog, rev):
988 sidedata = {}
989 if util.safehasattr(revlog, 'filteredrevs'): # this is a changelog
990 sidedata = _getsidedata(srcrepo, rev)
991 return False, (), sidedata
992
993 return sidedatacompanion
994
995
996 def getsidedataremover(srcrepo, destrepo):
997 def sidedatacompanion(revlog, rev):
998 f = ()
999 if util.safehasattr(revlog, 'filteredrevs'): # this is a changelog
1000 if revlog.flags(rev) & REVIDX_SIDEDATA:
1001 f = (
1002 sidedatamod.SD_P1COPIES,
1003 sidedatamod.SD_P2COPIES,
1004 sidedatamod.SD_FILESADDED,
1005 sidedatamod.SD_FILESREMOVED,
1006 )
1007 return False, f, {}
1008
1009 return sidedatacompanion
@@ -1,1379 +1,1387 b''
1 # upgrade.py - functions for in place upgrade of Mercurial repository
1 # upgrade.py - functions for in place upgrade of Mercurial repository
2 #
2 #
3 # Copyright (c) 2016-present, Gregory Szorc
3 # Copyright (c) 2016-present, Gregory Szorc
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 stat
10 import stat
11
11
12 from .i18n import _
12 from .i18n import _
13 from .pycompat import getattr
13 from .pycompat import getattr
14 from . import (
14 from . import (
15 changelog,
15 changelog,
16 copies,
16 error,
17 error,
17 filelog,
18 filelog,
18 hg,
19 hg,
19 localrepo,
20 localrepo,
20 manifest,
21 manifest,
21 pycompat,
22 pycompat,
22 revlog,
23 revlog,
23 scmutil,
24 scmutil,
24 util,
25 util,
25 vfs as vfsmod,
26 vfs as vfsmod,
26 )
27 )
27
28
28 from .utils import compression
29 from .utils import compression
29
30
30 # list of requirements that request a clone of all revlog if added/removed
31 # list of requirements that request a clone of all revlog if added/removed
31 RECLONES_REQUIREMENTS = {
32 RECLONES_REQUIREMENTS = {
32 b'generaldelta',
33 b'generaldelta',
33 localrepo.SPARSEREVLOG_REQUIREMENT,
34 localrepo.SPARSEREVLOG_REQUIREMENT,
34 localrepo.SIDEDATA_REQUIREMENT,
35 }
35 }
36
36
37
37
38 def requiredsourcerequirements(repo):
38 def requiredsourcerequirements(repo):
39 """Obtain requirements required to be present to upgrade a repo.
39 """Obtain requirements required to be present to upgrade a repo.
40
40
41 An upgrade will not be allowed if the repository doesn't have the
41 An upgrade will not be allowed if the repository doesn't have the
42 requirements returned by this function.
42 requirements returned by this function.
43 """
43 """
44 return {
44 return {
45 # Introduced in Mercurial 0.9.2.
45 # Introduced in Mercurial 0.9.2.
46 b'revlogv1',
46 b'revlogv1',
47 # Introduced in Mercurial 0.9.2.
47 # Introduced in Mercurial 0.9.2.
48 b'store',
48 b'store',
49 }
49 }
50
50
51
51
52 def blocksourcerequirements(repo):
52 def blocksourcerequirements(repo):
53 """Obtain requirements that will prevent an upgrade from occurring.
53 """Obtain requirements that will prevent an upgrade from occurring.
54
54
55 An upgrade cannot be performed if the source repository contains a
55 An upgrade cannot be performed if the source repository contains a
56 requirements in the returned set.
56 requirements in the returned set.
57 """
57 """
58 return {
58 return {
59 # The upgrade code does not yet support these experimental features.
59 # The upgrade code does not yet support these experimental features.
60 # This is an artificial limitation.
60 # This is an artificial limitation.
61 b'treemanifest',
61 b'treemanifest',
62 # This was a precursor to generaldelta and was never enabled by default.
62 # This was a precursor to generaldelta and was never enabled by default.
63 # It should (hopefully) not exist in the wild.
63 # It should (hopefully) not exist in the wild.
64 b'parentdelta',
64 b'parentdelta',
65 # Upgrade should operate on the actual store, not the shared link.
65 # Upgrade should operate on the actual store, not the shared link.
66 b'shared',
66 b'shared',
67 }
67 }
68
68
69
69
70 def supportremovedrequirements(repo):
70 def supportremovedrequirements(repo):
71 """Obtain requirements that can be removed during an upgrade.
71 """Obtain requirements that can be removed during an upgrade.
72
72
73 If an upgrade were to create a repository that dropped a requirement,
73 If an upgrade were to create a repository that dropped a requirement,
74 the dropped requirement must appear in the returned set for the upgrade
74 the dropped requirement must appear in the returned set for the upgrade
75 to be allowed.
75 to be allowed.
76 """
76 """
77 supported = {
77 supported = {
78 localrepo.SPARSEREVLOG_REQUIREMENT,
78 localrepo.SPARSEREVLOG_REQUIREMENT,
79 localrepo.SIDEDATA_REQUIREMENT,
79 localrepo.SIDEDATA_REQUIREMENT,
80 localrepo.COPIESSDC_REQUIREMENT,
80 }
81 }
81 for name in compression.compengines:
82 for name in compression.compengines:
82 engine = compression.compengines[name]
83 engine = compression.compengines[name]
83 if engine.available() and engine.revlogheader():
84 if engine.available() and engine.revlogheader():
84 supported.add(b'exp-compression-%s' % name)
85 supported.add(b'exp-compression-%s' % name)
85 if engine.name() == b'zstd':
86 if engine.name() == b'zstd':
86 supported.add(b'revlog-compression-zstd')
87 supported.add(b'revlog-compression-zstd')
87 return supported
88 return supported
88
89
89
90
90 def supporteddestrequirements(repo):
91 def supporteddestrequirements(repo):
91 """Obtain requirements that upgrade supports in the destination.
92 """Obtain requirements that upgrade supports in the destination.
92
93
93 If the result of the upgrade would create requirements not in this set,
94 If the result of the upgrade would create requirements not in this set,
94 the upgrade is disallowed.
95 the upgrade is disallowed.
95
96
96 Extensions should monkeypatch this to add their custom requirements.
97 Extensions should monkeypatch this to add their custom requirements.
97 """
98 """
98 supported = {
99 supported = {
99 b'dotencode',
100 b'dotencode',
100 b'fncache',
101 b'fncache',
101 b'generaldelta',
102 b'generaldelta',
102 b'revlogv1',
103 b'revlogv1',
103 b'store',
104 b'store',
104 localrepo.SPARSEREVLOG_REQUIREMENT,
105 localrepo.SPARSEREVLOG_REQUIREMENT,
105 localrepo.SIDEDATA_REQUIREMENT,
106 localrepo.SIDEDATA_REQUIREMENT,
107 localrepo.COPIESSDC_REQUIREMENT,
106 }
108 }
107 for name in compression.compengines:
109 for name in compression.compengines:
108 engine = compression.compengines[name]
110 engine = compression.compengines[name]
109 if engine.available() and engine.revlogheader():
111 if engine.available() and engine.revlogheader():
110 supported.add(b'exp-compression-%s' % name)
112 supported.add(b'exp-compression-%s' % name)
111 if engine.name() == b'zstd':
113 if engine.name() == b'zstd':
112 supported.add(b'revlog-compression-zstd')
114 supported.add(b'revlog-compression-zstd')
113 return supported
115 return supported
114
116
115
117
116 def allowednewrequirements(repo):
118 def allowednewrequirements(repo):
117 """Obtain requirements that can be added to a repository during upgrade.
119 """Obtain requirements that can be added to a repository during upgrade.
118
120
119 This is used to disallow proposed requirements from being added when
121 This is used to disallow proposed requirements from being added when
120 they weren't present before.
122 they weren't present before.
121
123
122 We use a list of allowed requirement additions instead of a list of known
124 We use a list of allowed requirement additions instead of a list of known
123 bad additions because the whitelist approach is safer and will prevent
125 bad additions because the whitelist approach is safer and will prevent
124 future, unknown requirements from accidentally being added.
126 future, unknown requirements from accidentally being added.
125 """
127 """
126 supported = {
128 supported = {
127 b'dotencode',
129 b'dotencode',
128 b'fncache',
130 b'fncache',
129 b'generaldelta',
131 b'generaldelta',
130 localrepo.SPARSEREVLOG_REQUIREMENT,
132 localrepo.SPARSEREVLOG_REQUIREMENT,
131 localrepo.SIDEDATA_REQUIREMENT,
133 localrepo.SIDEDATA_REQUIREMENT,
134 localrepo.COPIESSDC_REQUIREMENT,
132 }
135 }
133 for name in compression.compengines:
136 for name in compression.compengines:
134 engine = compression.compengines[name]
137 engine = compression.compengines[name]
135 if engine.available() and engine.revlogheader():
138 if engine.available() and engine.revlogheader():
136 supported.add(b'exp-compression-%s' % name)
139 supported.add(b'exp-compression-%s' % name)
137 if engine.name() == b'zstd':
140 if engine.name() == b'zstd':
138 supported.add(b'revlog-compression-zstd')
141 supported.add(b'revlog-compression-zstd')
139 return supported
142 return supported
140
143
141
144
142 def preservedrequirements(repo):
145 def preservedrequirements(repo):
143 return set()
146 return set()
144
147
145
148
146 deficiency = b'deficiency'
149 deficiency = b'deficiency'
147 optimisation = b'optimization'
150 optimisation = b'optimization'
148
151
149
152
150 class improvement(object):
153 class improvement(object):
151 """Represents an improvement that can be made as part of an upgrade.
154 """Represents an improvement that can be made as part of an upgrade.
152
155
153 The following attributes are defined on each instance:
156 The following attributes are defined on each instance:
154
157
155 name
158 name
156 Machine-readable string uniquely identifying this improvement. It
159 Machine-readable string uniquely identifying this improvement. It
157 will be mapped to an action later in the upgrade process.
160 will be mapped to an action later in the upgrade process.
158
161
159 type
162 type
160 Either ``deficiency`` or ``optimisation``. A deficiency is an obvious
163 Either ``deficiency`` or ``optimisation``. A deficiency is an obvious
161 problem. An optimization is an action (sometimes optional) that
164 problem. An optimization is an action (sometimes optional) that
162 can be taken to further improve the state of the repository.
165 can be taken to further improve the state of the repository.
163
166
164 description
167 description
165 Message intended for humans explaining the improvement in more detail,
168 Message intended for humans explaining the improvement in more detail,
166 including the implications of it. For ``deficiency`` types, should be
169 including the implications of it. For ``deficiency`` types, should be
167 worded in the present tense. For ``optimisation`` types, should be
170 worded in the present tense. For ``optimisation`` types, should be
168 worded in the future tense.
171 worded in the future tense.
169
172
170 upgrademessage
173 upgrademessage
171 Message intended for humans explaining what an upgrade addressing this
174 Message intended for humans explaining what an upgrade addressing this
172 issue will do. Should be worded in the future tense.
175 issue will do. Should be worded in the future tense.
173 """
176 """
174
177
175 def __init__(self, name, type, description, upgrademessage):
178 def __init__(self, name, type, description, upgrademessage):
176 self.name = name
179 self.name = name
177 self.type = type
180 self.type = type
178 self.description = description
181 self.description = description
179 self.upgrademessage = upgrademessage
182 self.upgrademessage = upgrademessage
180
183
181 def __eq__(self, other):
184 def __eq__(self, other):
182 if not isinstance(other, improvement):
185 if not isinstance(other, improvement):
183 # This is what python tell use to do
186 # This is what python tell use to do
184 return NotImplemented
187 return NotImplemented
185 return self.name == other.name
188 return self.name == other.name
186
189
187 def __ne__(self, other):
190 def __ne__(self, other):
188 return not (self == other)
191 return not (self == other)
189
192
190 def __hash__(self):
193 def __hash__(self):
191 return hash(self.name)
194 return hash(self.name)
192
195
193
196
194 allformatvariant = []
197 allformatvariant = []
195
198
196
199
197 def registerformatvariant(cls):
200 def registerformatvariant(cls):
198 allformatvariant.append(cls)
201 allformatvariant.append(cls)
199 return cls
202 return cls
200
203
201
204
202 class formatvariant(improvement):
205 class formatvariant(improvement):
203 """an improvement subclass dedicated to repository format"""
206 """an improvement subclass dedicated to repository format"""
204
207
205 type = deficiency
208 type = deficiency
206 ### The following attributes should be defined for each class:
209 ### The following attributes should be defined for each class:
207
210
208 # machine-readable string uniquely identifying this improvement. it will be
211 # machine-readable string uniquely identifying this improvement. it will be
209 # mapped to an action later in the upgrade process.
212 # mapped to an action later in the upgrade process.
210 name = None
213 name = None
211
214
212 # message intended for humans explaining the improvement in more detail,
215 # message intended for humans explaining the improvement in more detail,
213 # including the implications of it ``deficiency`` types, should be worded
216 # including the implications of it ``deficiency`` types, should be worded
214 # in the present tense.
217 # in the present tense.
215 description = None
218 description = None
216
219
217 # message intended for humans explaining what an upgrade addressing this
220 # message intended for humans explaining what an upgrade addressing this
218 # issue will do. should be worded in the future tense.
221 # issue will do. should be worded in the future tense.
219 upgrademessage = None
222 upgrademessage = None
220
223
221 # value of current Mercurial default for new repository
224 # value of current Mercurial default for new repository
222 default = None
225 default = None
223
226
224 def __init__(self):
227 def __init__(self):
225 raise NotImplementedError()
228 raise NotImplementedError()
226
229
227 @staticmethod
230 @staticmethod
228 def fromrepo(repo):
231 def fromrepo(repo):
229 """current value of the variant in the repository"""
232 """current value of the variant in the repository"""
230 raise NotImplementedError()
233 raise NotImplementedError()
231
234
232 @staticmethod
235 @staticmethod
233 def fromconfig(repo):
236 def fromconfig(repo):
234 """current value of the variant in the configuration"""
237 """current value of the variant in the configuration"""
235 raise NotImplementedError()
238 raise NotImplementedError()
236
239
237
240
238 class requirementformatvariant(formatvariant):
241 class requirementformatvariant(formatvariant):
239 """formatvariant based on a 'requirement' name.
242 """formatvariant based on a 'requirement' name.
240
243
241 Many format variant are controlled by a 'requirement'. We define a small
244 Many format variant are controlled by a 'requirement'. We define a small
242 subclass to factor the code.
245 subclass to factor the code.
243 """
246 """
244
247
245 # the requirement that control this format variant
248 # the requirement that control this format variant
246 _requirement = None
249 _requirement = None
247
250
248 @staticmethod
251 @staticmethod
249 def _newreporequirements(ui):
252 def _newreporequirements(ui):
250 return localrepo.newreporequirements(
253 return localrepo.newreporequirements(
251 ui, localrepo.defaultcreateopts(ui)
254 ui, localrepo.defaultcreateopts(ui)
252 )
255 )
253
256
254 @classmethod
257 @classmethod
255 def fromrepo(cls, repo):
258 def fromrepo(cls, repo):
256 assert cls._requirement is not None
259 assert cls._requirement is not None
257 return cls._requirement in repo.requirements
260 return cls._requirement in repo.requirements
258
261
259 @classmethod
262 @classmethod
260 def fromconfig(cls, repo):
263 def fromconfig(cls, repo):
261 assert cls._requirement is not None
264 assert cls._requirement is not None
262 return cls._requirement in cls._newreporequirements(repo.ui)
265 return cls._requirement in cls._newreporequirements(repo.ui)
263
266
264
267
265 @registerformatvariant
268 @registerformatvariant
266 class fncache(requirementformatvariant):
269 class fncache(requirementformatvariant):
267 name = b'fncache'
270 name = b'fncache'
268
271
269 _requirement = b'fncache'
272 _requirement = b'fncache'
270
273
271 default = True
274 default = True
272
275
273 description = _(
276 description = _(
274 b'long and reserved filenames may not work correctly; '
277 b'long and reserved filenames may not work correctly; '
275 b'repository performance is sub-optimal'
278 b'repository performance is sub-optimal'
276 )
279 )
277
280
278 upgrademessage = _(
281 upgrademessage = _(
279 b'repository will be more resilient to storing '
282 b'repository will be more resilient to storing '
280 b'certain paths and performance of certain '
283 b'certain paths and performance of certain '
281 b'operations should be improved'
284 b'operations should be improved'
282 )
285 )
283
286
284
287
285 @registerformatvariant
288 @registerformatvariant
286 class dotencode(requirementformatvariant):
289 class dotencode(requirementformatvariant):
287 name = b'dotencode'
290 name = b'dotencode'
288
291
289 _requirement = b'dotencode'
292 _requirement = b'dotencode'
290
293
291 default = True
294 default = True
292
295
293 description = _(
296 description = _(
294 b'storage of filenames beginning with a period or '
297 b'storage of filenames beginning with a period or '
295 b'space may not work correctly'
298 b'space may not work correctly'
296 )
299 )
297
300
298 upgrademessage = _(
301 upgrademessage = _(
299 b'repository will be better able to store files '
302 b'repository will be better able to store files '
300 b'beginning with a space or period'
303 b'beginning with a space or period'
301 )
304 )
302
305
303
306
304 @registerformatvariant
307 @registerformatvariant
305 class generaldelta(requirementformatvariant):
308 class generaldelta(requirementformatvariant):
306 name = b'generaldelta'
309 name = b'generaldelta'
307
310
308 _requirement = b'generaldelta'
311 _requirement = b'generaldelta'
309
312
310 default = True
313 default = True
311
314
312 description = _(
315 description = _(
313 b'deltas within internal storage are unable to '
316 b'deltas within internal storage are unable to '
314 b'choose optimal revisions; repository is larger and '
317 b'choose optimal revisions; repository is larger and '
315 b'slower than it could be; interaction with other '
318 b'slower than it could be; interaction with other '
316 b'repositories may require extra network and CPU '
319 b'repositories may require extra network and CPU '
317 b'resources, making "hg push" and "hg pull" slower'
320 b'resources, making "hg push" and "hg pull" slower'
318 )
321 )
319
322
320 upgrademessage = _(
323 upgrademessage = _(
321 b'repository storage will be able to create '
324 b'repository storage will be able to create '
322 b'optimal deltas; new repository data will be '
325 b'optimal deltas; new repository data will be '
323 b'smaller and read times should decrease; '
326 b'smaller and read times should decrease; '
324 b'interacting with other repositories using this '
327 b'interacting with other repositories using this '
325 b'storage model should require less network and '
328 b'storage model should require less network and '
326 b'CPU resources, making "hg push" and "hg pull" '
329 b'CPU resources, making "hg push" and "hg pull" '
327 b'faster'
330 b'faster'
328 )
331 )
329
332
330
333
331 @registerformatvariant
334 @registerformatvariant
332 class sparserevlog(requirementformatvariant):
335 class sparserevlog(requirementformatvariant):
333 name = b'sparserevlog'
336 name = b'sparserevlog'
334
337
335 _requirement = localrepo.SPARSEREVLOG_REQUIREMENT
338 _requirement = localrepo.SPARSEREVLOG_REQUIREMENT
336
339
337 default = True
340 default = True
338
341
339 description = _(
342 description = _(
340 b'in order to limit disk reading and memory usage on older '
343 b'in order to limit disk reading and memory usage on older '
341 b'version, the span of a delta chain from its root to its '
344 b'version, the span of a delta chain from its root to its '
342 b'end is limited, whatever the relevant data in this span. '
345 b'end is limited, whatever the relevant data in this span. '
343 b'This can severly limit Mercurial ability to build good '
346 b'This can severly limit Mercurial ability to build good '
344 b'chain of delta resulting is much more storage space being '
347 b'chain of delta resulting is much more storage space being '
345 b'taken and limit reusability of on disk delta during '
348 b'taken and limit reusability of on disk delta during '
346 b'exchange.'
349 b'exchange.'
347 )
350 )
348
351
349 upgrademessage = _(
352 upgrademessage = _(
350 b'Revlog supports delta chain with more unused data '
353 b'Revlog supports delta chain with more unused data '
351 b'between payload. These gaps will be skipped at read '
354 b'between payload. These gaps will be skipped at read '
352 b'time. This allows for better delta chains, making a '
355 b'time. This allows for better delta chains, making a '
353 b'better compression and faster exchange with server.'
356 b'better compression and faster exchange with server.'
354 )
357 )
355
358
356
359
357 @registerformatvariant
360 @registerformatvariant
358 class sidedata(requirementformatvariant):
361 class sidedata(requirementformatvariant):
359 name = b'sidedata'
362 name = b'sidedata'
360
363
361 _requirement = localrepo.SIDEDATA_REQUIREMENT
364 _requirement = localrepo.SIDEDATA_REQUIREMENT
362
365
363 default = False
366 default = False
364
367
365 description = _(
368 description = _(
366 b'Allows storage of extra data alongside a revision, '
369 b'Allows storage of extra data alongside a revision, '
367 b'unlocking various caching options.'
370 b'unlocking various caching options.'
368 )
371 )
369
372
370 upgrademessage = _(b'Allows storage of extra data alongside a revision.')
373 upgrademessage = _(b'Allows storage of extra data alongside a revision.')
371
374
372
375
373 @registerformatvariant
376 @registerformatvariant
374 class copiessdc(requirementformatvariant):
377 class copiessdc(requirementformatvariant):
375 name = b'copies-sdc'
378 name = b'copies-sdc'
376
379
377 _requirement = localrepo.COPIESSDC_REQUIREMENT
380 _requirement = localrepo.COPIESSDC_REQUIREMENT
378
381
379 default = False
382 default = False
380
383
381 description = _(b'Stores copies information alongside changesets.')
384 description = _(b'Stores copies information alongside changesets.')
382
385
383 upgrademessage = _(
386 upgrademessage = _(
384 b'Allows to use more efficient algorithm to deal with ' b'copy tracing.'
387 b'Allows to use more efficient algorithm to deal with ' b'copy tracing.'
385 )
388 )
386
389
387
390
388 @registerformatvariant
391 @registerformatvariant
389 class removecldeltachain(formatvariant):
392 class removecldeltachain(formatvariant):
390 name = b'plain-cl-delta'
393 name = b'plain-cl-delta'
391
394
392 default = True
395 default = True
393
396
394 description = _(
397 description = _(
395 b'changelog storage is using deltas instead of '
398 b'changelog storage is using deltas instead of '
396 b'raw entries; changelog reading and any '
399 b'raw entries; changelog reading and any '
397 b'operation relying on changelog data are slower '
400 b'operation relying on changelog data are slower '
398 b'than they could be'
401 b'than they could be'
399 )
402 )
400
403
401 upgrademessage = _(
404 upgrademessage = _(
402 b'changelog storage will be reformated to '
405 b'changelog storage will be reformated to '
403 b'store raw entries; changelog reading will be '
406 b'store raw entries; changelog reading will be '
404 b'faster; changelog size may be reduced'
407 b'faster; changelog size may be reduced'
405 )
408 )
406
409
407 @staticmethod
410 @staticmethod
408 def fromrepo(repo):
411 def fromrepo(repo):
409 # Mercurial 4.0 changed changelogs to not use delta chains. Search for
412 # Mercurial 4.0 changed changelogs to not use delta chains. Search for
410 # changelogs with deltas.
413 # changelogs with deltas.
411 cl = repo.changelog
414 cl = repo.changelog
412 chainbase = cl.chainbase
415 chainbase = cl.chainbase
413 return all(rev == chainbase(rev) for rev in cl)
416 return all(rev == chainbase(rev) for rev in cl)
414
417
415 @staticmethod
418 @staticmethod
416 def fromconfig(repo):
419 def fromconfig(repo):
417 return True
420 return True
418
421
419
422
420 @registerformatvariant
423 @registerformatvariant
421 class compressionengine(formatvariant):
424 class compressionengine(formatvariant):
422 name = b'compression'
425 name = b'compression'
423 default = b'zlib'
426 default = b'zlib'
424
427
425 description = _(
428 description = _(
426 b'Compresion algorithm used to compress data. '
429 b'Compresion algorithm used to compress data. '
427 b'Some engine are faster than other'
430 b'Some engine are faster than other'
428 )
431 )
429
432
430 upgrademessage = _(
433 upgrademessage = _(
431 b'revlog content will be recompressed with the new algorithm.'
434 b'revlog content will be recompressed with the new algorithm.'
432 )
435 )
433
436
434 @classmethod
437 @classmethod
435 def fromrepo(cls, repo):
438 def fromrepo(cls, repo):
436 # we allow multiple compression engine requirement to co-exist because
439 # we allow multiple compression engine requirement to co-exist because
437 # strickly speaking, revlog seems to support mixed compression style.
440 # strickly speaking, revlog seems to support mixed compression style.
438 #
441 #
439 # The compression used for new entries will be "the last one"
442 # The compression used for new entries will be "the last one"
440 compression = b'zlib'
443 compression = b'zlib'
441 for req in repo.requirements:
444 for req in repo.requirements:
442 prefix = req.startswith
445 prefix = req.startswith
443 if prefix(b'revlog-compression-') or prefix(b'exp-compression-'):
446 if prefix(b'revlog-compression-') or prefix(b'exp-compression-'):
444 compression = req.split(b'-', 2)[2]
447 compression = req.split(b'-', 2)[2]
445 return compression
448 return compression
446
449
447 @classmethod
450 @classmethod
448 def fromconfig(cls, repo):
451 def fromconfig(cls, repo):
449 return repo.ui.config(b'format', b'revlog-compression')
452 return repo.ui.config(b'format', b'revlog-compression')
450
453
451
454
452 @registerformatvariant
455 @registerformatvariant
453 class compressionlevel(formatvariant):
456 class compressionlevel(formatvariant):
454 name = b'compression-level'
457 name = b'compression-level'
455 default = b'default'
458 default = b'default'
456
459
457 description = _(b'compression level')
460 description = _(b'compression level')
458
461
459 upgrademessage = _(b'revlog content will be recompressed')
462 upgrademessage = _(b'revlog content will be recompressed')
460
463
461 @classmethod
464 @classmethod
462 def fromrepo(cls, repo):
465 def fromrepo(cls, repo):
463 comp = compressionengine.fromrepo(repo)
466 comp = compressionengine.fromrepo(repo)
464 level = None
467 level = None
465 if comp == b'zlib':
468 if comp == b'zlib':
466 level = repo.ui.configint(b'storage', b'revlog.zlib.level')
469 level = repo.ui.configint(b'storage', b'revlog.zlib.level')
467 elif comp == b'zstd':
470 elif comp == b'zstd':
468 level = repo.ui.configint(b'storage', b'revlog.zstd.level')
471 level = repo.ui.configint(b'storage', b'revlog.zstd.level')
469 if level is None:
472 if level is None:
470 return b'default'
473 return b'default'
471 return bytes(level)
474 return bytes(level)
472
475
473 @classmethod
476 @classmethod
474 def fromconfig(cls, repo):
477 def fromconfig(cls, repo):
475 comp = compressionengine.fromconfig(repo)
478 comp = compressionengine.fromconfig(repo)
476 level = None
479 level = None
477 if comp == b'zlib':
480 if comp == b'zlib':
478 level = repo.ui.configint(b'storage', b'revlog.zlib.level')
481 level = repo.ui.configint(b'storage', b'revlog.zlib.level')
479 elif comp == b'zstd':
482 elif comp == b'zstd':
480 level = repo.ui.configint(b'storage', b'revlog.zstd.level')
483 level = repo.ui.configint(b'storage', b'revlog.zstd.level')
481 if level is None:
484 if level is None:
482 return b'default'
485 return b'default'
483 return bytes(level)
486 return bytes(level)
484
487
485
488
486 def finddeficiencies(repo):
489 def finddeficiencies(repo):
487 """returns a list of deficiencies that the repo suffer from"""
490 """returns a list of deficiencies that the repo suffer from"""
488 deficiencies = []
491 deficiencies = []
489
492
490 # We could detect lack of revlogv1 and store here, but they were added
493 # We could detect lack of revlogv1 and store here, but they were added
491 # in 0.9.2 and we don't support upgrading repos without these
494 # in 0.9.2 and we don't support upgrading repos without these
492 # requirements, so let's not bother.
495 # requirements, so let's not bother.
493
496
494 for fv in allformatvariant:
497 for fv in allformatvariant:
495 if not fv.fromrepo(repo):
498 if not fv.fromrepo(repo):
496 deficiencies.append(fv)
499 deficiencies.append(fv)
497
500
498 return deficiencies
501 return deficiencies
499
502
500
503
501 # search without '-' to support older form on newer client.
504 # search without '-' to support older form on newer client.
502 #
505 #
503 # We don't enforce backward compatibility for debug command so this
506 # We don't enforce backward compatibility for debug command so this
504 # might eventually be dropped. However, having to use two different
507 # might eventually be dropped. However, having to use two different
505 # forms in script when comparing result is anoying enough to add
508 # forms in script when comparing result is anoying enough to add
506 # backward compatibility for a while.
509 # backward compatibility for a while.
507 legacy_opts_map = {
510 legacy_opts_map = {
508 b'redeltaparent': b're-delta-parent',
511 b'redeltaparent': b're-delta-parent',
509 b'redeltamultibase': b're-delta-multibase',
512 b'redeltamultibase': b're-delta-multibase',
510 b'redeltaall': b're-delta-all',
513 b'redeltaall': b're-delta-all',
511 b'redeltafulladd': b're-delta-fulladd',
514 b'redeltafulladd': b're-delta-fulladd',
512 }
515 }
513
516
514
517
515 def findoptimizations(repo):
518 def findoptimizations(repo):
516 """Determine optimisation that could be used during upgrade"""
519 """Determine optimisation that could be used during upgrade"""
517 # These are unconditionally added. There is logic later that figures out
520 # These are unconditionally added. There is logic later that figures out
518 # which ones to apply.
521 # which ones to apply.
519 optimizations = []
522 optimizations = []
520
523
521 optimizations.append(
524 optimizations.append(
522 improvement(
525 improvement(
523 name=b're-delta-parent',
526 name=b're-delta-parent',
524 type=optimisation,
527 type=optimisation,
525 description=_(
528 description=_(
526 b'deltas within internal storage will be recalculated to '
529 b'deltas within internal storage will be recalculated to '
527 b'choose an optimal base revision where this was not '
530 b'choose an optimal base revision where this was not '
528 b'already done; the size of the repository may shrink and '
531 b'already done; the size of the repository may shrink and '
529 b'various operations may become faster; the first time '
532 b'various operations may become faster; the first time '
530 b'this optimization is performed could slow down upgrade '
533 b'this optimization is performed could slow down upgrade '
531 b'execution considerably; subsequent invocations should '
534 b'execution considerably; subsequent invocations should '
532 b'not run noticeably slower'
535 b'not run noticeably slower'
533 ),
536 ),
534 upgrademessage=_(
537 upgrademessage=_(
535 b'deltas within internal storage will choose a new '
538 b'deltas within internal storage will choose a new '
536 b'base revision if needed'
539 b'base revision if needed'
537 ),
540 ),
538 )
541 )
539 )
542 )
540
543
541 optimizations.append(
544 optimizations.append(
542 improvement(
545 improvement(
543 name=b're-delta-multibase',
546 name=b're-delta-multibase',
544 type=optimisation,
547 type=optimisation,
545 description=_(
548 description=_(
546 b'deltas within internal storage will be recalculated '
549 b'deltas within internal storage will be recalculated '
547 b'against multiple base revision and the smallest '
550 b'against multiple base revision and the smallest '
548 b'difference will be used; the size of the repository may '
551 b'difference will be used; the size of the repository may '
549 b'shrink significantly when there are many merges; this '
552 b'shrink significantly when there are many merges; this '
550 b'optimization will slow down execution in proportion to '
553 b'optimization will slow down execution in proportion to '
551 b'the number of merges in the repository and the amount '
554 b'the number of merges in the repository and the amount '
552 b'of files in the repository; this slow down should not '
555 b'of files in the repository; this slow down should not '
553 b'be significant unless there are tens of thousands of '
556 b'be significant unless there are tens of thousands of '
554 b'files and thousands of merges'
557 b'files and thousands of merges'
555 ),
558 ),
556 upgrademessage=_(
559 upgrademessage=_(
557 b'deltas within internal storage will choose an '
560 b'deltas within internal storage will choose an '
558 b'optimal delta by computing deltas against multiple '
561 b'optimal delta by computing deltas against multiple '
559 b'parents; may slow down execution time '
562 b'parents; may slow down execution time '
560 b'significantly'
563 b'significantly'
561 ),
564 ),
562 )
565 )
563 )
566 )
564
567
565 optimizations.append(
568 optimizations.append(
566 improvement(
569 improvement(
567 name=b're-delta-all',
570 name=b're-delta-all',
568 type=optimisation,
571 type=optimisation,
569 description=_(
572 description=_(
570 b'deltas within internal storage will always be '
573 b'deltas within internal storage will always be '
571 b'recalculated without reusing prior deltas; this will '
574 b'recalculated without reusing prior deltas; this will '
572 b'likely make execution run several times slower; this '
575 b'likely make execution run several times slower; this '
573 b'optimization is typically not needed'
576 b'optimization is typically not needed'
574 ),
577 ),
575 upgrademessage=_(
578 upgrademessage=_(
576 b'deltas within internal storage will be fully '
579 b'deltas within internal storage will be fully '
577 b'recomputed; this will likely drastically slow down '
580 b'recomputed; this will likely drastically slow down '
578 b'execution time'
581 b'execution time'
579 ),
582 ),
580 )
583 )
581 )
584 )
582
585
583 optimizations.append(
586 optimizations.append(
584 improvement(
587 improvement(
585 name=b're-delta-fulladd',
588 name=b're-delta-fulladd',
586 type=optimisation,
589 type=optimisation,
587 description=_(
590 description=_(
588 b'every revision will be re-added as if it was new '
591 b'every revision will be re-added as if it was new '
589 b'content. It will go through the full storage '
592 b'content. It will go through the full storage '
590 b'mechanism giving extensions a chance to process it '
593 b'mechanism giving extensions a chance to process it '
591 b'(eg. lfs). This is similar to "re-delta-all" but even '
594 b'(eg. lfs). This is similar to "re-delta-all" but even '
592 b'slower since more logic is involved.'
595 b'slower since more logic is involved.'
593 ),
596 ),
594 upgrademessage=_(
597 upgrademessage=_(
595 b'each revision will be added as new content to the '
598 b'each revision will be added as new content to the '
596 b'internal storage; this will likely drastically slow '
599 b'internal storage; this will likely drastically slow '
597 b'down execution time, but some extensions might need '
600 b'down execution time, but some extensions might need '
598 b'it'
601 b'it'
599 ),
602 ),
600 )
603 )
601 )
604 )
602
605
603 return optimizations
606 return optimizations
604
607
605
608
606 def determineactions(repo, deficiencies, sourcereqs, destreqs):
609 def determineactions(repo, deficiencies, sourcereqs, destreqs):
607 """Determine upgrade actions that will be performed.
610 """Determine upgrade actions that will be performed.
608
611
609 Given a list of improvements as returned by ``finddeficiencies`` and
612 Given a list of improvements as returned by ``finddeficiencies`` and
610 ``findoptimizations``, determine the list of upgrade actions that
613 ``findoptimizations``, determine the list of upgrade actions that
611 will be performed.
614 will be performed.
612
615
613 The role of this function is to filter improvements if needed, apply
616 The role of this function is to filter improvements if needed, apply
614 recommended optimizations from the improvements list that make sense,
617 recommended optimizations from the improvements list that make sense,
615 etc.
618 etc.
616
619
617 Returns a list of action names.
620 Returns a list of action names.
618 """
621 """
619 newactions = []
622 newactions = []
620
623
621 knownreqs = supporteddestrequirements(repo)
624 knownreqs = supporteddestrequirements(repo)
622
625
623 for d in deficiencies:
626 for d in deficiencies:
624 name = d.name
627 name = d.name
625
628
626 # If the action is a requirement that doesn't show up in the
629 # If the action is a requirement that doesn't show up in the
627 # destination requirements, prune the action.
630 # destination requirements, prune the action.
628 if name in knownreqs and name not in destreqs:
631 if name in knownreqs and name not in destreqs:
629 continue
632 continue
630
633
631 newactions.append(d)
634 newactions.append(d)
632
635
633 # FUTURE consider adding some optimizations here for certain transitions.
636 # FUTURE consider adding some optimizations here for certain transitions.
634 # e.g. adding generaldelta could schedule parent redeltas.
637 # e.g. adding generaldelta could schedule parent redeltas.
635
638
636 return newactions
639 return newactions
637
640
638
641
639 def _revlogfrompath(repo, path):
642 def _revlogfrompath(repo, path):
640 """Obtain a revlog from a repo path.
643 """Obtain a revlog from a repo path.
641
644
642 An instance of the appropriate class is returned.
645 An instance of the appropriate class is returned.
643 """
646 """
644 if path == b'00changelog.i':
647 if path == b'00changelog.i':
645 return changelog.changelog(repo.svfs)
648 return changelog.changelog(repo.svfs)
646 elif path.endswith(b'00manifest.i'):
649 elif path.endswith(b'00manifest.i'):
647 mandir = path[: -len(b'00manifest.i')]
650 mandir = path[: -len(b'00manifest.i')]
648 return manifest.manifestrevlog(repo.svfs, tree=mandir)
651 return manifest.manifestrevlog(repo.svfs, tree=mandir)
649 else:
652 else:
650 # reverse of "/".join(("data", path + ".i"))
653 # reverse of "/".join(("data", path + ".i"))
651 return filelog.filelog(repo.svfs, path[5:-2])
654 return filelog.filelog(repo.svfs, path[5:-2])
652
655
653
656
654 def _copyrevlog(tr, destrepo, oldrl, unencodedname):
657 def _copyrevlog(tr, destrepo, oldrl, unencodedname):
655 """copy all relevant files for `oldrl` into `destrepo` store
658 """copy all relevant files for `oldrl` into `destrepo` store
656
659
657 Files are copied "as is" without any transformation. The copy is performed
660 Files are copied "as is" without any transformation. The copy is performed
658 without extra checks. Callers are responsible for making sure the copied
661 without extra checks. Callers are responsible for making sure the copied
659 content is compatible with format of the destination repository.
662 content is compatible with format of the destination repository.
660 """
663 """
661 oldrl = getattr(oldrl, '_revlog', oldrl)
664 oldrl = getattr(oldrl, '_revlog', oldrl)
662 newrl = _revlogfrompath(destrepo, unencodedname)
665 newrl = _revlogfrompath(destrepo, unencodedname)
663 newrl = getattr(newrl, '_revlog', newrl)
666 newrl = getattr(newrl, '_revlog', newrl)
664
667
665 oldvfs = oldrl.opener
668 oldvfs = oldrl.opener
666 newvfs = newrl.opener
669 newvfs = newrl.opener
667 oldindex = oldvfs.join(oldrl.indexfile)
670 oldindex = oldvfs.join(oldrl.indexfile)
668 newindex = newvfs.join(newrl.indexfile)
671 newindex = newvfs.join(newrl.indexfile)
669 olddata = oldvfs.join(oldrl.datafile)
672 olddata = oldvfs.join(oldrl.datafile)
670 newdata = newvfs.join(newrl.datafile)
673 newdata = newvfs.join(newrl.datafile)
671
674
672 with newvfs(newrl.indexfile, b'w'):
675 with newvfs(newrl.indexfile, b'w'):
673 pass # create all the directories
676 pass # create all the directories
674
677
675 util.copyfile(oldindex, newindex)
678 util.copyfile(oldindex, newindex)
676 copydata = oldrl.opener.exists(oldrl.datafile)
679 copydata = oldrl.opener.exists(oldrl.datafile)
677 if copydata:
680 if copydata:
678 util.copyfile(olddata, newdata)
681 util.copyfile(olddata, newdata)
679
682
680 if not (
683 if not (
681 unencodedname.endswith(b'00changelog.i')
684 unencodedname.endswith(b'00changelog.i')
682 or unencodedname.endswith(b'00manifest.i')
685 or unencodedname.endswith(b'00manifest.i')
683 ):
686 ):
684 destrepo.svfs.fncache.add(unencodedname)
687 destrepo.svfs.fncache.add(unencodedname)
685 if copydata:
688 if copydata:
686 destrepo.svfs.fncache.add(unencodedname[:-2] + b'.d')
689 destrepo.svfs.fncache.add(unencodedname[:-2] + b'.d')
687
690
688
691
689 UPGRADE_CHANGELOG = object()
692 UPGRADE_CHANGELOG = object()
690 UPGRADE_MANIFEST = object()
693 UPGRADE_MANIFEST = object()
691 UPGRADE_FILELOG = object()
694 UPGRADE_FILELOG = object()
692
695
693 UPGRADE_ALL_REVLOGS = frozenset(
696 UPGRADE_ALL_REVLOGS = frozenset(
694 [UPGRADE_CHANGELOG, UPGRADE_MANIFEST, UPGRADE_FILELOG]
697 [UPGRADE_CHANGELOG, UPGRADE_MANIFEST, UPGRADE_FILELOG]
695 )
698 )
696
699
697
700
698 def getsidedatacompanion(srcrepo, dstrepo):
701 def getsidedatacompanion(srcrepo, dstrepo):
699 sidedatacompanion = None
702 sidedatacompanion = None
700 removedreqs = srcrepo.requirements - dstrepo.requirements
703 removedreqs = srcrepo.requirements - dstrepo.requirements
704 addedreqs = dstrepo.requirements - srcrepo.requirements
701 if localrepo.SIDEDATA_REQUIREMENT in removedreqs:
705 if localrepo.SIDEDATA_REQUIREMENT in removedreqs:
702
706
703 def sidedatacompanion(rl, rev):
707 def sidedatacompanion(rl, rev):
704 rl = getattr(rl, '_revlog', rl)
708 rl = getattr(rl, '_revlog', rl)
705 if rl.flags(rev) & revlog.REVIDX_SIDEDATA:
709 if rl.flags(rev) & revlog.REVIDX_SIDEDATA:
706 return True, (), {}
710 return True, (), {}
707 return False, (), {}
711 return False, (), {}
708
712
713 elif localrepo.COPIESSDC_REQUIREMENT in addedreqs:
714 sidedatacompanion = copies.getsidedataadder(srcrepo, dstrepo)
715 elif localrepo.COPIESSDC_REQUIREMENT in removedreqs:
716 sidedatacompanion = copies.getsidedataremover(srcrepo, dstrepo)
709 return sidedatacompanion
717 return sidedatacompanion
710
718
711
719
712 def matchrevlog(revlogfilter, entry):
720 def matchrevlog(revlogfilter, entry):
713 """check is a revlog is selected for cloning
721 """check is a revlog is selected for cloning
714
722
715 The store entry is checked against the passed filter"""
723 The store entry is checked against the passed filter"""
716 if entry.endswith(b'00changelog.i'):
724 if entry.endswith(b'00changelog.i'):
717 return UPGRADE_CHANGELOG in revlogfilter
725 return UPGRADE_CHANGELOG in revlogfilter
718 elif entry.endswith(b'00manifest.i'):
726 elif entry.endswith(b'00manifest.i'):
719 return UPGRADE_MANIFEST in revlogfilter
727 return UPGRADE_MANIFEST in revlogfilter
720 return UPGRADE_FILELOG in revlogfilter
728 return UPGRADE_FILELOG in revlogfilter
721
729
722
730
723 def _clonerevlogs(
731 def _clonerevlogs(
724 ui,
732 ui,
725 srcrepo,
733 srcrepo,
726 dstrepo,
734 dstrepo,
727 tr,
735 tr,
728 deltareuse,
736 deltareuse,
729 forcedeltabothparents,
737 forcedeltabothparents,
730 revlogs=UPGRADE_ALL_REVLOGS,
738 revlogs=UPGRADE_ALL_REVLOGS,
731 ):
739 ):
732 """Copy revlogs between 2 repos."""
740 """Copy revlogs between 2 repos."""
733 revcount = 0
741 revcount = 0
734 srcsize = 0
742 srcsize = 0
735 srcrawsize = 0
743 srcrawsize = 0
736 dstsize = 0
744 dstsize = 0
737 fcount = 0
745 fcount = 0
738 frevcount = 0
746 frevcount = 0
739 fsrcsize = 0
747 fsrcsize = 0
740 frawsize = 0
748 frawsize = 0
741 fdstsize = 0
749 fdstsize = 0
742 mcount = 0
750 mcount = 0
743 mrevcount = 0
751 mrevcount = 0
744 msrcsize = 0
752 msrcsize = 0
745 mrawsize = 0
753 mrawsize = 0
746 mdstsize = 0
754 mdstsize = 0
747 crevcount = 0
755 crevcount = 0
748 csrcsize = 0
756 csrcsize = 0
749 crawsize = 0
757 crawsize = 0
750 cdstsize = 0
758 cdstsize = 0
751
759
752 alldatafiles = list(srcrepo.store.walk())
760 alldatafiles = list(srcrepo.store.walk())
753
761
754 # Perform a pass to collect metadata. This validates we can open all
762 # Perform a pass to collect metadata. This validates we can open all
755 # source files and allows a unified progress bar to be displayed.
763 # source files and allows a unified progress bar to be displayed.
756 for unencoded, encoded, size in alldatafiles:
764 for unencoded, encoded, size in alldatafiles:
757 if unencoded.endswith(b'.d'):
765 if unencoded.endswith(b'.d'):
758 continue
766 continue
759
767
760 rl = _revlogfrompath(srcrepo, unencoded)
768 rl = _revlogfrompath(srcrepo, unencoded)
761
769
762 info = rl.storageinfo(
770 info = rl.storageinfo(
763 exclusivefiles=True,
771 exclusivefiles=True,
764 revisionscount=True,
772 revisionscount=True,
765 trackedsize=True,
773 trackedsize=True,
766 storedsize=True,
774 storedsize=True,
767 )
775 )
768
776
769 revcount += info[b'revisionscount'] or 0
777 revcount += info[b'revisionscount'] or 0
770 datasize = info[b'storedsize'] or 0
778 datasize = info[b'storedsize'] or 0
771 rawsize = info[b'trackedsize'] or 0
779 rawsize = info[b'trackedsize'] or 0
772
780
773 srcsize += datasize
781 srcsize += datasize
774 srcrawsize += rawsize
782 srcrawsize += rawsize
775
783
776 # This is for the separate progress bars.
784 # This is for the separate progress bars.
777 if isinstance(rl, changelog.changelog):
785 if isinstance(rl, changelog.changelog):
778 crevcount += len(rl)
786 crevcount += len(rl)
779 csrcsize += datasize
787 csrcsize += datasize
780 crawsize += rawsize
788 crawsize += rawsize
781 elif isinstance(rl, manifest.manifestrevlog):
789 elif isinstance(rl, manifest.manifestrevlog):
782 mcount += 1
790 mcount += 1
783 mrevcount += len(rl)
791 mrevcount += len(rl)
784 msrcsize += datasize
792 msrcsize += datasize
785 mrawsize += rawsize
793 mrawsize += rawsize
786 elif isinstance(rl, filelog.filelog):
794 elif isinstance(rl, filelog.filelog):
787 fcount += 1
795 fcount += 1
788 frevcount += len(rl)
796 frevcount += len(rl)
789 fsrcsize += datasize
797 fsrcsize += datasize
790 frawsize += rawsize
798 frawsize += rawsize
791 else:
799 else:
792 error.ProgrammingError(b'unknown revlog type')
800 error.ProgrammingError(b'unknown revlog type')
793
801
794 if not revcount:
802 if not revcount:
795 return
803 return
796
804
797 ui.write(
805 ui.write(
798 _(
806 _(
799 b'migrating %d total revisions (%d in filelogs, %d in manifests, '
807 b'migrating %d total revisions (%d in filelogs, %d in manifests, '
800 b'%d in changelog)\n'
808 b'%d in changelog)\n'
801 )
809 )
802 % (revcount, frevcount, mrevcount, crevcount)
810 % (revcount, frevcount, mrevcount, crevcount)
803 )
811 )
804 ui.write(
812 ui.write(
805 _(b'migrating %s in store; %s tracked data\n')
813 _(b'migrating %s in store; %s tracked data\n')
806 % ((util.bytecount(srcsize), util.bytecount(srcrawsize)))
814 % ((util.bytecount(srcsize), util.bytecount(srcrawsize)))
807 )
815 )
808
816
809 # Used to keep track of progress.
817 # Used to keep track of progress.
810 progress = None
818 progress = None
811
819
812 def oncopiedrevision(rl, rev, node):
820 def oncopiedrevision(rl, rev, node):
813 progress.increment()
821 progress.increment()
814
822
815 sidedatacompanion = getsidedatacompanion(srcrepo, dstrepo)
823 sidedatacompanion = getsidedatacompanion(srcrepo, dstrepo)
816
824
817 # Do the actual copying.
825 # Do the actual copying.
818 # FUTURE this operation can be farmed off to worker processes.
826 # FUTURE this operation can be farmed off to worker processes.
819 seen = set()
827 seen = set()
820 for unencoded, encoded, size in alldatafiles:
828 for unencoded, encoded, size in alldatafiles:
821 if unencoded.endswith(b'.d'):
829 if unencoded.endswith(b'.d'):
822 continue
830 continue
823
831
824 oldrl = _revlogfrompath(srcrepo, unencoded)
832 oldrl = _revlogfrompath(srcrepo, unencoded)
825
833
826 if isinstance(oldrl, changelog.changelog) and b'c' not in seen:
834 if isinstance(oldrl, changelog.changelog) and b'c' not in seen:
827 ui.write(
835 ui.write(
828 _(
836 _(
829 b'finished migrating %d manifest revisions across %d '
837 b'finished migrating %d manifest revisions across %d '
830 b'manifests; change in size: %s\n'
838 b'manifests; change in size: %s\n'
831 )
839 )
832 % (mrevcount, mcount, util.bytecount(mdstsize - msrcsize))
840 % (mrevcount, mcount, util.bytecount(mdstsize - msrcsize))
833 )
841 )
834
842
835 ui.write(
843 ui.write(
836 _(
844 _(
837 b'migrating changelog containing %d revisions '
845 b'migrating changelog containing %d revisions '
838 b'(%s in store; %s tracked data)\n'
846 b'(%s in store; %s tracked data)\n'
839 )
847 )
840 % (
848 % (
841 crevcount,
849 crevcount,
842 util.bytecount(csrcsize),
850 util.bytecount(csrcsize),
843 util.bytecount(crawsize),
851 util.bytecount(crawsize),
844 )
852 )
845 )
853 )
846 seen.add(b'c')
854 seen.add(b'c')
847 progress = srcrepo.ui.makeprogress(
855 progress = srcrepo.ui.makeprogress(
848 _(b'changelog revisions'), total=crevcount
856 _(b'changelog revisions'), total=crevcount
849 )
857 )
850 elif isinstance(oldrl, manifest.manifestrevlog) and b'm' not in seen:
858 elif isinstance(oldrl, manifest.manifestrevlog) and b'm' not in seen:
851 ui.write(
859 ui.write(
852 _(
860 _(
853 b'finished migrating %d filelog revisions across %d '
861 b'finished migrating %d filelog revisions across %d '
854 b'filelogs; change in size: %s\n'
862 b'filelogs; change in size: %s\n'
855 )
863 )
856 % (frevcount, fcount, util.bytecount(fdstsize - fsrcsize))
864 % (frevcount, fcount, util.bytecount(fdstsize - fsrcsize))
857 )
865 )
858
866
859 ui.write(
867 ui.write(
860 _(
868 _(
861 b'migrating %d manifests containing %d revisions '
869 b'migrating %d manifests containing %d revisions '
862 b'(%s in store; %s tracked data)\n'
870 b'(%s in store; %s tracked data)\n'
863 )
871 )
864 % (
872 % (
865 mcount,
873 mcount,
866 mrevcount,
874 mrevcount,
867 util.bytecount(msrcsize),
875 util.bytecount(msrcsize),
868 util.bytecount(mrawsize),
876 util.bytecount(mrawsize),
869 )
877 )
870 )
878 )
871 seen.add(b'm')
879 seen.add(b'm')
872 if progress:
880 if progress:
873 progress.complete()
881 progress.complete()
874 progress = srcrepo.ui.makeprogress(
882 progress = srcrepo.ui.makeprogress(
875 _(b'manifest revisions'), total=mrevcount
883 _(b'manifest revisions'), total=mrevcount
876 )
884 )
877 elif b'f' not in seen:
885 elif b'f' not in seen:
878 ui.write(
886 ui.write(
879 _(
887 _(
880 b'migrating %d filelogs containing %d revisions '
888 b'migrating %d filelogs containing %d revisions '
881 b'(%s in store; %s tracked data)\n'
889 b'(%s in store; %s tracked data)\n'
882 )
890 )
883 % (
891 % (
884 fcount,
892 fcount,
885 frevcount,
893 frevcount,
886 util.bytecount(fsrcsize),
894 util.bytecount(fsrcsize),
887 util.bytecount(frawsize),
895 util.bytecount(frawsize),
888 )
896 )
889 )
897 )
890 seen.add(b'f')
898 seen.add(b'f')
891 if progress:
899 if progress:
892 progress.complete()
900 progress.complete()
893 progress = srcrepo.ui.makeprogress(
901 progress = srcrepo.ui.makeprogress(
894 _(b'file revisions'), total=frevcount
902 _(b'file revisions'), total=frevcount
895 )
903 )
896
904
897 if matchrevlog(revlogs, unencoded):
905 if matchrevlog(revlogs, unencoded):
898 ui.note(
906 ui.note(
899 _(b'cloning %d revisions from %s\n') % (len(oldrl), unencoded)
907 _(b'cloning %d revisions from %s\n') % (len(oldrl), unencoded)
900 )
908 )
901 newrl = _revlogfrompath(dstrepo, unencoded)
909 newrl = _revlogfrompath(dstrepo, unencoded)
902 oldrl.clone(
910 oldrl.clone(
903 tr,
911 tr,
904 newrl,
912 newrl,
905 addrevisioncb=oncopiedrevision,
913 addrevisioncb=oncopiedrevision,
906 deltareuse=deltareuse,
914 deltareuse=deltareuse,
907 forcedeltabothparents=forcedeltabothparents,
915 forcedeltabothparents=forcedeltabothparents,
908 sidedatacompanion=sidedatacompanion,
916 sidedatacompanion=sidedatacompanion,
909 )
917 )
910 else:
918 else:
911 msg = _(b'blindly copying %s containing %i revisions\n')
919 msg = _(b'blindly copying %s containing %i revisions\n')
912 ui.note(msg % (unencoded, len(oldrl)))
920 ui.note(msg % (unencoded, len(oldrl)))
913 _copyrevlog(tr, dstrepo, oldrl, unencoded)
921 _copyrevlog(tr, dstrepo, oldrl, unencoded)
914
922
915 newrl = _revlogfrompath(dstrepo, unencoded)
923 newrl = _revlogfrompath(dstrepo, unencoded)
916
924
917 info = newrl.storageinfo(storedsize=True)
925 info = newrl.storageinfo(storedsize=True)
918 datasize = info[b'storedsize'] or 0
926 datasize = info[b'storedsize'] or 0
919
927
920 dstsize += datasize
928 dstsize += datasize
921
929
922 if isinstance(newrl, changelog.changelog):
930 if isinstance(newrl, changelog.changelog):
923 cdstsize += datasize
931 cdstsize += datasize
924 elif isinstance(newrl, manifest.manifestrevlog):
932 elif isinstance(newrl, manifest.manifestrevlog):
925 mdstsize += datasize
933 mdstsize += datasize
926 else:
934 else:
927 fdstsize += datasize
935 fdstsize += datasize
928
936
929 progress.complete()
937 progress.complete()
930
938
931 ui.write(
939 ui.write(
932 _(
940 _(
933 b'finished migrating %d changelog revisions; change in size: '
941 b'finished migrating %d changelog revisions; change in size: '
934 b'%s\n'
942 b'%s\n'
935 )
943 )
936 % (crevcount, util.bytecount(cdstsize - csrcsize))
944 % (crevcount, util.bytecount(cdstsize - csrcsize))
937 )
945 )
938
946
939 ui.write(
947 ui.write(
940 _(
948 _(
941 b'finished migrating %d total revisions; total change in store '
949 b'finished migrating %d total revisions; total change in store '
942 b'size: %s\n'
950 b'size: %s\n'
943 )
951 )
944 % (revcount, util.bytecount(dstsize - srcsize))
952 % (revcount, util.bytecount(dstsize - srcsize))
945 )
953 )
946
954
947
955
948 def _filterstorefile(srcrepo, dstrepo, requirements, path, mode, st):
956 def _filterstorefile(srcrepo, dstrepo, requirements, path, mode, st):
949 """Determine whether to copy a store file during upgrade.
957 """Determine whether to copy a store file during upgrade.
950
958
951 This function is called when migrating store files from ``srcrepo`` to
959 This function is called when migrating store files from ``srcrepo`` to
952 ``dstrepo`` as part of upgrading a repository.
960 ``dstrepo`` as part of upgrading a repository.
953
961
954 Args:
962 Args:
955 srcrepo: repo we are copying from
963 srcrepo: repo we are copying from
956 dstrepo: repo we are copying to
964 dstrepo: repo we are copying to
957 requirements: set of requirements for ``dstrepo``
965 requirements: set of requirements for ``dstrepo``
958 path: store file being examined
966 path: store file being examined
959 mode: the ``ST_MODE`` file type of ``path``
967 mode: the ``ST_MODE`` file type of ``path``
960 st: ``stat`` data structure for ``path``
968 st: ``stat`` data structure for ``path``
961
969
962 Function should return ``True`` if the file is to be copied.
970 Function should return ``True`` if the file is to be copied.
963 """
971 """
964 # Skip revlogs.
972 # Skip revlogs.
965 if path.endswith((b'.i', b'.d')):
973 if path.endswith((b'.i', b'.d')):
966 return False
974 return False
967 # Skip transaction related files.
975 # Skip transaction related files.
968 if path.startswith(b'undo'):
976 if path.startswith(b'undo'):
969 return False
977 return False
970 # Only copy regular files.
978 # Only copy regular files.
971 if mode != stat.S_IFREG:
979 if mode != stat.S_IFREG:
972 return False
980 return False
973 # Skip other skipped files.
981 # Skip other skipped files.
974 if path in (b'lock', b'fncache'):
982 if path in (b'lock', b'fncache'):
975 return False
983 return False
976
984
977 return True
985 return True
978
986
979
987
980 def _finishdatamigration(ui, srcrepo, dstrepo, requirements):
988 def _finishdatamigration(ui, srcrepo, dstrepo, requirements):
981 """Hook point for extensions to perform additional actions during upgrade.
989 """Hook point for extensions to perform additional actions during upgrade.
982
990
983 This function is called after revlogs and store files have been copied but
991 This function is called after revlogs and store files have been copied but
984 before the new store is swapped into the original location.
992 before the new store is swapped into the original location.
985 """
993 """
986
994
987
995
988 def _upgraderepo(
996 def _upgraderepo(
989 ui, srcrepo, dstrepo, requirements, actions, revlogs=UPGRADE_ALL_REVLOGS
997 ui, srcrepo, dstrepo, requirements, actions, revlogs=UPGRADE_ALL_REVLOGS
990 ):
998 ):
991 """Do the low-level work of upgrading a repository.
999 """Do the low-level work of upgrading a repository.
992
1000
993 The upgrade is effectively performed as a copy between a source
1001 The upgrade is effectively performed as a copy between a source
994 repository and a temporary destination repository.
1002 repository and a temporary destination repository.
995
1003
996 The source repository is unmodified for as long as possible so the
1004 The source repository is unmodified for as long as possible so the
997 upgrade can abort at any time without causing loss of service for
1005 upgrade can abort at any time without causing loss of service for
998 readers and without corrupting the source repository.
1006 readers and without corrupting the source repository.
999 """
1007 """
1000 assert srcrepo.currentwlock()
1008 assert srcrepo.currentwlock()
1001 assert dstrepo.currentwlock()
1009 assert dstrepo.currentwlock()
1002
1010
1003 ui.write(
1011 ui.write(
1004 _(
1012 _(
1005 b'(it is safe to interrupt this process any time before '
1013 b'(it is safe to interrupt this process any time before '
1006 b'data migration completes)\n'
1014 b'data migration completes)\n'
1007 )
1015 )
1008 )
1016 )
1009
1017
1010 if b're-delta-all' in actions:
1018 if b're-delta-all' in actions:
1011 deltareuse = revlog.revlog.DELTAREUSENEVER
1019 deltareuse = revlog.revlog.DELTAREUSENEVER
1012 elif b're-delta-parent' in actions:
1020 elif b're-delta-parent' in actions:
1013 deltareuse = revlog.revlog.DELTAREUSESAMEREVS
1021 deltareuse = revlog.revlog.DELTAREUSESAMEREVS
1014 elif b're-delta-multibase' in actions:
1022 elif b're-delta-multibase' in actions:
1015 deltareuse = revlog.revlog.DELTAREUSESAMEREVS
1023 deltareuse = revlog.revlog.DELTAREUSESAMEREVS
1016 elif b're-delta-fulladd' in actions:
1024 elif b're-delta-fulladd' in actions:
1017 deltareuse = revlog.revlog.DELTAREUSEFULLADD
1025 deltareuse = revlog.revlog.DELTAREUSEFULLADD
1018 else:
1026 else:
1019 deltareuse = revlog.revlog.DELTAREUSEALWAYS
1027 deltareuse = revlog.revlog.DELTAREUSEALWAYS
1020
1028
1021 with dstrepo.transaction(b'upgrade') as tr:
1029 with dstrepo.transaction(b'upgrade') as tr:
1022 _clonerevlogs(
1030 _clonerevlogs(
1023 ui,
1031 ui,
1024 srcrepo,
1032 srcrepo,
1025 dstrepo,
1033 dstrepo,
1026 tr,
1034 tr,
1027 deltareuse,
1035 deltareuse,
1028 b're-delta-multibase' in actions,
1036 b're-delta-multibase' in actions,
1029 revlogs=revlogs,
1037 revlogs=revlogs,
1030 )
1038 )
1031
1039
1032 # Now copy other files in the store directory.
1040 # Now copy other files in the store directory.
1033 # The sorted() makes execution deterministic.
1041 # The sorted() makes execution deterministic.
1034 for p, kind, st in sorted(srcrepo.store.vfs.readdir(b'', stat=True)):
1042 for p, kind, st in sorted(srcrepo.store.vfs.readdir(b'', stat=True)):
1035 if not _filterstorefile(srcrepo, dstrepo, requirements, p, kind, st):
1043 if not _filterstorefile(srcrepo, dstrepo, requirements, p, kind, st):
1036 continue
1044 continue
1037
1045
1038 srcrepo.ui.write(_(b'copying %s\n') % p)
1046 srcrepo.ui.write(_(b'copying %s\n') % p)
1039 src = srcrepo.store.rawvfs.join(p)
1047 src = srcrepo.store.rawvfs.join(p)
1040 dst = dstrepo.store.rawvfs.join(p)
1048 dst = dstrepo.store.rawvfs.join(p)
1041 util.copyfile(src, dst, copystat=True)
1049 util.copyfile(src, dst, copystat=True)
1042
1050
1043 _finishdatamigration(ui, srcrepo, dstrepo, requirements)
1051 _finishdatamigration(ui, srcrepo, dstrepo, requirements)
1044
1052
1045 ui.write(_(b'data fully migrated to temporary repository\n'))
1053 ui.write(_(b'data fully migrated to temporary repository\n'))
1046
1054
1047 backuppath = pycompat.mkdtemp(prefix=b'upgradebackup.', dir=srcrepo.path)
1055 backuppath = pycompat.mkdtemp(prefix=b'upgradebackup.', dir=srcrepo.path)
1048 backupvfs = vfsmod.vfs(backuppath)
1056 backupvfs = vfsmod.vfs(backuppath)
1049
1057
1050 # Make a backup of requires file first, as it is the first to be modified.
1058 # Make a backup of requires file first, as it is the first to be modified.
1051 util.copyfile(srcrepo.vfs.join(b'requires'), backupvfs.join(b'requires'))
1059 util.copyfile(srcrepo.vfs.join(b'requires'), backupvfs.join(b'requires'))
1052
1060
1053 # We install an arbitrary requirement that clients must not support
1061 # We install an arbitrary requirement that clients must not support
1054 # as a mechanism to lock out new clients during the data swap. This is
1062 # as a mechanism to lock out new clients during the data swap. This is
1055 # better than allowing a client to continue while the repository is in
1063 # better than allowing a client to continue while the repository is in
1056 # an inconsistent state.
1064 # an inconsistent state.
1057 ui.write(
1065 ui.write(
1058 _(
1066 _(
1059 b'marking source repository as being upgraded; clients will be '
1067 b'marking source repository as being upgraded; clients will be '
1060 b'unable to read from repository\n'
1068 b'unable to read from repository\n'
1061 )
1069 )
1062 )
1070 )
1063 scmutil.writerequires(
1071 scmutil.writerequires(
1064 srcrepo.vfs, srcrepo.requirements | {b'upgradeinprogress'}
1072 srcrepo.vfs, srcrepo.requirements | {b'upgradeinprogress'}
1065 )
1073 )
1066
1074
1067 ui.write(_(b'starting in-place swap of repository data\n'))
1075 ui.write(_(b'starting in-place swap of repository data\n'))
1068 ui.write(_(b'replaced files will be backed up at %s\n') % backuppath)
1076 ui.write(_(b'replaced files will be backed up at %s\n') % backuppath)
1069
1077
1070 # Now swap in the new store directory. Doing it as a rename should make
1078 # Now swap in the new store directory. Doing it as a rename should make
1071 # the operation nearly instantaneous and atomic (at least in well-behaved
1079 # the operation nearly instantaneous and atomic (at least in well-behaved
1072 # environments).
1080 # environments).
1073 ui.write(_(b'replacing store...\n'))
1081 ui.write(_(b'replacing store...\n'))
1074 tstart = util.timer()
1082 tstart = util.timer()
1075 util.rename(srcrepo.spath, backupvfs.join(b'store'))
1083 util.rename(srcrepo.spath, backupvfs.join(b'store'))
1076 util.rename(dstrepo.spath, srcrepo.spath)
1084 util.rename(dstrepo.spath, srcrepo.spath)
1077 elapsed = util.timer() - tstart
1085 elapsed = util.timer() - tstart
1078 ui.write(
1086 ui.write(
1079 _(
1087 _(
1080 b'store replacement complete; repository was inconsistent for '
1088 b'store replacement complete; repository was inconsistent for '
1081 b'%0.1fs\n'
1089 b'%0.1fs\n'
1082 )
1090 )
1083 % elapsed
1091 % elapsed
1084 )
1092 )
1085
1093
1086 # We first write the requirements file. Any new requirements will lock
1094 # We first write the requirements file. Any new requirements will lock
1087 # out legacy clients.
1095 # out legacy clients.
1088 ui.write(
1096 ui.write(
1089 _(
1097 _(
1090 b'finalizing requirements file and making repository readable '
1098 b'finalizing requirements file and making repository readable '
1091 b'again\n'
1099 b'again\n'
1092 )
1100 )
1093 )
1101 )
1094 scmutil.writerequires(srcrepo.vfs, requirements)
1102 scmutil.writerequires(srcrepo.vfs, requirements)
1095
1103
1096 # The lock file from the old store won't be removed because nothing has a
1104 # The lock file from the old store won't be removed because nothing has a
1097 # reference to its new location. So clean it up manually. Alternatively, we
1105 # reference to its new location. So clean it up manually. Alternatively, we
1098 # could update srcrepo.svfs and other variables to point to the new
1106 # could update srcrepo.svfs and other variables to point to the new
1099 # location. This is simpler.
1107 # location. This is simpler.
1100 backupvfs.unlink(b'store/lock')
1108 backupvfs.unlink(b'store/lock')
1101
1109
1102 return backuppath
1110 return backuppath
1103
1111
1104
1112
1105 def upgraderepo(
1113 def upgraderepo(
1106 ui,
1114 ui,
1107 repo,
1115 repo,
1108 run=False,
1116 run=False,
1109 optimize=None,
1117 optimize=None,
1110 backup=True,
1118 backup=True,
1111 manifest=None,
1119 manifest=None,
1112 changelog=None,
1120 changelog=None,
1113 ):
1121 ):
1114 """Upgrade a repository in place."""
1122 """Upgrade a repository in place."""
1115 if optimize is None:
1123 if optimize is None:
1116 optimize = []
1124 optimize = []
1117 optimize = set(legacy_opts_map.get(o, o) for o in optimize)
1125 optimize = set(legacy_opts_map.get(o, o) for o in optimize)
1118 repo = repo.unfiltered()
1126 repo = repo.unfiltered()
1119
1127
1120 revlogs = set(UPGRADE_ALL_REVLOGS)
1128 revlogs = set(UPGRADE_ALL_REVLOGS)
1121 specentries = ((b'c', changelog), (b'm', manifest))
1129 specentries = ((b'c', changelog), (b'm', manifest))
1122 specified = [(y, x) for (y, x) in specentries if x is not None]
1130 specified = [(y, x) for (y, x) in specentries if x is not None]
1123 if specified:
1131 if specified:
1124 # we have some limitation on revlogs to be recloned
1132 # we have some limitation on revlogs to be recloned
1125 if any(x for y, x in specified):
1133 if any(x for y, x in specified):
1126 revlogs = set()
1134 revlogs = set()
1127 for r, enabled in specified:
1135 for r, enabled in specified:
1128 if enabled:
1136 if enabled:
1129 if r == b'c':
1137 if r == b'c':
1130 revlogs.add(UPGRADE_CHANGELOG)
1138 revlogs.add(UPGRADE_CHANGELOG)
1131 elif r == b'm':
1139 elif r == b'm':
1132 revlogs.add(UPGRADE_MANIFEST)
1140 revlogs.add(UPGRADE_MANIFEST)
1133 else:
1141 else:
1134 # none are enabled
1142 # none are enabled
1135 for r, __ in specified:
1143 for r, __ in specified:
1136 if r == b'c':
1144 if r == b'c':
1137 revlogs.discard(UPGRADE_CHANGELOG)
1145 revlogs.discard(UPGRADE_CHANGELOG)
1138 elif r == b'm':
1146 elif r == b'm':
1139 revlogs.discard(UPGRADE_MANIFEST)
1147 revlogs.discard(UPGRADE_MANIFEST)
1140
1148
1141 # Ensure the repository can be upgraded.
1149 # Ensure the repository can be upgraded.
1142 missingreqs = requiredsourcerequirements(repo) - repo.requirements
1150 missingreqs = requiredsourcerequirements(repo) - repo.requirements
1143 if missingreqs:
1151 if missingreqs:
1144 raise error.Abort(
1152 raise error.Abort(
1145 _(b'cannot upgrade repository; requirement missing: %s')
1153 _(b'cannot upgrade repository; requirement missing: %s')
1146 % _(b', ').join(sorted(missingreqs))
1154 % _(b', ').join(sorted(missingreqs))
1147 )
1155 )
1148
1156
1149 blockedreqs = blocksourcerequirements(repo) & repo.requirements
1157 blockedreqs = blocksourcerequirements(repo) & repo.requirements
1150 if blockedreqs:
1158 if blockedreqs:
1151 raise error.Abort(
1159 raise error.Abort(
1152 _(
1160 _(
1153 b'cannot upgrade repository; unsupported source '
1161 b'cannot upgrade repository; unsupported source '
1154 b'requirement: %s'
1162 b'requirement: %s'
1155 )
1163 )
1156 % _(b', ').join(sorted(blockedreqs))
1164 % _(b', ').join(sorted(blockedreqs))
1157 )
1165 )
1158
1166
1159 # FUTURE there is potentially a need to control the wanted requirements via
1167 # FUTURE there is potentially a need to control the wanted requirements via
1160 # command arguments or via an extension hook point.
1168 # command arguments or via an extension hook point.
1161 newreqs = localrepo.newreporequirements(
1169 newreqs = localrepo.newreporequirements(
1162 repo.ui, localrepo.defaultcreateopts(repo.ui)
1170 repo.ui, localrepo.defaultcreateopts(repo.ui)
1163 )
1171 )
1164 newreqs.update(preservedrequirements(repo))
1172 newreqs.update(preservedrequirements(repo))
1165
1173
1166 noremovereqs = (
1174 noremovereqs = (
1167 repo.requirements - newreqs - supportremovedrequirements(repo)
1175 repo.requirements - newreqs - supportremovedrequirements(repo)
1168 )
1176 )
1169 if noremovereqs:
1177 if noremovereqs:
1170 raise error.Abort(
1178 raise error.Abort(
1171 _(
1179 _(
1172 b'cannot upgrade repository; requirement would be '
1180 b'cannot upgrade repository; requirement would be '
1173 b'removed: %s'
1181 b'removed: %s'
1174 )
1182 )
1175 % _(b', ').join(sorted(noremovereqs))
1183 % _(b', ').join(sorted(noremovereqs))
1176 )
1184 )
1177
1185
1178 noaddreqs = newreqs - repo.requirements - allowednewrequirements(repo)
1186 noaddreqs = newreqs - repo.requirements - allowednewrequirements(repo)
1179 if noaddreqs:
1187 if noaddreqs:
1180 raise error.Abort(
1188 raise error.Abort(
1181 _(
1189 _(
1182 b'cannot upgrade repository; do not support adding '
1190 b'cannot upgrade repository; do not support adding '
1183 b'requirement: %s'
1191 b'requirement: %s'
1184 )
1192 )
1185 % _(b', ').join(sorted(noaddreqs))
1193 % _(b', ').join(sorted(noaddreqs))
1186 )
1194 )
1187
1195
1188 unsupportedreqs = newreqs - supporteddestrequirements(repo)
1196 unsupportedreqs = newreqs - supporteddestrequirements(repo)
1189 if unsupportedreqs:
1197 if unsupportedreqs:
1190 raise error.Abort(
1198 raise error.Abort(
1191 _(
1199 _(
1192 b'cannot upgrade repository; do not support '
1200 b'cannot upgrade repository; do not support '
1193 b'destination requirement: %s'
1201 b'destination requirement: %s'
1194 )
1202 )
1195 % _(b', ').join(sorted(unsupportedreqs))
1203 % _(b', ').join(sorted(unsupportedreqs))
1196 )
1204 )
1197
1205
1198 # Find and validate all improvements that can be made.
1206 # Find and validate all improvements that can be made.
1199 alloptimizations = findoptimizations(repo)
1207 alloptimizations = findoptimizations(repo)
1200
1208
1201 # Apply and Validate arguments.
1209 # Apply and Validate arguments.
1202 optimizations = []
1210 optimizations = []
1203 for o in alloptimizations:
1211 for o in alloptimizations:
1204 if o.name in optimize:
1212 if o.name in optimize:
1205 optimizations.append(o)
1213 optimizations.append(o)
1206 optimize.discard(o.name)
1214 optimize.discard(o.name)
1207
1215
1208 if optimize: # anything left is unknown
1216 if optimize: # anything left is unknown
1209 raise error.Abort(
1217 raise error.Abort(
1210 _(b'unknown optimization action requested: %s')
1218 _(b'unknown optimization action requested: %s')
1211 % b', '.join(sorted(optimize)),
1219 % b', '.join(sorted(optimize)),
1212 hint=_(b'run without arguments to see valid optimizations'),
1220 hint=_(b'run without arguments to see valid optimizations'),
1213 )
1221 )
1214
1222
1215 deficiencies = finddeficiencies(repo)
1223 deficiencies = finddeficiencies(repo)
1216 actions = determineactions(repo, deficiencies, repo.requirements, newreqs)
1224 actions = determineactions(repo, deficiencies, repo.requirements, newreqs)
1217 actions.extend(
1225 actions.extend(
1218 o
1226 o
1219 for o in sorted(optimizations)
1227 for o in sorted(optimizations)
1220 # determineactions could have added optimisation
1228 # determineactions could have added optimisation
1221 if o not in actions
1229 if o not in actions
1222 )
1230 )
1223
1231
1224 removedreqs = repo.requirements - newreqs
1232 removedreqs = repo.requirements - newreqs
1225 addedreqs = newreqs - repo.requirements
1233 addedreqs = newreqs - repo.requirements
1226
1234
1227 if revlogs != UPGRADE_ALL_REVLOGS:
1235 if revlogs != UPGRADE_ALL_REVLOGS:
1228 incompatible = RECLONES_REQUIREMENTS & (removedreqs | addedreqs)
1236 incompatible = RECLONES_REQUIREMENTS & (removedreqs | addedreqs)
1229 if incompatible:
1237 if incompatible:
1230 msg = _(
1238 msg = _(
1231 b'ignoring revlogs selection flags, format requirements '
1239 b'ignoring revlogs selection flags, format requirements '
1232 b'change: %s\n'
1240 b'change: %s\n'
1233 )
1241 )
1234 ui.warn(msg % b', '.join(sorted(incompatible)))
1242 ui.warn(msg % b', '.join(sorted(incompatible)))
1235 revlogs = UPGRADE_ALL_REVLOGS
1243 revlogs = UPGRADE_ALL_REVLOGS
1236
1244
1237 def printrequirements():
1245 def printrequirements():
1238 ui.write(_(b'requirements\n'))
1246 ui.write(_(b'requirements\n'))
1239 ui.write(
1247 ui.write(
1240 _(b' preserved: %s\n')
1248 _(b' preserved: %s\n')
1241 % _(b', ').join(sorted(newreqs & repo.requirements))
1249 % _(b', ').join(sorted(newreqs & repo.requirements))
1242 )
1250 )
1243
1251
1244 if repo.requirements - newreqs:
1252 if repo.requirements - newreqs:
1245 ui.write(
1253 ui.write(
1246 _(b' removed: %s\n')
1254 _(b' removed: %s\n')
1247 % _(b', ').join(sorted(repo.requirements - newreqs))
1255 % _(b', ').join(sorted(repo.requirements - newreqs))
1248 )
1256 )
1249
1257
1250 if newreqs - repo.requirements:
1258 if newreqs - repo.requirements:
1251 ui.write(
1259 ui.write(
1252 _(b' added: %s\n')
1260 _(b' added: %s\n')
1253 % _(b', ').join(sorted(newreqs - repo.requirements))
1261 % _(b', ').join(sorted(newreqs - repo.requirements))
1254 )
1262 )
1255
1263
1256 ui.write(b'\n')
1264 ui.write(b'\n')
1257
1265
1258 def printupgradeactions():
1266 def printupgradeactions():
1259 for a in actions:
1267 for a in actions:
1260 ui.write(b'%s\n %s\n\n' % (a.name, a.upgrademessage))
1268 ui.write(b'%s\n %s\n\n' % (a.name, a.upgrademessage))
1261
1269
1262 if not run:
1270 if not run:
1263 fromconfig = []
1271 fromconfig = []
1264 onlydefault = []
1272 onlydefault = []
1265
1273
1266 for d in deficiencies:
1274 for d in deficiencies:
1267 if d.fromconfig(repo):
1275 if d.fromconfig(repo):
1268 fromconfig.append(d)
1276 fromconfig.append(d)
1269 elif d.default:
1277 elif d.default:
1270 onlydefault.append(d)
1278 onlydefault.append(d)
1271
1279
1272 if fromconfig or onlydefault:
1280 if fromconfig or onlydefault:
1273
1281
1274 if fromconfig:
1282 if fromconfig:
1275 ui.write(
1283 ui.write(
1276 _(
1284 _(
1277 b'repository lacks features recommended by '
1285 b'repository lacks features recommended by '
1278 b'current config options:\n\n'
1286 b'current config options:\n\n'
1279 )
1287 )
1280 )
1288 )
1281 for i in fromconfig:
1289 for i in fromconfig:
1282 ui.write(b'%s\n %s\n\n' % (i.name, i.description))
1290 ui.write(b'%s\n %s\n\n' % (i.name, i.description))
1283
1291
1284 if onlydefault:
1292 if onlydefault:
1285 ui.write(
1293 ui.write(
1286 _(
1294 _(
1287 b'repository lacks features used by the default '
1295 b'repository lacks features used by the default '
1288 b'config options:\n\n'
1296 b'config options:\n\n'
1289 )
1297 )
1290 )
1298 )
1291 for i in onlydefault:
1299 for i in onlydefault:
1292 ui.write(b'%s\n %s\n\n' % (i.name, i.description))
1300 ui.write(b'%s\n %s\n\n' % (i.name, i.description))
1293
1301
1294 ui.write(b'\n')
1302 ui.write(b'\n')
1295 else:
1303 else:
1296 ui.write(
1304 ui.write(
1297 _(
1305 _(
1298 b'(no feature deficiencies found in existing '
1306 b'(no feature deficiencies found in existing '
1299 b'repository)\n'
1307 b'repository)\n'
1300 )
1308 )
1301 )
1309 )
1302
1310
1303 ui.write(
1311 ui.write(
1304 _(
1312 _(
1305 b'performing an upgrade with "--run" will make the following '
1313 b'performing an upgrade with "--run" will make the following '
1306 b'changes:\n\n'
1314 b'changes:\n\n'
1307 )
1315 )
1308 )
1316 )
1309
1317
1310 printrequirements()
1318 printrequirements()
1311 printupgradeactions()
1319 printupgradeactions()
1312
1320
1313 unusedoptimize = [i for i in alloptimizations if i not in actions]
1321 unusedoptimize = [i for i in alloptimizations if i not in actions]
1314
1322
1315 if unusedoptimize:
1323 if unusedoptimize:
1316 ui.write(
1324 ui.write(
1317 _(
1325 _(
1318 b'additional optimizations are available by specifying '
1326 b'additional optimizations are available by specifying '
1319 b'"--optimize <name>":\n\n'
1327 b'"--optimize <name>":\n\n'
1320 )
1328 )
1321 )
1329 )
1322 for i in unusedoptimize:
1330 for i in unusedoptimize:
1323 ui.write(_(b'%s\n %s\n\n') % (i.name, i.description))
1331 ui.write(_(b'%s\n %s\n\n') % (i.name, i.description))
1324 return
1332 return
1325
1333
1326 # Else we're in the run=true case.
1334 # Else we're in the run=true case.
1327 ui.write(_(b'upgrade will perform the following actions:\n\n'))
1335 ui.write(_(b'upgrade will perform the following actions:\n\n'))
1328 printrequirements()
1336 printrequirements()
1329 printupgradeactions()
1337 printupgradeactions()
1330
1338
1331 upgradeactions = [a.name for a in actions]
1339 upgradeactions = [a.name for a in actions]
1332
1340
1333 ui.write(_(b'beginning upgrade...\n'))
1341 ui.write(_(b'beginning upgrade...\n'))
1334 with repo.wlock(), repo.lock():
1342 with repo.wlock(), repo.lock():
1335 ui.write(_(b'repository locked and read-only\n'))
1343 ui.write(_(b'repository locked and read-only\n'))
1336 # Our strategy for upgrading the repository is to create a new,
1344 # Our strategy for upgrading the repository is to create a new,
1337 # temporary repository, write data to it, then do a swap of the
1345 # temporary repository, write data to it, then do a swap of the
1338 # data. There are less heavyweight ways to do this, but it is easier
1346 # data. There are less heavyweight ways to do this, but it is easier
1339 # to create a new repo object than to instantiate all the components
1347 # to create a new repo object than to instantiate all the components
1340 # (like the store) separately.
1348 # (like the store) separately.
1341 tmppath = pycompat.mkdtemp(prefix=b'upgrade.', dir=repo.path)
1349 tmppath = pycompat.mkdtemp(prefix=b'upgrade.', dir=repo.path)
1342 backuppath = None
1350 backuppath = None
1343 try:
1351 try:
1344 ui.write(
1352 ui.write(
1345 _(
1353 _(
1346 b'creating temporary repository to stage migrated '
1354 b'creating temporary repository to stage migrated '
1347 b'data: %s\n'
1355 b'data: %s\n'
1348 )
1356 )
1349 % tmppath
1357 % tmppath
1350 )
1358 )
1351
1359
1352 # clone ui without using ui.copy because repo.ui is protected
1360 # clone ui without using ui.copy because repo.ui is protected
1353 repoui = repo.ui.__class__(repo.ui)
1361 repoui = repo.ui.__class__(repo.ui)
1354 dstrepo = hg.repository(repoui, path=tmppath, create=True)
1362 dstrepo = hg.repository(repoui, path=tmppath, create=True)
1355
1363
1356 with dstrepo.wlock(), dstrepo.lock():
1364 with dstrepo.wlock(), dstrepo.lock():
1357 backuppath = _upgraderepo(
1365 backuppath = _upgraderepo(
1358 ui, repo, dstrepo, newreqs, upgradeactions, revlogs=revlogs
1366 ui, repo, dstrepo, newreqs, upgradeactions, revlogs=revlogs
1359 )
1367 )
1360 if not (backup or backuppath is None):
1368 if not (backup or backuppath is None):
1361 ui.write(_(b'removing old repository content%s\n') % backuppath)
1369 ui.write(_(b'removing old repository content%s\n') % backuppath)
1362 repo.vfs.rmtree(backuppath, forcibly=True)
1370 repo.vfs.rmtree(backuppath, forcibly=True)
1363 backuppath = None
1371 backuppath = None
1364
1372
1365 finally:
1373 finally:
1366 ui.write(_(b'removing temporary repository %s\n') % tmppath)
1374 ui.write(_(b'removing temporary repository %s\n') % tmppath)
1367 repo.vfs.rmtree(tmppath, forcibly=True)
1375 repo.vfs.rmtree(tmppath, forcibly=True)
1368
1376
1369 if backuppath:
1377 if backuppath:
1370 ui.warn(
1378 ui.warn(
1371 _(b'copy of old repository backed up at %s\n') % backuppath
1379 _(b'copy of old repository backed up at %s\n') % backuppath
1372 )
1380 )
1373 ui.warn(
1381 ui.warn(
1374 _(
1382 _(
1375 b'the old repository will not be deleted; remove '
1383 b'the old repository will not be deleted; remove '
1376 b'it to free up disk space once the upgraded '
1384 b'it to free up disk space once the upgraded '
1377 b'repository is verified\n'
1385 b'repository is verified\n'
1378 )
1386 )
1379 )
1387 )
@@ -1,453 +1,540 b''
1 #testcases extra sidedata
1 #testcases extra sidedata
2
2
3 #if extra
3 #if extra
4 $ cat >> $HGRCPATH << EOF
4 $ cat >> $HGRCPATH << EOF
5 > [experimental]
5 > [experimental]
6 > copies.write-to=changeset-only
6 > copies.write-to=changeset-only
7 > copies.read-from=changeset-only
7 > copies.read-from=changeset-only
8 > [alias]
8 > [alias]
9 > changesetcopies = log -r . -T 'files: {files}
9 > changesetcopies = log -r . -T 'files: {files}
10 > {extras % "{ifcontains("files", key, "{key}: {value}\n")}"}
10 > {extras % "{ifcontains("files", key, "{key}: {value}\n")}"}
11 > {extras % "{ifcontains("copies", key, "{key}: {value}\n")}"}'
11 > {extras % "{ifcontains("copies", key, "{key}: {value}\n")}"}'
12 > EOF
12 > EOF
13 #endif
13 #endif
14
14
15 #if sidedata
15 #if sidedata
16 $ cat >> $HGRCPATH << EOF
16 $ cat >> $HGRCPATH << EOF
17 > [format]
17 > [format]
18 > exp-use-copies-side-data-changeset = yes
18 > exp-use-copies-side-data-changeset = yes
19 > EOF
19 > EOF
20 #endif
20 #endif
21
21
22 $ cat >> $HGRCPATH << EOF
22 $ cat >> $HGRCPATH << EOF
23 > [alias]
23 > [alias]
24 > showcopies = log -r . -T '{file_copies % "{source} -> {name}\n"}'
24 > showcopies = log -r . -T '{file_copies % "{source} -> {name}\n"}'
25 > [extensions]
25 > [extensions]
26 > rebase =
26 > rebase =
27 > split =
27 > split =
28 > EOF
28 > EOF
29
29
30 Check that copies are recorded correctly
30 Check that copies are recorded correctly
31
31
32 $ hg init repo
32 $ hg init repo
33 $ cd repo
33 $ cd repo
34 #if sidedata
34 #if sidedata
35 $ hg debugformat -v
35 $ hg debugformat -v
36 format-variant repo config default
36 format-variant repo config default
37 fncache: yes yes yes
37 fncache: yes yes yes
38 dotencode: yes yes yes
38 dotencode: yes yes yes
39 generaldelta: yes yes yes
39 generaldelta: yes yes yes
40 sparserevlog: yes yes yes
40 sparserevlog: yes yes yes
41 sidedata: yes yes no
41 sidedata: yes yes no
42 copies-sdc: yes yes no
42 copies-sdc: yes yes no
43 plain-cl-delta: yes yes yes
43 plain-cl-delta: yes yes yes
44 compression: zlib zlib zlib
44 compression: zlib zlib zlib
45 compression-level: default default default
45 compression-level: default default default
46 #else
46 #else
47 $ hg debugformat -v
47 $ hg debugformat -v
48 format-variant repo config default
48 format-variant repo config default
49 fncache: yes yes yes
49 fncache: yes yes yes
50 dotencode: yes yes yes
50 dotencode: yes yes yes
51 generaldelta: yes yes yes
51 generaldelta: yes yes yes
52 sparserevlog: yes yes yes
52 sparserevlog: yes yes yes
53 sidedata: no no no
53 sidedata: no no no
54 copies-sdc: no no no
54 copies-sdc: no no no
55 plain-cl-delta: yes yes yes
55 plain-cl-delta: yes yes yes
56 compression: zlib zlib zlib
56 compression: zlib zlib zlib
57 compression-level: default default default
57 compression-level: default default default
58 #endif
58 #endif
59 $ echo a > a
59 $ echo a > a
60 $ hg add a
60 $ hg add a
61 $ hg ci -m initial
61 $ hg ci -m initial
62 $ hg cp a b
62 $ hg cp a b
63 $ hg cp a c
63 $ hg cp a c
64 $ hg cp a d
64 $ hg cp a d
65 $ hg ci -m 'copy a to b, c, and d'
65 $ hg ci -m 'copy a to b, c, and d'
66
66
67 #if extra
67 #if extra
68
68
69 $ hg changesetcopies
69 $ hg changesetcopies
70 files: b c d
70 files: b c d
71 filesadded: 0
71 filesadded: 0
72 1
72 1
73 2
73 2
74
74
75 p1copies: 0\x00a (esc)
75 p1copies: 0\x00a (esc)
76 1\x00a (esc)
76 1\x00a (esc)
77 2\x00a (esc)
77 2\x00a (esc)
78 #else
78 #else
79 $ hg debugsidedata -c -v -- -1
79 $ hg debugsidedata -c -v -- -1
80 4 sidedata entries
80 4 sidedata entries
81 entry-0010 size 11
81 entry-0010 size 11
82 '0\x00a\n1\x00a\n2\x00a'
82 '0\x00a\n1\x00a\n2\x00a'
83 entry-0011 size 0
83 entry-0011 size 0
84 ''
84 ''
85 entry-0012 size 5
85 entry-0012 size 5
86 '0\n1\n2'
86 '0\n1\n2'
87 entry-0013 size 0
87 entry-0013 size 0
88 ''
88 ''
89 #endif
89 #endif
90
90
91 $ hg showcopies
91 $ hg showcopies
92 a -> b
92 a -> b
93 a -> c
93 a -> c
94 a -> d
94 a -> d
95
95
96 #if extra
96 #if extra
97
97
98 $ hg showcopies --config experimental.copies.read-from=compatibility
98 $ hg showcopies --config experimental.copies.read-from=compatibility
99 a -> b
99 a -> b
100 a -> c
100 a -> c
101 a -> d
101 a -> d
102 $ hg showcopies --config experimental.copies.read-from=filelog-only
102 $ hg showcopies --config experimental.copies.read-from=filelog-only
103
103
104 #endif
104 #endif
105
105
106 Check that renames are recorded correctly
106 Check that renames are recorded correctly
107
107
108 $ hg mv b b2
108 $ hg mv b b2
109 $ hg ci -m 'rename b to b2'
109 $ hg ci -m 'rename b to b2'
110
110
111 #if extra
111 #if extra
112
112
113 $ hg changesetcopies
113 $ hg changesetcopies
114 files: b b2
114 files: b b2
115 filesadded: 1
115 filesadded: 1
116 filesremoved: 0
116 filesremoved: 0
117
117
118 p1copies: 1\x00b (esc)
118 p1copies: 1\x00b (esc)
119
119
120 #else
120 #else
121 $ hg debugsidedata -c -v -- -1
121 $ hg debugsidedata -c -v -- -1
122 4 sidedata entries
122 4 sidedata entries
123 entry-0010 size 3
123 entry-0010 size 3
124 '1\x00b'
124 '1\x00b'
125 entry-0011 size 0
125 entry-0011 size 0
126 ''
126 ''
127 entry-0012 size 1
127 entry-0012 size 1
128 '1'
128 '1'
129 entry-0013 size 1
129 entry-0013 size 1
130 '0'
130 '0'
131 #endif
131 #endif
132
132
133 $ hg showcopies
133 $ hg showcopies
134 b -> b2
134 b -> b2
135
135
136
136
137 Rename onto existing file. This should get recorded in the changeset files list and in the extras,
137 Rename onto existing file. This should get recorded in the changeset files list and in the extras,
138 even though there is no filelog entry.
138 even though there is no filelog entry.
139
139
140 $ hg cp b2 c --force
140 $ hg cp b2 c --force
141 $ hg st --copies
141 $ hg st --copies
142 M c
142 M c
143 b2
143 b2
144
144
145 #if extra
145 #if extra
146
146
147 $ hg debugindex c
147 $ hg debugindex c
148 rev linkrev nodeid p1 p2
148 rev linkrev nodeid p1 p2
149 0 1 b789fdd96dc2 000000000000 000000000000
149 0 1 b789fdd96dc2 000000000000 000000000000
150
150
151 #else
151 #else
152
152
153 $ hg debugindex c
153 $ hg debugindex c
154 rev linkrev nodeid p1 p2
154 rev linkrev nodeid p1 p2
155 0 1 37d9b5d994ea 000000000000 000000000000
155 0 1 37d9b5d994ea 000000000000 000000000000
156
156
157 #endif
157 #endif
158
158
159
159
160 $ hg ci -m 'move b onto d'
160 $ hg ci -m 'move b onto d'
161
161
162 #if extra
162 #if extra
163
163
164 $ hg changesetcopies
164 $ hg changesetcopies
165 files: c
165 files: c
166
166
167 p1copies: 0\x00b2 (esc)
167 p1copies: 0\x00b2 (esc)
168
168
169 #else
169 #else
170 $ hg debugsidedata -c -v -- -1
170 $ hg debugsidedata -c -v -- -1
171 4 sidedata entries
171 4 sidedata entries
172 entry-0010 size 4
172 entry-0010 size 4
173 '0\x00b2'
173 '0\x00b2'
174 entry-0011 size 0
174 entry-0011 size 0
175 ''
175 ''
176 entry-0012 size 0
176 entry-0012 size 0
177 ''
177 ''
178 entry-0013 size 0
178 entry-0013 size 0
179 ''
179 ''
180 #endif
180 #endif
181
181
182 $ hg showcopies
182 $ hg showcopies
183 b2 -> c
183 b2 -> c
184
184
185 #if extra
185 #if extra
186
186
187 $ hg debugindex c
187 $ hg debugindex c
188 rev linkrev nodeid p1 p2
188 rev linkrev nodeid p1 p2
189 0 1 b789fdd96dc2 000000000000 000000000000
189 0 1 b789fdd96dc2 000000000000 000000000000
190
190
191 #else
191 #else
192
192
193 $ hg debugindex c
193 $ hg debugindex c
194 rev linkrev nodeid p1 p2
194 rev linkrev nodeid p1 p2
195 0 1 37d9b5d994ea 000000000000 000000000000
195 0 1 37d9b5d994ea 000000000000 000000000000
196 1 3 029625640347 000000000000 000000000000
196 1 3 029625640347 000000000000 000000000000
197
197
198 #endif
198 #endif
199
199
200 Create a merge commit with copying done during merge.
200 Create a merge commit with copying done during merge.
201
201
202 $ hg co 0
202 $ hg co 0
203 0 files updated, 0 files merged, 3 files removed, 0 files unresolved
203 0 files updated, 0 files merged, 3 files removed, 0 files unresolved
204 $ hg cp a e
204 $ hg cp a e
205 $ hg cp a f
205 $ hg cp a f
206 $ hg ci -m 'copy a to e and f'
206 $ hg ci -m 'copy a to e and f'
207 created new head
207 created new head
208 $ hg merge 3
208 $ hg merge 3
209 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
209 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
210 (branch merge, don't forget to commit)
210 (branch merge, don't forget to commit)
211 File 'a' exists on both sides, so 'g' could be recorded as being from p1 or p2, but we currently
211 File 'a' exists on both sides, so 'g' could be recorded as being from p1 or p2, but we currently
212 always record it as being from p1
212 always record it as being from p1
213 $ hg cp a g
213 $ hg cp a g
214 File 'd' exists only in p2, so 'h' should be from p2
214 File 'd' exists only in p2, so 'h' should be from p2
215 $ hg cp d h
215 $ hg cp d h
216 File 'f' exists only in p1, so 'i' should be from p1
216 File 'f' exists only in p1, so 'i' should be from p1
217 $ hg cp f i
217 $ hg cp f i
218 $ hg ci -m 'merge'
218 $ hg ci -m 'merge'
219
219
220 #if extra
220 #if extra
221
221
222 $ hg changesetcopies
222 $ hg changesetcopies
223 files: g h i
223 files: g h i
224 filesadded: 0
224 filesadded: 0
225 1
225 1
226 2
226 2
227
227
228 p1copies: 0\x00a (esc)
228 p1copies: 0\x00a (esc)
229 2\x00f (esc)
229 2\x00f (esc)
230 p2copies: 1\x00d (esc)
230 p2copies: 1\x00d (esc)
231
231
232 #else
232 #else
233 $ hg debugsidedata -c -v -- -1
233 $ hg debugsidedata -c -v -- -1
234 4 sidedata entries
234 4 sidedata entries
235 entry-0010 size 7
235 entry-0010 size 7
236 '0\x00a\n2\x00f'
236 '0\x00a\n2\x00f'
237 entry-0011 size 3
237 entry-0011 size 3
238 '1\x00d'
238 '1\x00d'
239 entry-0012 size 5
239 entry-0012 size 5
240 '0\n1\n2'
240 '0\n1\n2'
241 entry-0013 size 0
241 entry-0013 size 0
242 ''
242 ''
243 #endif
243 #endif
244
244
245 $ hg showcopies
245 $ hg showcopies
246 a -> g
246 a -> g
247 d -> h
247 d -> h
248 f -> i
248 f -> i
249
249
250 Test writing to both changeset and filelog
250 Test writing to both changeset and filelog
251
251
252 $ hg cp a j
252 $ hg cp a j
253 #if extra
253 #if extra
254 $ hg ci -m 'copy a to j' --config experimental.copies.write-to=compatibility
254 $ hg ci -m 'copy a to j' --config experimental.copies.write-to=compatibility
255 $ hg changesetcopies
255 $ hg changesetcopies
256 files: j
256 files: j
257 filesadded: 0
257 filesadded: 0
258 filesremoved:
258 filesremoved:
259
259
260 p1copies: 0\x00a (esc)
260 p1copies: 0\x00a (esc)
261 p2copies:
261 p2copies:
262 #else
262 #else
263 $ hg ci -m 'copy a to j'
263 $ hg ci -m 'copy a to j'
264 $ hg debugsidedata -c -v -- -1
264 $ hg debugsidedata -c -v -- -1
265 4 sidedata entries
265 4 sidedata entries
266 entry-0010 size 3
266 entry-0010 size 3
267 '0\x00a'
267 '0\x00a'
268 entry-0011 size 0
268 entry-0011 size 0
269 ''
269 ''
270 entry-0012 size 1
270 entry-0012 size 1
271 '0'
271 '0'
272 entry-0013 size 0
272 entry-0013 size 0
273 ''
273 ''
274 #endif
274 #endif
275 $ hg debugdata j 0
275 $ hg debugdata j 0
276 \x01 (esc)
276 \x01 (esc)
277 copy: a
277 copy: a
278 copyrev: b789fdd96dc2f3bd229c1dd8eedf0fc60e2b68e3
278 copyrev: b789fdd96dc2f3bd229c1dd8eedf0fc60e2b68e3
279 \x01 (esc)
279 \x01 (esc)
280 a
280 a
281 $ hg showcopies
281 $ hg showcopies
282 a -> j
282 a -> j
283 $ hg showcopies --config experimental.copies.read-from=compatibility
283 $ hg showcopies --config experimental.copies.read-from=compatibility
284 a -> j
284 a -> j
285 $ hg showcopies --config experimental.copies.read-from=filelog-only
285 $ hg showcopies --config experimental.copies.read-from=filelog-only
286 a -> j
286 a -> j
287 Existing copy information in the changeset gets removed on amend and writing
287 Existing copy information in the changeset gets removed on amend and writing
288 copy information on to the filelog
288 copy information on to the filelog
289 #if extra
289 #if extra
290 $ hg ci --amend -m 'copy a to j, v2' \
290 $ hg ci --amend -m 'copy a to j, v2' \
291 > --config experimental.copies.write-to=filelog-only
291 > --config experimental.copies.write-to=filelog-only
292 saved backup bundle to $TESTTMP/repo/.hg/strip-backup/*-*-amend.hg (glob)
292 saved backup bundle to $TESTTMP/repo/.hg/strip-backup/*-*-amend.hg (glob)
293 $ hg changesetcopies
293 $ hg changesetcopies
294 files: j
294 files: j
295
295
296 #else
296 #else
297 $ hg ci --amend -m 'copy a to j, v2'
297 $ hg ci --amend -m 'copy a to j, v2'
298 saved backup bundle to $TESTTMP/repo/.hg/strip-backup/*-*-amend.hg (glob)
298 saved backup bundle to $TESTTMP/repo/.hg/strip-backup/*-*-amend.hg (glob)
299 $ hg debugsidedata -c -v -- -1
299 $ hg debugsidedata -c -v -- -1
300 4 sidedata entries
300 4 sidedata entries
301 entry-0010 size 3
301 entry-0010 size 3
302 '0\x00a'
302 '0\x00a'
303 entry-0011 size 0
303 entry-0011 size 0
304 ''
304 ''
305 entry-0012 size 1
305 entry-0012 size 1
306 '0'
306 '0'
307 entry-0013 size 0
307 entry-0013 size 0
308 ''
308 ''
309 #endif
309 #endif
310 $ hg showcopies --config experimental.copies.read-from=filelog-only
310 $ hg showcopies --config experimental.copies.read-from=filelog-only
311 a -> j
311 a -> j
312 The entries should be written to extras even if they're empty (so the client
312 The entries should be written to extras even if they're empty (so the client
313 won't have to fall back to reading from filelogs)
313 won't have to fall back to reading from filelogs)
314 $ echo x >> j
314 $ echo x >> j
315 #if extra
315 #if extra
316 $ hg ci -m 'modify j' --config experimental.copies.write-to=compatibility
316 $ hg ci -m 'modify j' --config experimental.copies.write-to=compatibility
317 $ hg changesetcopies
317 $ hg changesetcopies
318 files: j
318 files: j
319 filesadded:
319 filesadded:
320 filesremoved:
320 filesremoved:
321
321
322 p1copies:
322 p1copies:
323 p2copies:
323 p2copies:
324 #else
324 #else
325 $ hg ci -m 'modify j'
325 $ hg ci -m 'modify j'
326 $ hg debugsidedata -c -v -- -1
326 $ hg debugsidedata -c -v -- -1
327 4 sidedata entries
327 4 sidedata entries
328 entry-0010 size 0
328 entry-0010 size 0
329 ''
329 ''
330 entry-0011 size 0
330 entry-0011 size 0
331 ''
331 ''
332 entry-0012 size 0
332 entry-0012 size 0
333 ''
333 ''
334 entry-0013 size 0
334 entry-0013 size 0
335 ''
335 ''
336 #endif
336 #endif
337
337
338 Test writing only to filelog
338 Test writing only to filelog
339
339
340 $ hg cp a k
340 $ hg cp a k
341 #if extra
341 #if extra
342 $ hg ci -m 'copy a to k' --config experimental.copies.write-to=filelog-only
342 $ hg ci -m 'copy a to k' --config experimental.copies.write-to=filelog-only
343
343
344 $ hg changesetcopies
344 $ hg changesetcopies
345 files: k
345 files: k
346
346
347 #else
347 #else
348 $ hg ci -m 'copy a to k'
348 $ hg ci -m 'copy a to k'
349 $ hg debugsidedata -c -v -- -1
349 $ hg debugsidedata -c -v -- -1
350 4 sidedata entries
350 4 sidedata entries
351 entry-0010 size 3
351 entry-0010 size 3
352 '0\x00a'
352 '0\x00a'
353 entry-0011 size 0
353 entry-0011 size 0
354 ''
354 ''
355 entry-0012 size 1
355 entry-0012 size 1
356 '0'
356 '0'
357 entry-0013 size 0
357 entry-0013 size 0
358 ''
358 ''
359 #endif
359 #endif
360
360
361 $ hg debugdata k 0
361 $ hg debugdata k 0
362 \x01 (esc)
362 \x01 (esc)
363 copy: a
363 copy: a
364 copyrev: b789fdd96dc2f3bd229c1dd8eedf0fc60e2b68e3
364 copyrev: b789fdd96dc2f3bd229c1dd8eedf0fc60e2b68e3
365 \x01 (esc)
365 \x01 (esc)
366 a
366 a
367 #if extra
367 #if extra
368 $ hg showcopies
368 $ hg showcopies
369
369
370 $ hg showcopies --config experimental.copies.read-from=compatibility
370 $ hg showcopies --config experimental.copies.read-from=compatibility
371 a -> k
371 a -> k
372 $ hg showcopies --config experimental.copies.read-from=filelog-only
372 $ hg showcopies --config experimental.copies.read-from=filelog-only
373 a -> k
373 a -> k
374 #else
374 #else
375 $ hg showcopies
375 $ hg showcopies
376 a -> k
376 a -> k
377 #endif
377 #endif
378
378
379 $ cd ..
379 $ cd ..
380
380
381 Test rebasing a commit with copy information
381 Test rebasing a commit with copy information
382
382
383 $ hg init rebase-rename
383 $ hg init rebase-rename
384 $ cd rebase-rename
384 $ cd rebase-rename
385 $ echo a > a
385 $ echo a > a
386 $ hg ci -Aqm 'add a'
386 $ hg ci -Aqm 'add a'
387 $ echo a2 > a
387 $ echo a2 > a
388 $ hg ci -m 'modify a'
388 $ hg ci -m 'modify a'
389 $ hg co -q 0
389 $ hg co -q 0
390 $ hg mv a b
390 $ hg mv a b
391 $ hg ci -qm 'rename a to b'
391 $ hg ci -qm 'rename a to b'
392 $ hg rebase -d 1 --config rebase.experimental.inmemory=yes
392 $ hg rebase -d 1 --config rebase.experimental.inmemory=yes
393 rebasing 2:* "rename a to b" (tip) (glob)
393 rebasing 2:* "rename a to b" (tip) (glob)
394 merging a and b to b
394 merging a and b to b
395 saved backup bundle to $TESTTMP/rebase-rename/.hg/strip-backup/*-*-rebase.hg (glob)
395 saved backup bundle to $TESTTMP/rebase-rename/.hg/strip-backup/*-*-rebase.hg (glob)
396 $ hg st --change . --copies
396 $ hg st --change . --copies
397 A b
397 A b
398 a
398 a
399 R a
399 R a
400 $ cd ..
400 $ cd ..
401
401
402 Test splitting a commit
402 Test splitting a commit
403
403
404 $ hg init split
404 $ hg init split
405 $ cd split
405 $ cd split
406 $ echo a > a
406 $ echo a > a
407 $ echo b > b
407 $ echo b > b
408 $ hg ci -Aqm 'add a and b'
408 $ hg ci -Aqm 'add a and b'
409 $ echo a2 > a
409 $ echo a2 > a
410 $ hg mv b c
410 $ hg mv b c
411 $ hg ci -m 'modify a, move b to c'
411 $ hg ci -m 'modify a, move b to c'
412 $ hg --config ui.interactive=yes split <<EOF
412 $ hg --config ui.interactive=yes split <<EOF
413 > y
413 > y
414 > y
414 > y
415 > n
415 > n
416 > y
416 > y
417 > EOF
417 > EOF
418 diff --git a/a b/a
418 diff --git a/a b/a
419 1 hunks, 1 lines changed
419 1 hunks, 1 lines changed
420 examine changes to 'a'?
420 examine changes to 'a'?
421 (enter ? for help) [Ynesfdaq?] y
421 (enter ? for help) [Ynesfdaq?] y
422
422
423 @@ -1,1 +1,1 @@
423 @@ -1,1 +1,1 @@
424 -a
424 -a
425 +a2
425 +a2
426 record this change to 'a'?
426 record this change to 'a'?
427 (enter ? for help) [Ynesfdaq?] y
427 (enter ? for help) [Ynesfdaq?] y
428
428
429 diff --git a/b b/c
429 diff --git a/b b/c
430 rename from b
430 rename from b
431 rename to c
431 rename to c
432 examine changes to 'b' and 'c'?
432 examine changes to 'b' and 'c'?
433 (enter ? for help) [Ynesfdaq?] n
433 (enter ? for help) [Ynesfdaq?] n
434
434
435 created new head
435 created new head
436 diff --git a/b b/c
436 diff --git a/b b/c
437 rename from b
437 rename from b
438 rename to c
438 rename to c
439 examine changes to 'b' and 'c'?
439 examine changes to 'b' and 'c'?
440 (enter ? for help) [Ynesfdaq?] y
440 (enter ? for help) [Ynesfdaq?] y
441
441
442 saved backup bundle to $TESTTMP/split/.hg/strip-backup/*-*-split.hg (glob)
442 saved backup bundle to $TESTTMP/split/.hg/strip-backup/*-*-split.hg (glob)
443 $ cd ..
443 $ cd ..
444
444
445 Test committing half a rename
445 Test committing half a rename
446
446
447 $ hg init partial
447 $ hg init partial
448 $ cd partial
448 $ cd partial
449 $ echo a > a
449 $ echo a > a
450 $ hg ci -Aqm 'add a'
450 $ hg ci -Aqm 'add a'
451 $ hg mv a b
451 $ hg mv a b
452 $ hg ci -m 'remove a' a
452 $ hg ci -m 'remove a' a
453
454 #if sidedata
455
456 Test upgrading/downgrading to sidedata storage
457 ==============================================
458
459 downgrading (keeping some sidedata)
460
461 $ hg debugformat -v
462 format-variant repo config default
463 fncache: yes yes yes
464 dotencode: yes yes yes
465 generaldelta: yes yes yes
466 sparserevlog: yes yes yes
467 sidedata: yes yes no
468 copies-sdc: yes yes no
469 plain-cl-delta: yes yes yes
470 compression: zlib zlib zlib
471 compression-level: default default default
472 $ hg debugsidedata -c -- 0
473 4 sidedata entries
474 entry-0010 size 0
475 entry-0011 size 0
476 entry-0012 size 1
477 entry-0013 size 0
478 $ hg debugsidedata -c -- 1
479 4 sidedata entries
480 entry-0010 size 0
481 entry-0011 size 0
482 entry-0012 size 0
483 entry-0013 size 1
484 $ hg debugsidedata -m -- 0
485 $ cat << EOF > .hg/hgrc
486 > [format]
487 > use-side-data = yes
488 > exp-use-copies-side-data-changeset = no
489 > EOF
490 $ hg debugupgraderepo --run --quiet --no-backup > /dev/null
491 $ hg debugformat -v
492 format-variant repo config default
493 fncache: yes yes yes
494 dotencode: yes yes yes
495 generaldelta: yes yes yes
496 sparserevlog: yes yes yes
497 sidedata: yes yes no
498 copies-sdc: no no no
499 plain-cl-delta: yes yes yes
500 compression: zlib zlib zlib
501 compression-level: default default default
502 $ hg debugsidedata -c -- 0
503 $ hg debugsidedata -c -- 1
504 $ hg debugsidedata -m -- 0
505
506 upgrading
507
508 $ cat << EOF > .hg/hgrc
509 > [format]
510 > exp-use-copies-side-data-changeset = yes
511 > EOF
512 $ hg debugupgraderepo --run --quiet --no-backup > /dev/null
513 $ hg debugformat -v
514 format-variant repo config default
515 fncache: yes yes yes
516 dotencode: yes yes yes
517 generaldelta: yes yes yes
518 sparserevlog: yes yes yes
519 sidedata: yes yes no
520 copies-sdc: yes yes no
521 plain-cl-delta: yes yes yes
522 compression: zlib zlib zlib
523 compression-level: default default default
524 $ hg debugsidedata -c -- 0
525 4 sidedata entries
526 entry-0010 size 0
527 entry-0011 size 0
528 entry-0012 size 1
529 entry-0013 size 0
530 $ hg debugsidedata -c -- 1
531 4 sidedata entries
532 entry-0010 size 0
533 entry-0011 size 0
534 entry-0012 size 0
535 entry-0013 size 1
536 $ hg debugsidedata -m -- 0
537
538 #endif
539
453 $ cd ..
540 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now