##// END OF EJS Templates
commitctx: extract the function that commit a new manifest...
marmoute -
r45789:f0d4d134 default
parent child Browse files
Show More
@@ -1,351 +1,379
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 nullid,
13 nullid,
14 nullrev,
14 nullrev,
15 )
15 )
16
16
17 from . import (
17 from . import (
18 context,
18 context,
19 mergestate,
19 mergestate,
20 metadata,
20 metadata,
21 phases,
21 phases,
22 scmutil,
22 scmutil,
23 subrepoutil,
23 subrepoutil,
24 )
24 )
25
25
26
26
27 def commitctx(repo, ctx, error=False, origctx=None):
27 def commitctx(repo, ctx, error=False, origctx=None):
28 """Add a new revision to the target repository.
28 """Add a new revision to the target repository.
29 Revision information is passed via the context argument.
29 Revision information is passed via the context argument.
30
30
31 ctx.files() should list all files involved in this commit, i.e.
31 ctx.files() should list all files involved in this commit, i.e.
32 modified/added/removed files. On merge, it may be wider than the
32 modified/added/removed files. On merge, it may be wider than the
33 ctx.files() to be committed, since any file nodes derived directly
33 ctx.files() to be committed, since any file nodes derived directly
34 from p1 or p2 are excluded from the committed ctx.files().
34 from p1 or p2 are excluded from the committed ctx.files().
35
35
36 origctx is for convert to work around the problem that bug
36 origctx is for convert to work around the problem that bug
37 fixes to the files list in changesets change hashes. For
37 fixes to the files list in changesets change hashes. For
38 convert to be the identity, it can pass an origctx and this
38 convert to be the identity, it can pass an origctx and this
39 function will use the same files list when it makes sense to
39 function will use the same files list when it makes sense to
40 do so.
40 do so.
41 """
41 """
42 repo = repo.unfiltered()
42 repo = repo.unfiltered()
43
43
44 p1, p2 = ctx.p1(), ctx.p2()
44 p1, p2 = ctx.p1(), ctx.p2()
45 user = ctx.user()
45 user = ctx.user()
46
46
47 if repo.filecopiesmode == b'changeset-sidedata':
47 if repo.filecopiesmode == b'changeset-sidedata':
48 writechangesetcopy = True
48 writechangesetcopy = True
49 writefilecopymeta = True
49 writefilecopymeta = True
50 writecopiesto = None
50 writecopiesto = None
51 else:
51 else:
52 writecopiesto = repo.ui.config(b'experimental', b'copies.write-to')
52 writecopiesto = repo.ui.config(b'experimental', b'copies.write-to')
53 writefilecopymeta = writecopiesto != b'changeset-only'
53 writefilecopymeta = writecopiesto != b'changeset-only'
54 writechangesetcopy = writecopiesto in (
54 writechangesetcopy = writecopiesto in (
55 b'changeset-only',
55 b'changeset-only',
56 b'compatibility',
56 b'compatibility',
57 )
57 )
58 p1copies, p2copies = None, None
58 p1copies, p2copies = None, None
59 if writechangesetcopy:
59 if writechangesetcopy:
60 p1copies = ctx.p1copies()
60 p1copies = ctx.p1copies()
61 p2copies = ctx.p2copies()
61 p2copies = ctx.p2copies()
62 filesadded, filesremoved = None, None
62 filesadded, filesremoved = None, None
63 with repo.lock(), repo.transaction(b"commit") as tr:
63 with repo.lock(), repo.transaction(b"commit") as tr:
64 if ctx.manifestnode():
64 if ctx.manifestnode():
65 # reuse an existing manifest revision
65 # reuse an existing manifest revision
66 repo.ui.debug(b'reusing known manifest\n')
66 repo.ui.debug(b'reusing known manifest\n')
67 mn = ctx.manifestnode()
67 mn = ctx.manifestnode()
68 files = ctx.files()
68 files = ctx.files()
69 if writechangesetcopy:
69 if writechangesetcopy:
70 filesadded = ctx.filesadded()
70 filesadded = ctx.filesadded()
71 filesremoved = ctx.filesremoved()
71 filesremoved = ctx.filesremoved()
72 elif not ctx.files():
72 elif not ctx.files():
73 repo.ui.debug(b'reusing manifest from p1 (no file change)\n')
73 repo.ui.debug(b'reusing manifest from p1 (no file change)\n')
74 mn = p1.manifestnode()
74 mn = p1.manifestnode()
75 files = []
75 files = []
76 else:
76 else:
77 m1ctx = p1.manifestctx()
77 m1ctx = p1.manifestctx()
78 m2ctx = p2.manifestctx()
78 m2ctx = p2.manifestctx()
79 mctx = m1ctx.copy()
79 mctx = m1ctx.copy()
80
80
81 m = mctx.read()
81 m = mctx.read()
82 m1 = m1ctx.read()
82 m1 = m1ctx.read()
83 m2 = m2ctx.read()
83 m2 = m2ctx.read()
84
84
85 # check in files
85 # check in files
86 added = []
86 added = []
87 filesadded = []
87 filesadded = []
88 removed = list(ctx.removed())
88 removed = list(ctx.removed())
89 touched = []
89 touched = []
90 linkrev = len(repo)
90 linkrev = len(repo)
91 repo.ui.note(_(b"committing files:\n"))
91 repo.ui.note(_(b"committing files:\n"))
92 uipathfn = scmutil.getuipathfn(repo)
92 uipathfn = scmutil.getuipathfn(repo)
93 for f in sorted(ctx.modified() + ctx.added()):
93 for f in sorted(ctx.modified() + ctx.added()):
94 repo.ui.note(uipathfn(f) + b"\n")
94 repo.ui.note(uipathfn(f) + b"\n")
95 try:
95 try:
96 fctx = ctx[f]
96 fctx = ctx[f]
97 if fctx is None:
97 if fctx is None:
98 removed.append(f)
98 removed.append(f)
99 else:
99 else:
100 added.append(f)
100 added.append(f)
101 m[f], is_touched = _filecommit(
101 m[f], is_touched = _filecommit(
102 repo, fctx, m1, m2, linkrev, tr, writefilecopymeta,
102 repo, fctx, m1, m2, linkrev, tr, writefilecopymeta,
103 )
103 )
104 if is_touched:
104 if is_touched:
105 touched.append(f)
105 touched.append(f)
106 if writechangesetcopy and is_touched == 'added':
106 if writechangesetcopy and is_touched == 'added':
107 filesadded.append(f)
107 filesadded.append(f)
108 m.setflag(f, fctx.flags())
108 m.setflag(f, fctx.flags())
109 except OSError:
109 except OSError:
110 repo.ui.warn(_(b"trouble committing %s!\n") % uipathfn(f))
110 repo.ui.warn(_(b"trouble committing %s!\n") % uipathfn(f))
111 raise
111 raise
112 except IOError as inst:
112 except IOError as inst:
113 errcode = getattr(inst, 'errno', errno.ENOENT)
113 errcode = getattr(inst, 'errno', errno.ENOENT)
114 if error or errcode and errcode != errno.ENOENT:
114 if error or errcode and errcode != errno.ENOENT:
115 repo.ui.warn(
115 repo.ui.warn(
116 _(b"trouble committing %s!\n") % uipathfn(f)
116 _(b"trouble committing %s!\n") % uipathfn(f)
117 )
117 )
118 raise
118 raise
119
119
120 # update manifest
120 # update manifest
121 removed = [f for f in removed if f in m1 or f in m2]
121 removed = [f for f in removed if f in m1 or f in m2]
122 drop = sorted([f for f in removed if f in m])
122 drop = sorted([f for f in removed if f in m])
123 for f in drop:
123 for f in drop:
124 del m[f]
124 del m[f]
125 if p2.rev() != nullrev:
125 if p2.rev() != nullrev:
126 rf = metadata.get_removal_filter(ctx, (p1, p2, m1, m2))
126 rf = metadata.get_removal_filter(ctx, (p1, p2, m1, m2))
127 removed = [f for f in removed if not rf(f)]
127 removed = [f for f in removed if not rf(f)]
128
128
129 touched.extend(removed)
129 touched.extend(removed)
130
130
131 if writechangesetcopy:
131 if writechangesetcopy:
132 filesremoved = removed
132 filesremoved = removed
133
133
134 files = touched
134 files = touched
135 md = None
135 mn = _commit_manifest(tr, linkrev, ctx, mctx, files, added, drop)
136 if not files:
137 # if no "files" actually changed in terms of the changelog,
138 # try hard to detect unmodified manifest entry so that the
139 # exact same commit can be reproduced later on convert.
140 md = m1.diff(m, scmutil.matchfiles(repo, ctx.files()))
141 if not files and md:
142 repo.ui.debug(
143 b'not reusing manifest (no file change in '
144 b'changelog, but manifest differs)\n'
145 )
146 if files or md:
147 repo.ui.note(_(b"committing manifest\n"))
148 # we're using narrowmatch here since it's already applied at
149 # other stages (such as dirstate.walk), so we're already
150 # ignoring things outside of narrowspec in most cases. The
151 # one case where we might have files outside the narrowspec
152 # at this point is merges, and we already error out in the
153 # case where the merge has files outside of the narrowspec,
154 # so this is safe.
155 mn = mctx.write(
156 tr,
157 linkrev,
158 p1.manifestnode(),
159 p2.manifestnode(),
160 added,
161 drop,
162 match=repo.narrowmatch(),
163 )
164 else:
165 repo.ui.debug(
166 b'reusing manifest from p1 (listed files '
167 b'actually unchanged)\n'
168 )
169 mn = p1.manifestnode()
170
136
171 if writecopiesto == b'changeset-only':
137 if writecopiesto == b'changeset-only':
172 # If writing only to changeset extras, use None to indicate that
138 # If writing only to changeset extras, use None to indicate that
173 # no entry should be written. If writing to both, write an empty
139 # no entry should be written. If writing to both, write an empty
174 # entry to prevent the reader from falling back to reading
140 # entry to prevent the reader from falling back to reading
175 # filelogs.
141 # filelogs.
176 p1copies = p1copies or None
142 p1copies = p1copies or None
177 p2copies = p2copies or None
143 p2copies = p2copies or None
178 filesadded = filesadded or None
144 filesadded = filesadded or None
179 filesremoved = filesremoved or None
145 filesremoved = filesremoved or None
180
146
181 if origctx and origctx.manifestnode() == mn:
147 if origctx and origctx.manifestnode() == mn:
182 files = origctx.files()
148 files = origctx.files()
183
149
184 # update changelog
150 # update changelog
185 repo.ui.note(_(b"committing changelog\n"))
151 repo.ui.note(_(b"committing changelog\n"))
186 repo.changelog.delayupdate(tr)
152 repo.changelog.delayupdate(tr)
187 n = repo.changelog.add(
153 n = repo.changelog.add(
188 mn,
154 mn,
189 files,
155 files,
190 ctx.description(),
156 ctx.description(),
191 tr,
157 tr,
192 p1.node(),
158 p1.node(),
193 p2.node(),
159 p2.node(),
194 user,
160 user,
195 ctx.date(),
161 ctx.date(),
196 ctx.extra().copy(),
162 ctx.extra().copy(),
197 p1copies,
163 p1copies,
198 p2copies,
164 p2copies,
199 filesadded,
165 filesadded,
200 filesremoved,
166 filesremoved,
201 )
167 )
202 xp1, xp2 = p1.hex(), p2 and p2.hex() or b''
168 xp1, xp2 = p1.hex(), p2 and p2.hex() or b''
203 repo.hook(
169 repo.hook(
204 b'pretxncommit', throw=True, node=hex(n), parent1=xp1, parent2=xp2,
170 b'pretxncommit', throw=True, node=hex(n), parent1=xp1, parent2=xp2,
205 )
171 )
206 # set the new commit is proper phase
172 # set the new commit is proper phase
207 targetphase = subrepoutil.newcommitphase(repo.ui, ctx)
173 targetphase = subrepoutil.newcommitphase(repo.ui, ctx)
208 if targetphase:
174 if targetphase:
209 # retract boundary do not alter parent changeset.
175 # retract boundary do not alter parent changeset.
210 # if a parent have higher the resulting phase will
176 # if a parent have higher the resulting phase will
211 # be compliant anyway
177 # be compliant anyway
212 #
178 #
213 # if minimal phase was 0 we don't need to retract anything
179 # if minimal phase was 0 we don't need to retract anything
214 phases.registernew(repo, tr, targetphase, [n])
180 phases.registernew(repo, tr, targetphase, [n])
215 return n
181 return n
216
182
217
183
218 def _filecommit(
184 def _filecommit(
219 repo, fctx, manifest1, manifest2, linkrev, tr, includecopymeta,
185 repo, fctx, manifest1, manifest2, linkrev, tr, includecopymeta,
220 ):
186 ):
221 """
187 """
222 commit an individual file as part of a larger transaction
188 commit an individual file as part of a larger transaction
223
189
224 input:
190 input:
225
191
226 fctx: a file context with the content we are trying to commit
192 fctx: a file context with the content we are trying to commit
227 manifest1: manifest of changeset first parent
193 manifest1: manifest of changeset first parent
228 manifest2: manifest of changeset second parent
194 manifest2: manifest of changeset second parent
229 linkrev: revision number of the changeset being created
195 linkrev: revision number of the changeset being created
230 tr: current transation
196 tr: current transation
231 individual: boolean, set to False to skip storing the copy data
197 individual: boolean, set to False to skip storing the copy data
232 (only used by the Google specific feature of using
198 (only used by the Google specific feature of using
233 changeset extra as copy source of truth).
199 changeset extra as copy source of truth).
234
200
235 output: (filenode, touched)
201 output: (filenode, touched)
236
202
237 filenode: the filenode that should be used by this changeset
203 filenode: the filenode that should be used by this changeset
238 touched: one of: None (mean untouched), 'added' or 'modified'
204 touched: one of: None (mean untouched), 'added' or 'modified'
239 """
205 """
240
206
241 fname = fctx.path()
207 fname = fctx.path()
242 fparent1 = manifest1.get(fname, nullid)
208 fparent1 = manifest1.get(fname, nullid)
243 fparent2 = manifest2.get(fname, nullid)
209 fparent2 = manifest2.get(fname, nullid)
244 touched = None
210 touched = None
245 if fparent1 == fparent2 == nullid:
211 if fparent1 == fparent2 == nullid:
246 touched = 'added'
212 touched = 'added'
247
213
248 if isinstance(fctx, context.filectx):
214 if isinstance(fctx, context.filectx):
249 # This block fast path most comparisons which are usually done. It
215 # This block fast path most comparisons which are usually done. It
250 # assumes that bare filectx is used and no merge happened, hence no
216 # assumes that bare filectx is used and no merge happened, hence no
251 # need to create a new file revision in this case.
217 # need to create a new file revision in this case.
252 node = fctx.filenode()
218 node = fctx.filenode()
253 if node in [fparent1, fparent2]:
219 if node in [fparent1, fparent2]:
254 repo.ui.debug(b'reusing %s filelog entry\n' % fname)
220 repo.ui.debug(b'reusing %s filelog entry\n' % fname)
255 if (
221 if (
256 fparent1 != nullid and manifest1.flags(fname) != fctx.flags()
222 fparent1 != nullid and manifest1.flags(fname) != fctx.flags()
257 ) or (
223 ) or (
258 fparent2 != nullid and manifest2.flags(fname) != fctx.flags()
224 fparent2 != nullid and manifest2.flags(fname) != fctx.flags()
259 ):
225 ):
260 touched = 'modified'
226 touched = 'modified'
261 return node, touched
227 return node, touched
262
228
263 flog = repo.file(fname)
229 flog = repo.file(fname)
264 meta = {}
230 meta = {}
265 cfname = fctx.copysource()
231 cfname = fctx.copysource()
266 fnode = None
232 fnode = None
267
233
268 if cfname and cfname != fname:
234 if cfname and cfname != fname:
269 # Mark the new revision of this file as a copy of another
235 # Mark the new revision of this file as a copy of another
270 # file. This copy data will effectively act as a parent
236 # file. This copy data will effectively act as a parent
271 # of this new revision. If this is a merge, the first
237 # of this new revision. If this is a merge, the first
272 # parent will be the nullid (meaning "look up the copy data")
238 # parent will be the nullid (meaning "look up the copy data")
273 # and the second one will be the other parent. For example:
239 # and the second one will be the other parent. For example:
274 #
240 #
275 # 0 --- 1 --- 3 rev1 changes file foo
241 # 0 --- 1 --- 3 rev1 changes file foo
276 # \ / rev2 renames foo to bar and changes it
242 # \ / rev2 renames foo to bar and changes it
277 # \- 2 -/ rev3 should have bar with all changes and
243 # \- 2 -/ rev3 should have bar with all changes and
278 # should record that bar descends from
244 # should record that bar descends from
279 # bar in rev2 and foo in rev1
245 # bar in rev2 and foo in rev1
280 #
246 #
281 # this allows this merge to succeed:
247 # this allows this merge to succeed:
282 #
248 #
283 # 0 --- 1 --- 3 rev4 reverts the content change from rev2
249 # 0 --- 1 --- 3 rev4 reverts the content change from rev2
284 # \ / merging rev3 and rev4 should use bar@rev2
250 # \ / merging rev3 and rev4 should use bar@rev2
285 # \- 2 --- 4 as the merge base
251 # \- 2 --- 4 as the merge base
286 #
252 #
287
253
288 cnode = manifest1.get(cfname)
254 cnode = manifest1.get(cfname)
289 newfparent = fparent2
255 newfparent = fparent2
290
256
291 if manifest2: # branch merge
257 if manifest2: # branch merge
292 if fparent2 == nullid or cnode is None: # copied on remote side
258 if fparent2 == nullid or cnode is None: # copied on remote side
293 if cfname in manifest2:
259 if cfname in manifest2:
294 cnode = manifest2[cfname]
260 cnode = manifest2[cfname]
295 newfparent = fparent1
261 newfparent = fparent1
296
262
297 # Here, we used to search backwards through history to try to find
263 # Here, we used to search backwards through history to try to find
298 # where the file copy came from if the source of a copy was not in
264 # where the file copy came from if the source of a copy was not in
299 # the parent directory. However, this doesn't actually make sense to
265 # the parent directory. However, this doesn't actually make sense to
300 # do (what does a copy from something not in your working copy even
266 # do (what does a copy from something not in your working copy even
301 # mean?) and it causes bugs (eg, issue4476). Instead, we will warn
267 # mean?) and it causes bugs (eg, issue4476). Instead, we will warn
302 # the user that copy information was dropped, so if they didn't
268 # the user that copy information was dropped, so if they didn't
303 # expect this outcome it can be fixed, but this is the correct
269 # expect this outcome it can be fixed, but this is the correct
304 # behavior in this circumstance.
270 # behavior in this circumstance.
305
271
306 if cnode:
272 if cnode:
307 repo.ui.debug(b" %s: copy %s:%s\n" % (fname, cfname, hex(cnode)))
273 repo.ui.debug(b" %s: copy %s:%s\n" % (fname, cfname, hex(cnode)))
308 if includecopymeta:
274 if includecopymeta:
309 meta[b"copy"] = cfname
275 meta[b"copy"] = cfname
310 meta[b"copyrev"] = hex(cnode)
276 meta[b"copyrev"] = hex(cnode)
311 fparent1, fparent2 = nullid, newfparent
277 fparent1, fparent2 = nullid, newfparent
312 else:
278 else:
313 repo.ui.warn(
279 repo.ui.warn(
314 _(
280 _(
315 b"warning: can't find ancestor for '%s' "
281 b"warning: can't find ancestor for '%s' "
316 b"copied from '%s'!\n"
282 b"copied from '%s'!\n"
317 )
283 )
318 % (fname, cfname)
284 % (fname, cfname)
319 )
285 )
320
286
321 elif fparent1 == nullid:
287 elif fparent1 == nullid:
322 fparent1, fparent2 = fparent2, nullid
288 fparent1, fparent2 = fparent2, nullid
323 elif fparent2 != nullid:
289 elif fparent2 != nullid:
324 # is one parent an ancestor of the other?
290 # is one parent an ancestor of the other?
325 fparentancestors = flog.commonancestorsheads(fparent1, fparent2)
291 fparentancestors = flog.commonancestorsheads(fparent1, fparent2)
326 if fparent1 in fparentancestors:
292 if fparent1 in fparentancestors:
327 fparent1, fparent2 = fparent2, nullid
293 fparent1, fparent2 = fparent2, nullid
328 elif fparent2 in fparentancestors:
294 elif fparent2 in fparentancestors:
329 fparent2 = nullid
295 fparent2 = nullid
330 elif not fparentancestors:
296 elif not fparentancestors:
331 # TODO: this whole if-else might be simplified much more
297 # TODO: this whole if-else might be simplified much more
332 ms = mergestate.mergestate.read(repo)
298 ms = mergestate.mergestate.read(repo)
333 if (
299 if (
334 fname in ms
300 fname in ms
335 and ms[fname] == mergestate.MERGE_RECORD_MERGED_OTHER
301 and ms[fname] == mergestate.MERGE_RECORD_MERGED_OTHER
336 ):
302 ):
337 fparent1, fparent2 = fparent2, nullid
303 fparent1, fparent2 = fparent2, nullid
338
304
339 # is the file changed?
305 # is the file changed?
340 text = fctx.data()
306 text = fctx.data()
341 if fparent2 != nullid or meta or flog.cmp(fparent1, text):
307 if fparent2 != nullid or meta or flog.cmp(fparent1, text):
342 if touched is None: # do not overwrite added
308 if touched is None: # do not overwrite added
343 touched = 'modified'
309 touched = 'modified'
344 fnode = flog.add(text, meta, tr, linkrev, fparent1, fparent2)
310 fnode = flog.add(text, meta, tr, linkrev, fparent1, fparent2)
345 # are just the flags changed during merge?
311 # are just the flags changed during merge?
346 elif fname in manifest1 and manifest1.flags(fname) != fctx.flags():
312 elif fname in manifest1 and manifest1.flags(fname) != fctx.flags():
347 touched = 'modified'
313 touched = 'modified'
348 fnode = fparent1
314 fnode = fparent1
349 else:
315 else:
350 fnode = fparent1
316 fnode = fparent1
351 return fnode, touched
317 return fnode, touched
318
319
320 def _commit_manifest(tr, linkrev, ctx, mctx, files, added, drop):
321 """make a new manifest entry (or reuse a new one)
322
323 given an initialised manifest context and precomputed list of
324 - files: files affected by the commit
325 - added: new entries in the manifest
326 - drop: entries present in parents but absent of this one
327
328 Create a new manifest revision, reuse existing ones if possible.
329
330 Return the nodeid of the manifest revision.
331 """
332 repo = ctx.repo()
333
334 md = None
335
336 # all this is cached, so it is find to get them all from the ctx.
337 p1 = ctx.p1()
338 p2 = ctx.p2()
339 m1ctx = p1.manifestctx()
340
341 m1 = m1ctx.read()
342
343 manifest = mctx.read()
344
345 if not files:
346 # if no "files" actually changed in terms of the changelog,
347 # try hard to detect unmodified manifest entry so that the
348 # exact same commit can be reproduced later on convert.
349 md = m1.diff(manifest, scmutil.matchfiles(repo, ctx.files()))
350 if not files and md:
351 repo.ui.debug(
352 b'not reusing manifest (no file change in '
353 b'changelog, but manifest differs)\n'
354 )
355 if files or md:
356 repo.ui.note(_(b"committing manifest\n"))
357 # we're using narrowmatch here since it's already applied at
358 # other stages (such as dirstate.walk), so we're already
359 # ignoring things outside of narrowspec in most cases. The
360 # one case where we might have files outside the narrowspec
361 # at this point is merges, and we already error out in the
362 # case where the merge has files outside of the narrowspec,
363 # so this is safe.
364 mn = mctx.write(
365 tr,
366 linkrev,
367 p1.manifestnode(),
368 p2.manifestnode(),
369 added,
370 drop,
371 match=repo.narrowmatch(),
372 )
373 else:
374 repo.ui.debug(
375 b'reusing manifest from p1 (listed files ' b'actually unchanged)\n'
376 )
377 mn = p1.manifestnode()
378
379 return mn
General Comments 0
You need to be logged in to leave comments. Login now