3065 self._afterlock(commithook) |
3065 self._afterlock(commithook) |
3066 return ret |
3066 return ret |
3067 |
3067 |
3068 @unfilteredmethod |
3068 @unfilteredmethod |
3069 def commitctx(self, ctx, error=False, origctx=None): |
3069 def commitctx(self, ctx, error=False, origctx=None): |
3070 """Add a new revision to current repository. |
3070 return commit.commitctx(self, ctx, error=error, origctx=origctx) |
3071 Revision information is passed via the context argument. |
|
3072 |
|
3073 ctx.files() should list all files involved in this commit, i.e. |
|
3074 modified/added/removed files. On merge, it may be wider than the |
|
3075 ctx.files() to be committed, since any file nodes derived directly |
|
3076 from p1 or p2 are excluded from the committed ctx.files(). |
|
3077 |
|
3078 origctx is for convert to work around the problem that bug |
|
3079 fixes to the files list in changesets change hashes. For |
|
3080 convert to be the identity, it can pass an origctx and this |
|
3081 function will use the same files list when it makes sense to |
|
3082 do so. |
|
3083 """ |
|
3084 |
|
3085 p1, p2 = ctx.p1(), ctx.p2() |
|
3086 user = ctx.user() |
|
3087 |
|
3088 if self.filecopiesmode == b'changeset-sidedata': |
|
3089 writechangesetcopy = True |
|
3090 writefilecopymeta = True |
|
3091 writecopiesto = None |
|
3092 else: |
|
3093 writecopiesto = self.ui.config(b'experimental', b'copies.write-to') |
|
3094 writefilecopymeta = writecopiesto != b'changeset-only' |
|
3095 writechangesetcopy = writecopiesto in ( |
|
3096 b'changeset-only', |
|
3097 b'compatibility', |
|
3098 ) |
|
3099 p1copies, p2copies = None, None |
|
3100 if writechangesetcopy: |
|
3101 p1copies = ctx.p1copies() |
|
3102 p2copies = ctx.p2copies() |
|
3103 filesadded, filesremoved = None, None |
|
3104 with self.lock(), self.transaction(b"commit") as tr: |
|
3105 trp = weakref.proxy(tr) |
|
3106 |
|
3107 if ctx.manifestnode(): |
|
3108 # reuse an existing manifest revision |
|
3109 self.ui.debug(b'reusing known manifest\n') |
|
3110 mn = ctx.manifestnode() |
|
3111 files = ctx.files() |
|
3112 if writechangesetcopy: |
|
3113 filesadded = ctx.filesadded() |
|
3114 filesremoved = ctx.filesremoved() |
|
3115 elif not ctx.files(): |
|
3116 self.ui.debug(b'reusing manifest from p1 (no file change)\n') |
|
3117 mn = p1.manifestnode() |
|
3118 files = [] |
|
3119 else: |
|
3120 m1ctx = p1.manifestctx() |
|
3121 m2ctx = p2.manifestctx() |
|
3122 mctx = m1ctx.copy() |
|
3123 |
|
3124 m = mctx.read() |
|
3125 m1 = m1ctx.read() |
|
3126 m2 = m2ctx.read() |
|
3127 |
|
3128 # check in files |
|
3129 added = [] |
|
3130 filesadded = [] |
|
3131 removed = list(ctx.removed()) |
|
3132 touched = [] |
|
3133 linkrev = len(self) |
|
3134 self.ui.note(_(b"committing files:\n")) |
|
3135 uipathfn = scmutil.getuipathfn(self) |
|
3136 for f in sorted(ctx.modified() + ctx.added()): |
|
3137 self.ui.note(uipathfn(f) + b"\n") |
|
3138 try: |
|
3139 fctx = ctx[f] |
|
3140 if fctx is None: |
|
3141 removed.append(f) |
|
3142 else: |
|
3143 added.append(f) |
|
3144 m[f], is_touched = self._filecommit( |
|
3145 fctx, m1, m2, linkrev, trp, writefilecopymeta, |
|
3146 ) |
|
3147 if is_touched: |
|
3148 touched.append(f) |
|
3149 if writechangesetcopy and is_touched == 'added': |
|
3150 filesadded.append(f) |
|
3151 m.setflag(f, fctx.flags()) |
|
3152 except OSError: |
|
3153 self.ui.warn( |
|
3154 _(b"trouble committing %s!\n") % uipathfn(f) |
|
3155 ) |
|
3156 raise |
|
3157 except IOError as inst: |
|
3158 errcode = getattr(inst, 'errno', errno.ENOENT) |
|
3159 if error or errcode and errcode != errno.ENOENT: |
|
3160 self.ui.warn( |
|
3161 _(b"trouble committing %s!\n") % uipathfn(f) |
|
3162 ) |
|
3163 raise |
|
3164 |
|
3165 # update manifest |
|
3166 removed = [f for f in removed if f in m1 or f in m2] |
|
3167 drop = sorted([f for f in removed if f in m]) |
|
3168 for f in drop: |
|
3169 del m[f] |
|
3170 if p2.rev() != nullrev: |
|
3171 rf = metadata.get_removal_filter(ctx, (p1, p2, m1, m2)) |
|
3172 removed = [f for f in removed if not rf(f)] |
|
3173 |
|
3174 touched.extend(removed) |
|
3175 |
|
3176 if writechangesetcopy: |
|
3177 filesremoved = removed |
|
3178 |
|
3179 files = touched |
|
3180 md = None |
|
3181 if not files: |
|
3182 # if no "files" actually changed in terms of the changelog, |
|
3183 # try hard to detect unmodified manifest entry so that the |
|
3184 # exact same commit can be reproduced later on convert. |
|
3185 md = m1.diff(m, scmutil.matchfiles(self, ctx.files())) |
|
3186 if not files and md: |
|
3187 self.ui.debug( |
|
3188 b'not reusing manifest (no file change in ' |
|
3189 b'changelog, but manifest differs)\n' |
|
3190 ) |
|
3191 if files or md: |
|
3192 self.ui.note(_(b"committing manifest\n")) |
|
3193 # we're using narrowmatch here since it's already applied at |
|
3194 # other stages (such as dirstate.walk), so we're already |
|
3195 # ignoring things outside of narrowspec in most cases. The |
|
3196 # one case where we might have files outside the narrowspec |
|
3197 # at this point is merges, and we already error out in the |
|
3198 # case where the merge has files outside of the narrowspec, |
|
3199 # so this is safe. |
|
3200 mn = mctx.write( |
|
3201 trp, |
|
3202 linkrev, |
|
3203 p1.manifestnode(), |
|
3204 p2.manifestnode(), |
|
3205 added, |
|
3206 drop, |
|
3207 match=self.narrowmatch(), |
|
3208 ) |
|
3209 else: |
|
3210 self.ui.debug( |
|
3211 b'reusing manifest from p1 (listed files ' |
|
3212 b'actually unchanged)\n' |
|
3213 ) |
|
3214 mn = p1.manifestnode() |
|
3215 |
|
3216 if writecopiesto == b'changeset-only': |
|
3217 # If writing only to changeset extras, use None to indicate that |
|
3218 # no entry should be written. If writing to both, write an empty |
|
3219 # entry to prevent the reader from falling back to reading |
|
3220 # filelogs. |
|
3221 p1copies = p1copies or None |
|
3222 p2copies = p2copies or None |
|
3223 filesadded = filesadded or None |
|
3224 filesremoved = filesremoved or None |
|
3225 |
|
3226 if origctx and origctx.manifestnode() == mn: |
|
3227 files = origctx.files() |
|
3228 |
|
3229 # update changelog |
|
3230 self.ui.note(_(b"committing changelog\n")) |
|
3231 self.changelog.delayupdate(tr) |
|
3232 n = self.changelog.add( |
|
3233 mn, |
|
3234 files, |
|
3235 ctx.description(), |
|
3236 trp, |
|
3237 p1.node(), |
|
3238 p2.node(), |
|
3239 user, |
|
3240 ctx.date(), |
|
3241 ctx.extra().copy(), |
|
3242 p1copies, |
|
3243 p2copies, |
|
3244 filesadded, |
|
3245 filesremoved, |
|
3246 ) |
|
3247 xp1, xp2 = p1.hex(), p2 and p2.hex() or b'' |
|
3248 self.hook( |
|
3249 b'pretxncommit', |
|
3250 throw=True, |
|
3251 node=hex(n), |
|
3252 parent1=xp1, |
|
3253 parent2=xp2, |
|
3254 ) |
|
3255 # set the new commit is proper phase |
|
3256 targetphase = subrepoutil.newcommitphase(self.ui, ctx) |
|
3257 if targetphase: |
|
3258 # retract boundary do not alter parent changeset. |
|
3259 # if a parent have higher the resulting phase will |
|
3260 # be compliant anyway |
|
3261 # |
|
3262 # if minimal phase was 0 we don't need to retract anything |
|
3263 phases.registernew(self, tr, targetphase, [n]) |
|
3264 return n |
|
3265 |
3071 |
3266 @unfilteredmethod |
3072 @unfilteredmethod |
3267 def destroying(self): |
3073 def destroying(self): |
3268 '''Inform the repository that nodes are about to be destroyed. |
3074 '''Inform the repository that nodes are about to be destroyed. |
3269 Intended for use by strip and rollback, so there's a common |
3075 Intended for use by strip and rollback, so there's a common |