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