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