##// END OF EJS Templates
commit: move salvaged calculation a bit earlier in the function...
Pulkit Goyal -
r46298:b92887ce default
parent child Browse files
Show More
@@ -1,480 +1,480 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 mn, files = _prepare_files(tr, ctx, error=error, origctx=origctx)
68 68
69 69 extra = ctx.extra().copy()
70 70
71 71 if extra is not None:
72 72 for name in (
73 73 b'p1copies',
74 74 b'p2copies',
75 75 b'filesadded',
76 76 b'filesremoved',
77 77 ):
78 78 extra.pop(name, None)
79 79 if repo.changelog._copiesstorage == b'extra':
80 80 extra = _extra_with_copies(repo, extra, files)
81 81
82 82 # update changelog
83 83 repo.ui.note(_(b"committing changelog\n"))
84 84 repo.changelog.delayupdate(tr)
85 85 n = repo.changelog.add(
86 86 mn,
87 87 files,
88 88 ctx.description(),
89 89 tr,
90 90 p1.node(),
91 91 p2.node(),
92 92 user,
93 93 ctx.date(),
94 94 extra,
95 95 )
96 96 xp1, xp2 = p1.hex(), p2 and p2.hex() or b''
97 97 repo.hook(
98 98 b'pretxncommit', throw=True, node=hex(n), parent1=xp1, parent2=xp2,
99 99 )
100 100 # set the new commit is proper phase
101 101 targetphase = subrepoutil.newcommitphase(repo.ui, ctx)
102 102 if targetphase:
103 103 # retract boundary do not alter parent changeset.
104 104 # if a parent have higher the resulting phase will
105 105 # be compliant anyway
106 106 #
107 107 # if minimal phase was 0 we don't need to retract anything
108 108 phases.registernew(repo, tr, targetphase, [n])
109 109 return n
110 110
111 111
112 112 def _prepare_files(tr, ctx, error=False, origctx=None):
113 113 repo = ctx.repo()
114 114 p1 = ctx.p1()
115 115
116 116 writechangesetcopy, writefilecopymeta = _write_copy_meta(repo)
117 ms = mergestate.mergestate.read(repo)
118 salvaged = _get_salvaged(repo, ms, ctx)
117 119
118 120 if ctx.manifestnode():
119 121 # reuse an existing manifest revision
120 122 repo.ui.debug(b'reusing known manifest\n')
121 123 mn = ctx.manifestnode()
122 124 files = metadata.ChangingFiles()
123 125 files.update_touched(ctx.files())
124 126 if writechangesetcopy:
125 127 files.update_added(ctx.filesadded())
126 128 files.update_removed(ctx.filesremoved())
127 129 elif not ctx.files():
128 130 repo.ui.debug(b'reusing manifest from p1 (no file change)\n')
129 131 mn = p1.manifestnode()
130 132 files = metadata.ChangingFiles()
131 133 else:
132 134 mn, files = _process_files(tr, ctx, error=error)
133 135
134 136 if origctx and origctx.manifestnode() == mn:
135 137 origfiles = origctx.files()
136 138 assert files.touched.issubset(origfiles)
137 139 files.update_touched(origfiles)
138 140
139 141 if writechangesetcopy:
140 142 files.update_copies_from_p1(ctx.p1copies())
141 143 files.update_copies_from_p2(ctx.p2copies())
142 144
143 ms = mergestate.mergestate.read(repo)
144 salvaged = _get_salvaged(ctx.repo(), ms, ctx)
145 145 for s in salvaged:
146 146 files.mark_salvaged(s)
147 147
148 148 return mn, files
149 149
150 150
151 151 def _get_salvaged(repo, ms, ctx):
152 152 """ returns a list of salvaged files
153 153
154 154 returns empty list if config option which process salvaged files are
155 155 not enabled """
156 156 salvaged = []
157 157 copy_sd = repo.filecopiesmode == b'changeset-sidedata'
158 158 if copy_sd and len(ctx.parents()) > 1:
159 159 if ms.active():
160 160 for fname in sorted(ms._stateextras.keys()):
161 161 might_removed = ms.extras(fname).get(b'merge-removal-candidate')
162 162 if might_removed == b'yes':
163 163 if fname in ctx:
164 164 salvaged.append(fname)
165 165 return salvaged
166 166
167 167
168 168 def _process_files(tr, ctx, error=False):
169 169 repo = ctx.repo()
170 170 p1 = ctx.p1()
171 171 p2 = ctx.p2()
172 172
173 173 writechangesetcopy, writefilecopymeta = _write_copy_meta(repo)
174 174
175 175 m1ctx = p1.manifestctx()
176 176 m2ctx = p2.manifestctx()
177 177 mctx = m1ctx.copy()
178 178
179 179 m = mctx.read()
180 180 m1 = m1ctx.read()
181 181 m2 = m2ctx.read()
182 182 ms = mergestate.mergestate.read(repo)
183 183
184 184 files = metadata.ChangingFiles()
185 185
186 186 # check in files
187 187 added = []
188 188 removed = list(ctx.removed())
189 189 linkrev = len(repo)
190 190 repo.ui.note(_(b"committing files:\n"))
191 191 uipathfn = scmutil.getuipathfn(repo)
192 192 for f in sorted(ctx.modified() + ctx.added()):
193 193 repo.ui.note(uipathfn(f) + b"\n")
194 194 try:
195 195 fctx = ctx[f]
196 196 if fctx is None:
197 197 removed.append(f)
198 198 else:
199 199 added.append(f)
200 200 m[f], is_touched = _filecommit(
201 201 repo, fctx, m1, m2, linkrev, tr, writefilecopymeta, ms
202 202 )
203 203 if is_touched:
204 204 if is_touched == 'added':
205 205 files.mark_added(f)
206 206 elif is_touched == 'merged':
207 207 files.mark_merged(f)
208 208 else:
209 209 files.mark_touched(f)
210 210 m.setflag(f, fctx.flags())
211 211 except OSError:
212 212 repo.ui.warn(_(b"trouble committing %s!\n") % uipathfn(f))
213 213 raise
214 214 except IOError as inst:
215 215 errcode = getattr(inst, 'errno', errno.ENOENT)
216 216 if error or errcode and errcode != errno.ENOENT:
217 217 repo.ui.warn(_(b"trouble committing %s!\n") % uipathfn(f))
218 218 raise
219 219
220 220 # update manifest
221 221 removed = [f for f in removed if f in m1 or f in m2]
222 222 drop = sorted([f for f in removed if f in m])
223 223 for f in drop:
224 224 del m[f]
225 225 if p2.rev() == nullrev:
226 226 files.update_removed(removed)
227 227 else:
228 228 rf = metadata.get_removal_filter(ctx, (p1, p2, m1, m2))
229 229 for f in removed:
230 230 if not rf(f):
231 231 files.mark_removed(f)
232 232
233 233 mn = _commit_manifest(tr, linkrev, ctx, mctx, m, files.touched, added, drop)
234 234
235 235 return mn, files
236 236
237 237
238 238 def _filecommit(
239 239 repo, fctx, manifest1, manifest2, linkrev, tr, includecopymeta, ms,
240 240 ):
241 241 """
242 242 commit an individual file as part of a larger transaction
243 243
244 244 input:
245 245
246 246 fctx: a file context with the content we are trying to commit
247 247 manifest1: manifest of changeset first parent
248 248 manifest2: manifest of changeset second parent
249 249 linkrev: revision number of the changeset being created
250 250 tr: current transation
251 251 includecopymeta: boolean, set to False to skip storing the copy data
252 252 (only used by the Google specific feature of using
253 253 changeset extra as copy source of truth).
254 254 ms: mergestate object
255 255
256 256 output: (filenode, touched)
257 257
258 258 filenode: the filenode that should be used by this changeset
259 259 touched: one of: None (mean untouched), 'added' or 'modified'
260 260 """
261 261
262 262 fname = fctx.path()
263 263 fparent1 = manifest1.get(fname, nullid)
264 264 fparent2 = manifest2.get(fname, nullid)
265 265 touched = None
266 266 if fparent1 == fparent2 == nullid:
267 267 touched = 'added'
268 268
269 269 if isinstance(fctx, context.filectx):
270 270 # This block fast path most comparisons which are usually done. It
271 271 # assumes that bare filectx is used and no merge happened, hence no
272 272 # need to create a new file revision in this case.
273 273 node = fctx.filenode()
274 274 if node in [fparent1, fparent2]:
275 275 repo.ui.debug(b'reusing %s filelog entry\n' % fname)
276 276 if (
277 277 fparent1 != nullid and manifest1.flags(fname) != fctx.flags()
278 278 ) or (
279 279 fparent2 != nullid and manifest2.flags(fname) != fctx.flags()
280 280 ):
281 281 touched = 'modified'
282 282 return node, touched
283 283
284 284 flog = repo.file(fname)
285 285 meta = {}
286 286 cfname = fctx.copysource()
287 287 fnode = None
288 288
289 289 if cfname and cfname != fname:
290 290 # Mark the new revision of this file as a copy of another
291 291 # file. This copy data will effectively act as a parent
292 292 # of this new revision. If this is a merge, the first
293 293 # parent will be the nullid (meaning "look up the copy data")
294 294 # and the second one will be the other parent. For example:
295 295 #
296 296 # 0 --- 1 --- 3 rev1 changes file foo
297 297 # \ / rev2 renames foo to bar and changes it
298 298 # \- 2 -/ rev3 should have bar with all changes and
299 299 # should record that bar descends from
300 300 # bar in rev2 and foo in rev1
301 301 #
302 302 # this allows this merge to succeed:
303 303 #
304 304 # 0 --- 1 --- 3 rev4 reverts the content change from rev2
305 305 # \ / merging rev3 and rev4 should use bar@rev2
306 306 # \- 2 --- 4 as the merge base
307 307 #
308 308
309 309 cnode = manifest1.get(cfname)
310 310 newfparent = fparent2
311 311
312 312 if manifest2: # branch merge
313 313 if fparent2 == nullid or cnode is None: # copied on remote side
314 314 if cfname in manifest2:
315 315 cnode = manifest2[cfname]
316 316 newfparent = fparent1
317 317
318 318 # Here, we used to search backwards through history to try to find
319 319 # where the file copy came from if the source of a copy was not in
320 320 # the parent directory. However, this doesn't actually make sense to
321 321 # do (what does a copy from something not in your working copy even
322 322 # mean?) and it causes bugs (eg, issue4476). Instead, we will warn
323 323 # the user that copy information was dropped, so if they didn't
324 324 # expect this outcome it can be fixed, but this is the correct
325 325 # behavior in this circumstance.
326 326
327 327 if cnode:
328 328 repo.ui.debug(b" %s: copy %s:%s\n" % (fname, cfname, hex(cnode)))
329 329 if includecopymeta:
330 330 meta[b"copy"] = cfname
331 331 meta[b"copyrev"] = hex(cnode)
332 332 fparent1, fparent2 = nullid, newfparent
333 333 else:
334 334 repo.ui.warn(
335 335 _(
336 336 b"warning: can't find ancestor for '%s' "
337 337 b"copied from '%s'!\n"
338 338 )
339 339 % (fname, cfname)
340 340 )
341 341
342 342 elif fparent1 == nullid:
343 343 fparent1, fparent2 = fparent2, nullid
344 344 elif fparent2 != nullid:
345 345 # is one parent an ancestor of the other?
346 346 fparentancestors = flog.commonancestorsheads(fparent1, fparent2)
347 347 if fparent1 in fparentancestors:
348 348 fparent1, fparent2 = fparent2, nullid
349 349 elif fparent2 in fparentancestors:
350 350 fparent2 = nullid
351 351 elif not fparentancestors:
352 352 # TODO: this whole if-else might be simplified much more
353 353 if (
354 354 ms.active()
355 355 and ms.extras(fname).get(b'filenode-source') == b'other'
356 356 ):
357 357 fparent1, fparent2 = fparent2, nullid
358 358
359 359 force_new_node = False
360 360 # The file might have been deleted by merge code and user explicitly choose
361 361 # to revert the file and keep it. The other case can be where there is
362 362 # change-delete or delete-change conflict and user explicitly choose to keep
363 363 # the file. The goal is to create a new filenode for users explicit choices
364 364 if (
365 365 repo.ui.configbool(b'experimental', b'merge-track-salvaged')
366 366 and ms.active()
367 367 and ms.extras(fname).get(b'merge-removal-candidate') == b'yes'
368 368 ):
369 369 force_new_node = True
370 370 # is the file changed?
371 371 text = fctx.data()
372 372 if fparent2 != nullid or meta or flog.cmp(fparent1, text) or force_new_node:
373 373 if touched is None: # do not overwrite added
374 374 if fparent2 == nullid:
375 375 touched = 'modified'
376 376 else:
377 377 touched = 'merged'
378 378 fnode = flog.add(text, meta, tr, linkrev, fparent1, fparent2)
379 379 # are just the flags changed during merge?
380 380 elif fname in manifest1 and manifest1.flags(fname) != fctx.flags():
381 381 touched = 'modified'
382 382 fnode = fparent1
383 383 else:
384 384 fnode = fparent1
385 385 return fnode, touched
386 386
387 387
388 388 def _commit_manifest(tr, linkrev, ctx, mctx, manifest, files, added, drop):
389 389 """make a new manifest entry (or reuse a new one)
390 390
391 391 given an initialised manifest context and precomputed list of
392 392 - files: files affected by the commit
393 393 - added: new entries in the manifest
394 394 - drop: entries present in parents but absent of this one
395 395
396 396 Create a new manifest revision, reuse existing ones if possible.
397 397
398 398 Return the nodeid of the manifest revision.
399 399 """
400 400 repo = ctx.repo()
401 401
402 402 md = None
403 403
404 404 # all this is cached, so it is find to get them all from the ctx.
405 405 p1 = ctx.p1()
406 406 p2 = ctx.p2()
407 407 m1ctx = p1.manifestctx()
408 408
409 409 m1 = m1ctx.read()
410 410
411 411 if not files:
412 412 # if no "files" actually changed in terms of the changelog,
413 413 # try hard to detect unmodified manifest entry so that the
414 414 # exact same commit can be reproduced later on convert.
415 415 md = m1.diff(manifest, scmutil.matchfiles(repo, ctx.files()))
416 416 if not files and md:
417 417 repo.ui.debug(
418 418 b'not reusing manifest (no file change in '
419 419 b'changelog, but manifest differs)\n'
420 420 )
421 421 if files or md:
422 422 repo.ui.note(_(b"committing manifest\n"))
423 423 # we're using narrowmatch here since it's already applied at
424 424 # other stages (such as dirstate.walk), so we're already
425 425 # ignoring things outside of narrowspec in most cases. The
426 426 # one case where we might have files outside the narrowspec
427 427 # at this point is merges, and we already error out in the
428 428 # case where the merge has files outside of the narrowspec,
429 429 # so this is safe.
430 430 mn = mctx.write(
431 431 tr,
432 432 linkrev,
433 433 p1.manifestnode(),
434 434 p2.manifestnode(),
435 435 added,
436 436 drop,
437 437 match=repo.narrowmatch(),
438 438 )
439 439 else:
440 440 repo.ui.debug(
441 441 b'reusing manifest from p1 (listed files ' b'actually unchanged)\n'
442 442 )
443 443 mn = p1.manifestnode()
444 444
445 445 return mn
446 446
447 447
448 448 def _extra_with_copies(repo, extra, files):
449 449 """encode copy information into a `extra` dictionnary"""
450 450 p1copies = files.copied_from_p1
451 451 p2copies = files.copied_from_p2
452 452 filesadded = files.added
453 453 filesremoved = files.removed
454 454 files = sorted(files.touched)
455 455 if not _write_copy_meta(repo)[1]:
456 456 # If writing only to changeset extras, use None to indicate that
457 457 # no entry should be written. If writing to both, write an empty
458 458 # entry to prevent the reader from falling back to reading
459 459 # filelogs.
460 460 p1copies = p1copies or None
461 461 p2copies = p2copies or None
462 462 filesadded = filesadded or None
463 463 filesremoved = filesremoved or None
464 464
465 465 extrasentries = p1copies, p2copies, filesadded, filesremoved
466 466 if extra is None and any(x is not None for x in extrasentries):
467 467 extra = {}
468 468 if p1copies is not None:
469 469 p1copies = metadata.encodecopies(files, p1copies)
470 470 extra[b'p1copies'] = p1copies
471 471 if p2copies is not None:
472 472 p2copies = metadata.encodecopies(files, p2copies)
473 473 extra[b'p2copies'] = p2copies
474 474 if filesadded is not None:
475 475 filesadded = metadata.encodefileindices(files, filesadded)
476 476 extra[b'filesadded'] = filesadded
477 477 if filesremoved is not None:
478 478 filesremoved = metadata.encodefileindices(files, filesremoved)
479 479 extra[b'filesremoved'] = filesremoved
480 480 return extra
General Comments 0
You need to be logged in to leave comments. Login now