##// END OF EJS Templates
narrow: fix commits of empty files...
Valentin Gatien-Baron -
r48770:5b9de38a stable
parent child Browse files
Show More
@@ -1,503 +1,504 b''
1 # commit.py - fonction to perform commit
1 # commit.py - fonction to perform commit
2 #
2 #
3 # This software may be used and distributed according to the terms of the
3 # This software may be used and distributed according to the terms of the
4 # GNU General Public License version 2 or any later version.
4 # GNU General Public License version 2 or any later version.
5
5
6 from __future__ import absolute_import
6 from __future__ import absolute_import
7
7
8 import errno
8 import errno
9
9
10 from .i18n import _
10 from .i18n import _
11 from .node import (
11 from .node import (
12 hex,
12 hex,
13 nullrev,
13 nullrev,
14 )
14 )
15
15
16 from . import (
16 from . import (
17 context,
17 context,
18 mergestate,
18 mergestate,
19 metadata,
19 metadata,
20 phases,
20 phases,
21 scmutil,
21 scmutil,
22 subrepoutil,
22 subrepoutil,
23 )
23 )
24
24
25
25
26 def _write_copy_meta(repo):
26 def _write_copy_meta(repo):
27 """return a (changelog, filelog) boolean tuple
27 """return a (changelog, filelog) boolean tuple
28
28
29 changelog: copy related information should be stored in the changeset
29 changelog: copy related information should be stored in the changeset
30 filelof: copy related information should be written in the file revision
30 filelof: copy related information should be written in the file revision
31 """
31 """
32 if repo.filecopiesmode == b'changeset-sidedata':
32 if repo.filecopiesmode == b'changeset-sidedata':
33 writechangesetcopy = True
33 writechangesetcopy = True
34 writefilecopymeta = True
34 writefilecopymeta = True
35 else:
35 else:
36 writecopiesto = repo.ui.config(b'experimental', b'copies.write-to')
36 writecopiesto = repo.ui.config(b'experimental', b'copies.write-to')
37 writefilecopymeta = writecopiesto != b'changeset-only'
37 writefilecopymeta = writecopiesto != b'changeset-only'
38 writechangesetcopy = writecopiesto in (
38 writechangesetcopy = writecopiesto in (
39 b'changeset-only',
39 b'changeset-only',
40 b'compatibility',
40 b'compatibility',
41 )
41 )
42 return writechangesetcopy, writefilecopymeta
42 return writechangesetcopy, writefilecopymeta
43
43
44
44
45 def commitctx(repo, ctx, error=False, origctx=None):
45 def commitctx(repo, ctx, error=False, origctx=None):
46 """Add a new revision to the target repository.
46 """Add a new revision to the target repository.
47 Revision information is passed via the context argument.
47 Revision information is passed via the context argument.
48
48
49 ctx.files() should list all files involved in this commit, i.e.
49 ctx.files() should list all files involved in this commit, i.e.
50 modified/added/removed files. On merge, it may be wider than the
50 modified/added/removed files. On merge, it may be wider than the
51 ctx.files() to be committed, since any file nodes derived directly
51 ctx.files() to be committed, since any file nodes derived directly
52 from p1 or p2 are excluded from the committed ctx.files().
52 from p1 or p2 are excluded from the committed ctx.files().
53
53
54 origctx is for convert to work around the problem that bug
54 origctx is for convert to work around the problem that bug
55 fixes to the files list in changesets change hashes. For
55 fixes to the files list in changesets change hashes. For
56 convert to be the identity, it can pass an origctx and this
56 convert to be the identity, it can pass an origctx and this
57 function will use the same files list when it makes sense to
57 function will use the same files list when it makes sense to
58 do so.
58 do so.
59 """
59 """
60 repo = repo.unfiltered()
60 repo = repo.unfiltered()
61
61
62 p1, p2 = ctx.p1(), ctx.p2()
62 p1, p2 = ctx.p1(), ctx.p2()
63 user = ctx.user()
63 user = ctx.user()
64
64
65 with repo.lock(), repo.transaction(b"commit") as tr:
65 with repo.lock(), repo.transaction(b"commit") as tr:
66 mn, files = _prepare_files(tr, ctx, error=error, origctx=origctx)
66 mn, files = _prepare_files(tr, ctx, error=error, origctx=origctx)
67
67
68 extra = ctx.extra().copy()
68 extra = ctx.extra().copy()
69
69
70 if extra is not None:
70 if extra is not None:
71 for name in (
71 for name in (
72 b'p1copies',
72 b'p1copies',
73 b'p2copies',
73 b'p2copies',
74 b'filesadded',
74 b'filesadded',
75 b'filesremoved',
75 b'filesremoved',
76 ):
76 ):
77 extra.pop(name, None)
77 extra.pop(name, None)
78 if repo.changelog._copiesstorage == b'extra':
78 if repo.changelog._copiesstorage == b'extra':
79 extra = _extra_with_copies(repo, extra, files)
79 extra = _extra_with_copies(repo, extra, files)
80
80
81 # save the tip to check whether we actually committed anything
81 # save the tip to check whether we actually committed anything
82 oldtip = repo.changelog.tiprev()
82 oldtip = repo.changelog.tiprev()
83
83
84 # update changelog
84 # update changelog
85 repo.ui.note(_(b"committing changelog\n"))
85 repo.ui.note(_(b"committing changelog\n"))
86 repo.changelog.delayupdate(tr)
86 repo.changelog.delayupdate(tr)
87 n = repo.changelog.add(
87 n = repo.changelog.add(
88 mn,
88 mn,
89 files,
89 files,
90 ctx.description(),
90 ctx.description(),
91 tr,
91 tr,
92 p1.node(),
92 p1.node(),
93 p2.node(),
93 p2.node(),
94 user,
94 user,
95 ctx.date(),
95 ctx.date(),
96 extra,
96 extra,
97 )
97 )
98 rev = repo[n].rev()
98 rev = repo[n].rev()
99 if oldtip != repo.changelog.tiprev():
99 if oldtip != repo.changelog.tiprev():
100 repo.register_changeset(rev, repo.changelog.changelogrevision(rev))
100 repo.register_changeset(rev, repo.changelog.changelogrevision(rev))
101
101
102 xp1, xp2 = p1.hex(), p2 and p2.hex() or b''
102 xp1, xp2 = p1.hex(), p2 and p2.hex() or b''
103 repo.hook(
103 repo.hook(
104 b'pretxncommit',
104 b'pretxncommit',
105 throw=True,
105 throw=True,
106 node=hex(n),
106 node=hex(n),
107 parent1=xp1,
107 parent1=xp1,
108 parent2=xp2,
108 parent2=xp2,
109 )
109 )
110 # set the new commit is proper phase
110 # set the new commit is proper phase
111 targetphase = subrepoutil.newcommitphase(repo.ui, ctx)
111 targetphase = subrepoutil.newcommitphase(repo.ui, ctx)
112
112
113 # prevent unmarking changesets as public on recommit
113 # prevent unmarking changesets as public on recommit
114 waspublic = oldtip == repo.changelog.tiprev() and not repo[rev].phase()
114 waspublic = oldtip == repo.changelog.tiprev() and not repo[rev].phase()
115
115
116 if targetphase and not waspublic:
116 if targetphase and not waspublic:
117 # retract boundary do not alter parent changeset.
117 # retract boundary do not alter parent changeset.
118 # if a parent have higher the resulting phase will
118 # if a parent have higher the resulting phase will
119 # be compliant anyway
119 # be compliant anyway
120 #
120 #
121 # if minimal phase was 0 we don't need to retract anything
121 # if minimal phase was 0 we don't need to retract anything
122 phases.registernew(repo, tr, targetphase, [rev])
122 phases.registernew(repo, tr, targetphase, [rev])
123 return n
123 return n
124
124
125
125
126 def _prepare_files(tr, ctx, error=False, origctx=None):
126 def _prepare_files(tr, ctx, error=False, origctx=None):
127 repo = ctx.repo()
127 repo = ctx.repo()
128 p1 = ctx.p1()
128 p1 = ctx.p1()
129
129
130 writechangesetcopy, writefilecopymeta = _write_copy_meta(repo)
130 writechangesetcopy, writefilecopymeta = _write_copy_meta(repo)
131 files = metadata.ChangingFiles()
131 files = metadata.ChangingFiles()
132 ms = mergestate.mergestate.read(repo)
132 ms = mergestate.mergestate.read(repo)
133 salvaged = _get_salvaged(repo, ms, ctx)
133 salvaged = _get_salvaged(repo, ms, ctx)
134 for s in salvaged:
134 for s in salvaged:
135 files.mark_salvaged(s)
135 files.mark_salvaged(s)
136
136
137 if ctx.manifestnode():
137 if ctx.manifestnode():
138 # reuse an existing manifest revision
138 # reuse an existing manifest revision
139 repo.ui.debug(b'reusing known manifest\n')
139 repo.ui.debug(b'reusing known manifest\n')
140 mn = ctx.manifestnode()
140 mn = ctx.manifestnode()
141 files.update_touched(ctx.files())
141 files.update_touched(ctx.files())
142 if writechangesetcopy:
142 if writechangesetcopy:
143 files.update_added(ctx.filesadded())
143 files.update_added(ctx.filesadded())
144 files.update_removed(ctx.filesremoved())
144 files.update_removed(ctx.filesremoved())
145 elif not ctx.files():
145 elif not ctx.files():
146 repo.ui.debug(b'reusing manifest from p1 (no file change)\n')
146 repo.ui.debug(b'reusing manifest from p1 (no file change)\n')
147 mn = p1.manifestnode()
147 mn = p1.manifestnode()
148 else:
148 else:
149 mn = _process_files(tr, ctx, ms, files, error=error)
149 mn = _process_files(tr, ctx, ms, files, error=error)
150
150
151 if origctx and origctx.manifestnode() == mn:
151 if origctx and origctx.manifestnode() == mn:
152 origfiles = origctx.files()
152 origfiles = origctx.files()
153 assert files.touched.issubset(origfiles)
153 assert files.touched.issubset(origfiles)
154 files.update_touched(origfiles)
154 files.update_touched(origfiles)
155
155
156 if writechangesetcopy:
156 if writechangesetcopy:
157 files.update_copies_from_p1(ctx.p1copies())
157 files.update_copies_from_p1(ctx.p1copies())
158 files.update_copies_from_p2(ctx.p2copies())
158 files.update_copies_from_p2(ctx.p2copies())
159
159
160 return mn, files
160 return mn, files
161
161
162
162
163 def _get_salvaged(repo, ms, ctx):
163 def _get_salvaged(repo, ms, ctx):
164 """returns a list of salvaged files
164 """returns a list of salvaged files
165
165
166 returns empty list if config option which process salvaged files are
166 returns empty list if config option which process salvaged files are
167 not enabled"""
167 not enabled"""
168 salvaged = []
168 salvaged = []
169 copy_sd = repo.filecopiesmode == b'changeset-sidedata'
169 copy_sd = repo.filecopiesmode == b'changeset-sidedata'
170 if copy_sd and len(ctx.parents()) > 1:
170 if copy_sd and len(ctx.parents()) > 1:
171 if ms.active():
171 if ms.active():
172 for fname in sorted(ms.allextras().keys()):
172 for fname in sorted(ms.allextras().keys()):
173 might_removed = ms.extras(fname).get(b'merge-removal-candidate')
173 might_removed = ms.extras(fname).get(b'merge-removal-candidate')
174 if might_removed == b'yes':
174 if might_removed == b'yes':
175 if fname in ctx:
175 if fname in ctx:
176 salvaged.append(fname)
176 salvaged.append(fname)
177 return salvaged
177 return salvaged
178
178
179
179
180 def _process_files(tr, ctx, ms, files, error=False):
180 def _process_files(tr, ctx, ms, files, error=False):
181 repo = ctx.repo()
181 repo = ctx.repo()
182 p1 = ctx.p1()
182 p1 = ctx.p1()
183 p2 = ctx.p2()
183 p2 = ctx.p2()
184
184
185 writechangesetcopy, writefilecopymeta = _write_copy_meta(repo)
185 writechangesetcopy, writefilecopymeta = _write_copy_meta(repo)
186
186
187 m1ctx = p1.manifestctx()
187 m1ctx = p1.manifestctx()
188 m2ctx = p2.manifestctx()
188 m2ctx = p2.manifestctx()
189 mctx = m1ctx.copy()
189 mctx = m1ctx.copy()
190
190
191 m = mctx.read()
191 m = mctx.read()
192 m1 = m1ctx.read()
192 m1 = m1ctx.read()
193 m2 = m2ctx.read()
193 m2 = m2ctx.read()
194
194
195 # check in files
195 # check in files
196 added = []
196 added = []
197 removed = list(ctx.removed())
197 removed = list(ctx.removed())
198 linkrev = len(repo)
198 linkrev = len(repo)
199 repo.ui.note(_(b"committing files:\n"))
199 repo.ui.note(_(b"committing files:\n"))
200 uipathfn = scmutil.getuipathfn(repo)
200 uipathfn = scmutil.getuipathfn(repo)
201 for f in sorted(ctx.modified() + ctx.added()):
201 for f in sorted(ctx.modified() + ctx.added()):
202 repo.ui.note(uipathfn(f) + b"\n")
202 repo.ui.note(uipathfn(f) + b"\n")
203 try:
203 try:
204 fctx = ctx[f]
204 fctx = ctx[f]
205 if fctx is None:
205 if fctx is None:
206 removed.append(f)
206 removed.append(f)
207 else:
207 else:
208 added.append(f)
208 added.append(f)
209 m[f], is_touched = _filecommit(
209 m[f], is_touched = _filecommit(
210 repo, fctx, m1, m2, linkrev, tr, writefilecopymeta, ms
210 repo, fctx, m1, m2, linkrev, tr, writefilecopymeta, ms
211 )
211 )
212 if is_touched:
212 if is_touched:
213 if is_touched == 'added':
213 if is_touched == 'added':
214 files.mark_added(f)
214 files.mark_added(f)
215 elif is_touched == 'merged':
215 elif is_touched == 'merged':
216 files.mark_merged(f)
216 files.mark_merged(f)
217 else:
217 else:
218 files.mark_touched(f)
218 files.mark_touched(f)
219 m.setflag(f, fctx.flags())
219 m.setflag(f, fctx.flags())
220 except OSError:
220 except OSError:
221 repo.ui.warn(_(b"trouble committing %s!\n") % uipathfn(f))
221 repo.ui.warn(_(b"trouble committing %s!\n") % uipathfn(f))
222 raise
222 raise
223 except IOError as inst:
223 except IOError as inst:
224 errcode = getattr(inst, 'errno', errno.ENOENT)
224 errcode = getattr(inst, 'errno', errno.ENOENT)
225 if error or errcode and errcode != errno.ENOENT:
225 if error or errcode and errcode != errno.ENOENT:
226 repo.ui.warn(_(b"trouble committing %s!\n") % uipathfn(f))
226 repo.ui.warn(_(b"trouble committing %s!\n") % uipathfn(f))
227 raise
227 raise
228
228
229 # update manifest
229 # update manifest
230 removed = [f for f in removed if f in m1 or f in m2]
230 removed = [f for f in removed if f in m1 or f in m2]
231 drop = sorted([f for f in removed if f in m])
231 drop = sorted([f for f in removed if f in m])
232 for f in drop:
232 for f in drop:
233 del m[f]
233 del m[f]
234 if p2.rev() == nullrev:
234 if p2.rev() == nullrev:
235 files.update_removed(removed)
235 files.update_removed(removed)
236 else:
236 else:
237 rf = metadata.get_removal_filter(ctx, (p1, p2, m1, m2))
237 rf = metadata.get_removal_filter(ctx, (p1, p2, m1, m2))
238 for f in removed:
238 for f in removed:
239 if not rf(f):
239 if not rf(f):
240 files.mark_removed(f)
240 files.mark_removed(f)
241
241
242 mn = _commit_manifest(tr, linkrev, ctx, mctx, m, files.touched, added, drop)
242 mn = _commit_manifest(tr, linkrev, ctx, mctx, m, files.touched, added, drop)
243
243
244 return mn
244 return mn
245
245
246
246
247 def _filecommit(
247 def _filecommit(
248 repo,
248 repo,
249 fctx,
249 fctx,
250 manifest1,
250 manifest1,
251 manifest2,
251 manifest2,
252 linkrev,
252 linkrev,
253 tr,
253 tr,
254 includecopymeta,
254 includecopymeta,
255 ms,
255 ms,
256 ):
256 ):
257 """
257 """
258 commit an individual file as part of a larger transaction
258 commit an individual file as part of a larger transaction
259
259
260 input:
260 input:
261
261
262 fctx: a file context with the content we are trying to commit
262 fctx: a file context with the content we are trying to commit
263 manifest1: manifest of changeset first parent
263 manifest1: manifest of changeset first parent
264 manifest2: manifest of changeset second parent
264 manifest2: manifest of changeset second parent
265 linkrev: revision number of the changeset being created
265 linkrev: revision number of the changeset being created
266 tr: current transation
266 tr: current transation
267 includecopymeta: boolean, set to False to skip storing the copy data
267 includecopymeta: boolean, set to False to skip storing the copy data
268 (only used by the Google specific feature of using
268 (only used by the Google specific feature of using
269 changeset extra as copy source of truth).
269 changeset extra as copy source of truth).
270 ms: mergestate object
270 ms: mergestate object
271
271
272 output: (filenode, touched)
272 output: (filenode, touched)
273
273
274 filenode: the filenode that should be used by this changeset
274 filenode: the filenode that should be used by this changeset
275 touched: one of: None (mean untouched), 'added' or 'modified'
275 touched: one of: None (mean untouched), 'added' or 'modified'
276 """
276 """
277
277
278 fname = fctx.path()
278 fname = fctx.path()
279 fparent1 = manifest1.get(fname, repo.nullid)
279 fparent1 = manifest1.get(fname, repo.nullid)
280 fparent2 = manifest2.get(fname, repo.nullid)
280 fparent2 = manifest2.get(fname, repo.nullid)
281 touched = None
281 touched = None
282 if fparent1 == fparent2 == repo.nullid:
282 if fparent1 == fparent2 == repo.nullid:
283 touched = 'added'
283 touched = 'added'
284
284
285 if isinstance(fctx, context.filectx):
285 if isinstance(fctx, context.filectx):
286 # This block fast path most comparisons which are usually done. It
286 # This block fast path most comparisons which are usually done. It
287 # assumes that bare filectx is used and no merge happened, hence no
287 # assumes that bare filectx is used and no merge happened, hence no
288 # need to create a new file revision in this case.
288 # need to create a new file revision in this case.
289 node = fctx.filenode()
289 node = fctx.filenode()
290 if node in [fparent1, fparent2]:
290 if node in [fparent1, fparent2]:
291 repo.ui.debug(b'reusing %s filelog entry\n' % fname)
291 repo.ui.debug(b'reusing %s filelog entry\n' % fname)
292 if (
292 if (
293 fparent1 != repo.nullid
293 fparent1 != repo.nullid
294 and manifest1.flags(fname) != fctx.flags()
294 and manifest1.flags(fname) != fctx.flags()
295 ) or (
295 ) or (
296 fparent2 != repo.nullid
296 fparent2 != repo.nullid
297 and manifest2.flags(fname) != fctx.flags()
297 and manifest2.flags(fname) != fctx.flags()
298 ):
298 ):
299 touched = 'modified'
299 touched = 'modified'
300 return node, touched
300 return node, touched
301
301
302 flog = repo.file(fname)
302 flog = repo.file(fname)
303 meta = {}
303 meta = {}
304 cfname = fctx.copysource()
304 cfname = fctx.copysource()
305 fnode = None
305 fnode = None
306
306
307 if cfname and cfname != fname:
307 if cfname and cfname != fname:
308 # Mark the new revision of this file as a copy of another
308 # Mark the new revision of this file as a copy of another
309 # file. This copy data will effectively act as a parent
309 # file. This copy data will effectively act as a parent
310 # of this new revision. If this is a merge, the first
310 # of this new revision. If this is a merge, the first
311 # parent will be the nullid (meaning "look up the copy data")
311 # parent will be the nullid (meaning "look up the copy data")
312 # and the second one will be the other parent. For example:
312 # and the second one will be the other parent. For example:
313 #
313 #
314 # 0 --- 1 --- 3 rev1 changes file foo
314 # 0 --- 1 --- 3 rev1 changes file foo
315 # \ / rev2 renames foo to bar and changes it
315 # \ / rev2 renames foo to bar and changes it
316 # \- 2 -/ rev3 should have bar with all changes and
316 # \- 2 -/ rev3 should have bar with all changes and
317 # should record that bar descends from
317 # should record that bar descends from
318 # bar in rev2 and foo in rev1
318 # bar in rev2 and foo in rev1
319 #
319 #
320 # this allows this merge to succeed:
320 # this allows this merge to succeed:
321 #
321 #
322 # 0 --- 1 --- 3 rev4 reverts the content change from rev2
322 # 0 --- 1 --- 3 rev4 reverts the content change from rev2
323 # \ / merging rev3 and rev4 should use bar@rev2
323 # \ / merging rev3 and rev4 should use bar@rev2
324 # \- 2 --- 4 as the merge base
324 # \- 2 --- 4 as the merge base
325 #
325 #
326
326
327 cnode = manifest1.get(cfname)
327 cnode = manifest1.get(cfname)
328 newfparent = fparent2
328 newfparent = fparent2
329
329
330 if manifest2: # branch merge
330 if manifest2: # branch merge
331 if (
331 if (
332 fparent2 == repo.nullid or cnode is None
332 fparent2 == repo.nullid or cnode is None
333 ): # copied on remote side
333 ): # copied on remote side
334 if cfname in manifest2:
334 if cfname in manifest2:
335 cnode = manifest2[cfname]
335 cnode = manifest2[cfname]
336 newfparent = fparent1
336 newfparent = fparent1
337
337
338 # Here, we used to search backwards through history to try to find
338 # Here, we used to search backwards through history to try to find
339 # where the file copy came from if the source of a copy was not in
339 # where the file copy came from if the source of a copy was not in
340 # the parent directory. However, this doesn't actually make sense to
340 # the parent directory. However, this doesn't actually make sense to
341 # do (what does a copy from something not in your working copy even
341 # do (what does a copy from something not in your working copy even
342 # mean?) and it causes bugs (eg, issue4476). Instead, we will warn
342 # mean?) and it causes bugs (eg, issue4476). Instead, we will warn
343 # the user that copy information was dropped, so if they didn't
343 # the user that copy information was dropped, so if they didn't
344 # expect this outcome it can be fixed, but this is the correct
344 # expect this outcome it can be fixed, but this is the correct
345 # behavior in this circumstance.
345 # behavior in this circumstance.
346
346
347 if cnode:
347 if cnode:
348 repo.ui.debug(b" %s: copy %s:%s\n" % (fname, cfname, hex(cnode)))
348 repo.ui.debug(b" %s: copy %s:%s\n" % (fname, cfname, hex(cnode)))
349 if includecopymeta:
349 if includecopymeta:
350 meta[b"copy"] = cfname
350 meta[b"copy"] = cfname
351 meta[b"copyrev"] = hex(cnode)
351 meta[b"copyrev"] = hex(cnode)
352 fparent1, fparent2 = repo.nullid, newfparent
352 fparent1, fparent2 = repo.nullid, newfparent
353 else:
353 else:
354 repo.ui.warn(
354 repo.ui.warn(
355 _(
355 _(
356 b"warning: can't find ancestor for '%s' "
356 b"warning: can't find ancestor for '%s' "
357 b"copied from '%s'!\n"
357 b"copied from '%s'!\n"
358 )
358 )
359 % (fname, cfname)
359 % (fname, cfname)
360 )
360 )
361
361
362 elif fparent1 == repo.nullid:
362 elif fparent1 == repo.nullid:
363 fparent1, fparent2 = fparent2, repo.nullid
363 fparent1, fparent2 = fparent2, repo.nullid
364 elif fparent2 != repo.nullid:
364 elif fparent2 != repo.nullid:
365 if ms.active() and ms.extras(fname).get(b'filenode-source') == b'other':
365 if ms.active() and ms.extras(fname).get(b'filenode-source') == b'other':
366 fparent1, fparent2 = fparent2, repo.nullid
366 fparent1, fparent2 = fparent2, repo.nullid
367 elif ms.active() and ms.extras(fname).get(b'merged') != b'yes':
367 elif ms.active() and ms.extras(fname).get(b'merged') != b'yes':
368 fparent1, fparent2 = fparent1, repo.nullid
368 fparent1, fparent2 = fparent1, repo.nullid
369 # is one parent an ancestor of the other?
369 # is one parent an ancestor of the other?
370 else:
370 else:
371 fparentancestors = flog.commonancestorsheads(fparent1, fparent2)
371 fparentancestors = flog.commonancestorsheads(fparent1, fparent2)
372 if fparent1 in fparentancestors:
372 if fparent1 in fparentancestors:
373 fparent1, fparent2 = fparent2, repo.nullid
373 fparent1, fparent2 = fparent2, repo.nullid
374 elif fparent2 in fparentancestors:
374 elif fparent2 in fparentancestors:
375 fparent2 = repo.nullid
375 fparent2 = repo.nullid
376
376
377 force_new_node = False
377 force_new_node = False
378 # The file might have been deleted by merge code and user explicitly choose
378 # The file might have been deleted by merge code and user explicitly choose
379 # to revert the file and keep it. The other case can be where there is
379 # to revert the file and keep it. The other case can be where there is
380 # change-delete or delete-change conflict and user explicitly choose to keep
380 # change-delete or delete-change conflict and user explicitly choose to keep
381 # the file. The goal is to create a new filenode for users explicit choices
381 # the file. The goal is to create a new filenode for users explicit choices
382 if (
382 if (
383 repo.ui.configbool(b'experimental', b'merge-track-salvaged')
383 repo.ui.configbool(b'experimental', b'merge-track-salvaged')
384 and ms.active()
384 and ms.active()
385 and ms.extras(fname).get(b'merge-removal-candidate') == b'yes'
385 and ms.extras(fname).get(b'merge-removal-candidate') == b'yes'
386 ):
386 ):
387 force_new_node = True
387 force_new_node = True
388 # is the file changed?
388 # is the file changed?
389 text = fctx.data()
389 text = fctx.data()
390 if (
390 if (
391 fparent2 != repo.nullid
391 fparent2 != repo.nullid
392 or fparent1 == repo.nullid
392 or meta
393 or meta
393 or flog.cmp(fparent1, text)
394 or flog.cmp(fparent1, text)
394 or force_new_node
395 or force_new_node
395 ):
396 ):
396 if touched is None: # do not overwrite added
397 if touched is None: # do not overwrite added
397 if fparent2 == repo.nullid:
398 if fparent2 == repo.nullid:
398 touched = 'modified'
399 touched = 'modified'
399 else:
400 else:
400 touched = 'merged'
401 touched = 'merged'
401 fnode = flog.add(text, meta, tr, linkrev, fparent1, fparent2)
402 fnode = flog.add(text, meta, tr, linkrev, fparent1, fparent2)
402 # are just the flags changed during merge?
403 # are just the flags changed during merge?
403 elif fname in manifest1 and manifest1.flags(fname) != fctx.flags():
404 elif fname in manifest1 and manifest1.flags(fname) != fctx.flags():
404 touched = 'modified'
405 touched = 'modified'
405 fnode = fparent1
406 fnode = fparent1
406 else:
407 else:
407 fnode = fparent1
408 fnode = fparent1
408 return fnode, touched
409 return fnode, touched
409
410
410
411
411 def _commit_manifest(tr, linkrev, ctx, mctx, manifest, files, added, drop):
412 def _commit_manifest(tr, linkrev, ctx, mctx, manifest, files, added, drop):
412 """make a new manifest entry (or reuse a new one)
413 """make a new manifest entry (or reuse a new one)
413
414
414 given an initialised manifest context and precomputed list of
415 given an initialised manifest context and precomputed list of
415 - files: files affected by the commit
416 - files: files affected by the commit
416 - added: new entries in the manifest
417 - added: new entries in the manifest
417 - drop: entries present in parents but absent of this one
418 - drop: entries present in parents but absent of this one
418
419
419 Create a new manifest revision, reuse existing ones if possible.
420 Create a new manifest revision, reuse existing ones if possible.
420
421
421 Return the nodeid of the manifest revision.
422 Return the nodeid of the manifest revision.
422 """
423 """
423 repo = ctx.repo()
424 repo = ctx.repo()
424
425
425 md = None
426 md = None
426
427
427 # all this is cached, so it is find to get them all from the ctx.
428 # all this is cached, so it is find to get them all from the ctx.
428 p1 = ctx.p1()
429 p1 = ctx.p1()
429 p2 = ctx.p2()
430 p2 = ctx.p2()
430 m1ctx = p1.manifestctx()
431 m1ctx = p1.manifestctx()
431
432
432 m1 = m1ctx.read()
433 m1 = m1ctx.read()
433
434
434 if not files:
435 if not files:
435 # if no "files" actually changed in terms of the changelog,
436 # if no "files" actually changed in terms of the changelog,
436 # try hard to detect unmodified manifest entry so that the
437 # try hard to detect unmodified manifest entry so that the
437 # exact same commit can be reproduced later on convert.
438 # exact same commit can be reproduced later on convert.
438 md = m1.diff(manifest, scmutil.matchfiles(repo, ctx.files()))
439 md = m1.diff(manifest, scmutil.matchfiles(repo, ctx.files()))
439 if not files and md:
440 if not files and md:
440 repo.ui.debug(
441 repo.ui.debug(
441 b'not reusing manifest (no file change in '
442 b'not reusing manifest (no file change in '
442 b'changelog, but manifest differs)\n'
443 b'changelog, but manifest differs)\n'
443 )
444 )
444 if files or md:
445 if files or md:
445 repo.ui.note(_(b"committing manifest\n"))
446 repo.ui.note(_(b"committing manifest\n"))
446 # we're using narrowmatch here since it's already applied at
447 # we're using narrowmatch here since it's already applied at
447 # other stages (such as dirstate.walk), so we're already
448 # other stages (such as dirstate.walk), so we're already
448 # ignoring things outside of narrowspec in most cases. The
449 # ignoring things outside of narrowspec in most cases. The
449 # one case where we might have files outside the narrowspec
450 # one case where we might have files outside the narrowspec
450 # at this point is merges, and we already error out in the
451 # at this point is merges, and we already error out in the
451 # case where the merge has files outside of the narrowspec,
452 # case where the merge has files outside of the narrowspec,
452 # so this is safe.
453 # so this is safe.
453 mn = mctx.write(
454 mn = mctx.write(
454 tr,
455 tr,
455 linkrev,
456 linkrev,
456 p1.manifestnode(),
457 p1.manifestnode(),
457 p2.manifestnode(),
458 p2.manifestnode(),
458 added,
459 added,
459 drop,
460 drop,
460 match=repo.narrowmatch(),
461 match=repo.narrowmatch(),
461 )
462 )
462 else:
463 else:
463 repo.ui.debug(
464 repo.ui.debug(
464 b'reusing manifest from p1 (listed files ' b'actually unchanged)\n'
465 b'reusing manifest from p1 (listed files ' b'actually unchanged)\n'
465 )
466 )
466 mn = p1.manifestnode()
467 mn = p1.manifestnode()
467
468
468 return mn
469 return mn
469
470
470
471
471 def _extra_with_copies(repo, extra, files):
472 def _extra_with_copies(repo, extra, files):
472 """encode copy information into a `extra` dictionnary"""
473 """encode copy information into a `extra` dictionnary"""
473 p1copies = files.copied_from_p1
474 p1copies = files.copied_from_p1
474 p2copies = files.copied_from_p2
475 p2copies = files.copied_from_p2
475 filesadded = files.added
476 filesadded = files.added
476 filesremoved = files.removed
477 filesremoved = files.removed
477 files = sorted(files.touched)
478 files = sorted(files.touched)
478 if not _write_copy_meta(repo)[1]:
479 if not _write_copy_meta(repo)[1]:
479 # If writing only to changeset extras, use None to indicate that
480 # If writing only to changeset extras, use None to indicate that
480 # no entry should be written. If writing to both, write an empty
481 # no entry should be written. If writing to both, write an empty
481 # entry to prevent the reader from falling back to reading
482 # entry to prevent the reader from falling back to reading
482 # filelogs.
483 # filelogs.
483 p1copies = p1copies or None
484 p1copies = p1copies or None
484 p2copies = p2copies or None
485 p2copies = p2copies or None
485 filesadded = filesadded or None
486 filesadded = filesadded or None
486 filesremoved = filesremoved or None
487 filesremoved = filesremoved or None
487
488
488 extrasentries = p1copies, p2copies, filesadded, filesremoved
489 extrasentries = p1copies, p2copies, filesadded, filesremoved
489 if extra is None and any(x is not None for x in extrasentries):
490 if extra is None and any(x is not None for x in extrasentries):
490 extra = {}
491 extra = {}
491 if p1copies is not None:
492 if p1copies is not None:
492 p1copies = metadata.encodecopies(files, p1copies)
493 p1copies = metadata.encodecopies(files, p1copies)
493 extra[b'p1copies'] = p1copies
494 extra[b'p1copies'] = p1copies
494 if p2copies is not None:
495 if p2copies is not None:
495 p2copies = metadata.encodecopies(files, p2copies)
496 p2copies = metadata.encodecopies(files, p2copies)
496 extra[b'p2copies'] = p2copies
497 extra[b'p2copies'] = p2copies
497 if filesadded is not None:
498 if filesadded is not None:
498 filesadded = metadata.encodefileindices(files, filesadded)
499 filesadded = metadata.encodefileindices(files, filesadded)
499 extra[b'filesadded'] = filesadded
500 extra[b'filesadded'] = filesadded
500 if filesremoved is not None:
501 if filesremoved is not None:
501 filesremoved = metadata.encodefileindices(files, filesremoved)
502 filesremoved = metadata.encodefileindices(files, filesremoved)
502 extra[b'filesremoved'] = filesremoved
503 extra[b'filesremoved'] = filesremoved
503 return extra
504 return extra
@@ -1,118 +1,111 b''
1 #testcases flat tree
1 #testcases flat tree
2
2
3 $ . "$TESTDIR/narrow-library.sh"
3 $ . "$TESTDIR/narrow-library.sh"
4
4
5 #if tree
5 #if tree
6 $ cat << EOF >> $HGRCPATH
6 $ cat << EOF >> $HGRCPATH
7 > [experimental]
7 > [experimental]
8 > treemanifest = 1
8 > treemanifest = 1
9 > EOF
9 > EOF
10 #endif
10 #endif
11
11
12 create full repo
12 create full repo
13
13
14 $ hg init master
14 $ hg init master
15 $ cd master
15 $ cd master
16
16
17 $ mkdir inside
17 $ mkdir inside
18 $ echo inside > inside/f1
18 $ echo inside > inside/f1
19 $ mkdir outside
19 $ mkdir outside
20 $ echo outside > outside/f1
20 $ echo outside > outside/f1
21 $ hg ci -Aqm 'initial'
21 $ hg ci -Aqm 'initial'
22
22
23 $ echo modified > inside/f1
23 $ echo modified > inside/f1
24 $ hg ci -qm 'modify inside'
24 $ hg ci -qm 'modify inside'
25
25
26 $ echo modified > outside/f1
26 $ echo modified > outside/f1
27 $ hg ci -qm 'modify outside'
27 $ hg ci -qm 'modify outside'
28
28
29 $ cd ..
29 $ cd ..
30
30
31 (The lfs extension does nothing here, but this test ensures that its hook that
31 (The lfs extension does nothing here, but this test ensures that its hook that
32 determines whether to add the lfs requirement, respects the narrow boundaries.)
32 determines whether to add the lfs requirement, respects the narrow boundaries.)
33
33
34 $ hg --config extensions.lfs= clone --narrow ssh://user@dummy/master narrow \
34 $ hg --config extensions.lfs= clone --narrow ssh://user@dummy/master narrow \
35 > --include inside
35 > --include inside
36 requesting all changes
36 requesting all changes
37 adding changesets
37 adding changesets
38 adding manifests
38 adding manifests
39 adding file changes
39 adding file changes
40 added 3 changesets with 2 changes to 1 files
40 added 3 changesets with 2 changes to 1 files
41 new changesets *:* (glob)
41 new changesets *:* (glob)
42 updating to branch default
42 updating to branch default
43 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
43 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
44 $ cd narrow
44 $ cd narrow
45
45
46 $ hg update -q 0
46 $ hg update -q 0
47
47
48 Can not modify dirstate outside
48 Can not modify dirstate outside
49
49
50 $ mkdir outside
50 $ mkdir outside
51 $ touch outside/f1
51 $ touch outside/f1
52 $ hg debugwalk -v -I 'relglob:f1'
52 $ hg debugwalk -v -I 'relglob:f1'
53 * matcher:
53 * matcher:
54 <includematcher includes='(?:|.*/)f1(?:/|$)'>
54 <includematcher includes='(?:|.*/)f1(?:/|$)'>
55 f inside/f1 inside/f1
55 f inside/f1 inside/f1
56 $ hg add .
56 $ hg add .
57 $ hg add outside/f1
57 $ hg add outside/f1
58 abort: cannot track 'outside/f1' - it is outside the narrow clone
58 abort: cannot track 'outside/f1' - it is outside the narrow clone
59 [255]
59 [255]
60 $ touch outside/f3
60 $ touch outside/f3
61 $ hg add outside/f3
61 $ hg add outside/f3
62 abort: cannot track 'outside/f3' - it is outside the narrow clone
62 abort: cannot track 'outside/f3' - it is outside the narrow clone
63 [255]
63 [255]
64
64
65 But adding a truly excluded file shouldn't count
65 But adding a truly excluded file shouldn't count
66
66
67 $ hg add outside/f3 -X outside/f3
67 $ hg add outside/f3 -X outside/f3
68
68
69 $ rm -r outside
69 $ rm -r outside
70
70
71 Can modify dirstate inside
71 Can modify dirstate inside
72
72
73 $ echo modified > inside/f1
73 $ echo modified > inside/f1
74 $ touch inside/f3
74 $ touch inside/f3
75 $ hg add inside/f3
75 $ hg add inside/f3
76 $ hg status
76 $ hg status
77 M inside/f1
77 M inside/f1
78 A inside/f3
78 A inside/f3
79 $ hg revert -qC .
79 $ hg revert -qC .
80 $ rm inside/f3
80 $ rm inside/f3
81
81
82 Can commit changes inside. Leaves outside unchanged.
82 Can commit changes inside. Leaves outside unchanged.
83
83
84 $ hg update -q 'desc("initial")'
84 $ hg update -q 'desc("initial")'
85 $ echo modified2 > inside/f1
85 $ echo modified2 > inside/f1
86 $ hg manifest --debug
86 $ hg manifest --debug
87 4d6a634d5ba06331a60c29ee0db8412490a54fcd 644 inside/f1
87 4d6a634d5ba06331a60c29ee0db8412490a54fcd 644 inside/f1
88 7fb3bb6356d28d4dc352c5ba52d7350a81b6bd46 644 outside/f1 (flat !)
88 7fb3bb6356d28d4dc352c5ba52d7350a81b6bd46 644 outside/f1 (flat !)
89 d0f2f706468ab0e8bec7af87446835fb1b13511b 755 d outside/ (tree !)
89 d0f2f706468ab0e8bec7af87446835fb1b13511b 755 d outside/ (tree !)
90 $ hg commit -m 'modify inside/f1'
90 $ hg commit -m 'modify inside/f1'
91 created new head
91 created new head
92 $ hg files -r .
92 $ hg files -r .
93 inside/f1
93 inside/f1
94 $ hg manifest --debug
94 $ hg manifest --debug
95 3f4197b4a11b9016e77ebc47fe566944885fd11b 644 inside/f1
95 3f4197b4a11b9016e77ebc47fe566944885fd11b 644 inside/f1
96 7fb3bb6356d28d4dc352c5ba52d7350a81b6bd46 644 outside/f1 (flat !)
96 7fb3bb6356d28d4dc352c5ba52d7350a81b6bd46 644 outside/f1 (flat !)
97 d0f2f706468ab0e8bec7af87446835fb1b13511b 755 d outside/ (tree !)
97 d0f2f706468ab0e8bec7af87446835fb1b13511b 755 d outside/ (tree !)
98 Some filesystems (notably FAT/exFAT only store timestamps with 2
98 Some filesystems (notably FAT/exFAT only store timestamps with 2
99 seconds of precision, so by sleeping for 3 seconds, we can ensure that
99 seconds of precision, so by sleeping for 3 seconds, we can ensure that
100 the timestamps of files stored by dirstate will appear older than the
100 the timestamps of files stored by dirstate will appear older than the
101 dirstate file, and therefore we'll be able to get stable output from
101 dirstate file, and therefore we'll be able to get stable output from
102 debugdirstate. If we don't do this, the test can be slightly flaky.
102 debugdirstate. If we don't do this, the test can be slightly flaky.
103 $ sleep 3
103 $ sleep 3
104 $ hg status
104 $ hg status
105 $ hg debugdirstate --no-dates
105 $ hg debugdirstate --no-dates
106 n 644 10 set inside/f1
106 n 644 10 set inside/f1
107
107
108 Can't commit empty files
108 Can commit empty files
109
109
110 $ touch inside/c; hg add inside/c; hg commit -qm _; hg verify -q
110 $ touch inside/c; hg add inside/c; hg commit -qm _; hg verify -q
111 warning: revlog 'data/inside/c.i' not in fncache!
111 $ hg cat -r . inside/c
112 4: empty or missing inside/c
113 inside/c@4: manifest refers to unknown revision 000000000000
114 1 warnings encountered!
115 hint: run "hg debugrebuildfncache" to recover from corrupt fncache
116 2 integrity errors encountered!
117 (first damaged changeset appears to be 4)
118 [1]
General Comments 0
You need to be logged in to leave comments. Login now