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