3183 See :hg:`help revisions` for more about specifying revisions. |
3186 See :hg:`help revisions` for more about specifying revisions. |
3184 |
3187 |
3185 Returns 0 on successful completion, 1 if there are unresolved files. |
3188 Returns 0 on successful completion, 1 if there are unresolved files. |
3186 """ |
3189 """ |
3187 with repo.wlock(): |
3190 with repo.wlock(): |
3188 return _dograft(ui, repo, *revs, **opts) |
3191 return graft_impl.cmd_graft(ui, repo, *revs, **opts) |
3189 |
|
3190 |
|
3191 def _dograft(ui, repo, *revs, **opts): |
|
3192 if revs and opts.get('rev'): |
|
3193 ui.warn( |
|
3194 _( |
|
3195 b'warning: inconsistent use of --rev might give unexpected ' |
|
3196 b'revision ordering!\n' |
|
3197 ) |
|
3198 ) |
|
3199 |
|
3200 revs = list(revs) |
|
3201 revs.extend(opts.get('rev')) |
|
3202 # a dict of data to be stored in state file |
|
3203 statedata = {} |
|
3204 # list of new nodes created by ongoing graft |
|
3205 statedata[b'newnodes'] = [] |
|
3206 |
|
3207 cmdutil.resolve_commit_options(ui, opts) |
|
3208 |
|
3209 editor = cmdutil.getcommiteditor(editform=b'graft', **opts) |
|
3210 |
|
3211 cmdutil.check_at_most_one_arg(opts, 'abort', 'stop', 'continue') |
|
3212 |
|
3213 cont = False |
|
3214 if opts.get('no_commit'): |
|
3215 cmdutil.check_incompatible_arguments( |
|
3216 opts, |
|
3217 'no_commit', |
|
3218 ['edit', 'currentuser', 'currentdate', 'log'], |
|
3219 ) |
|
3220 |
|
3221 graftstate = statemod.cmdstate(repo, b'graftstate') |
|
3222 |
|
3223 if opts.get('stop'): |
|
3224 cmdutil.check_incompatible_arguments( |
|
3225 opts, |
|
3226 'stop', |
|
3227 [ |
|
3228 'edit', |
|
3229 'log', |
|
3230 'user', |
|
3231 'date', |
|
3232 'currentdate', |
|
3233 'currentuser', |
|
3234 'rev', |
|
3235 ], |
|
3236 ) |
|
3237 return _stopgraft(ui, repo, graftstate) |
|
3238 elif opts.get('abort'): |
|
3239 cmdutil.check_incompatible_arguments( |
|
3240 opts, |
|
3241 'abort', |
|
3242 [ |
|
3243 'edit', |
|
3244 'log', |
|
3245 'user', |
|
3246 'date', |
|
3247 'currentdate', |
|
3248 'currentuser', |
|
3249 'rev', |
|
3250 ], |
|
3251 ) |
|
3252 return cmdutil.abortgraft(ui, repo, graftstate) |
|
3253 elif opts.get('continue'): |
|
3254 cont = True |
|
3255 if revs: |
|
3256 raise error.InputError(_(b"can't specify --continue and revisions")) |
|
3257 # read in unfinished revisions |
|
3258 if graftstate.exists(): |
|
3259 statedata = cmdutil.readgraftstate(repo, graftstate) |
|
3260 if statedata.get(b'date'): |
|
3261 opts['date'] = statedata[b'date'] |
|
3262 if statedata.get(b'user'): |
|
3263 opts['user'] = statedata[b'user'] |
|
3264 if statedata.get(b'log'): |
|
3265 opts['log'] = True |
|
3266 if statedata.get(b'no_commit'): |
|
3267 opts['no_commit'] = statedata.get(b'no_commit') |
|
3268 if statedata.get(b'base'): |
|
3269 opts['base'] = statedata.get(b'base') |
|
3270 nodes = statedata[b'nodes'] |
|
3271 revs = [repo[node].rev() for node in nodes] |
|
3272 else: |
|
3273 cmdutil.wrongtooltocontinue(repo, _(b'graft')) |
|
3274 else: |
|
3275 if not revs: |
|
3276 raise error.InputError(_(b'no revisions specified')) |
|
3277 cmdutil.checkunfinished(repo) |
|
3278 cmdutil.bailifchanged(repo) |
|
3279 revs = logcmdutil.revrange(repo, revs) |
|
3280 |
|
3281 skipped = set() |
|
3282 basectx = None |
|
3283 if opts.get('base'): |
|
3284 basectx = logcmdutil.revsingle(repo, opts['base'], None) |
|
3285 if basectx is None: |
|
3286 # check for merges |
|
3287 for rev in repo.revs(b'%ld and merge()', revs): |
|
3288 ui.warn(_(b'skipping ungraftable merge revision %d\n') % rev) |
|
3289 skipped.add(rev) |
|
3290 revs = [r for r in revs if r not in skipped] |
|
3291 if not revs: |
|
3292 return -1 |
|
3293 if basectx is not None and len(revs) != 1: |
|
3294 raise error.InputError(_(b'only one revision allowed with --base ')) |
|
3295 |
|
3296 # Don't check in the --continue case, in effect retaining --force across |
|
3297 # --continues. That's because without --force, any revisions we decided to |
|
3298 # skip would have been filtered out here, so they wouldn't have made their |
|
3299 # way to the graftstate. With --force, any revisions we would have otherwise |
|
3300 # skipped would not have been filtered out, and if they hadn't been applied |
|
3301 # already, they'd have been in the graftstate. |
|
3302 if not (cont or opts.get('force')) and basectx is None: |
|
3303 # check for ancestors of dest branch |
|
3304 ancestors = repo.revs(b'%ld & (::.)', revs) |
|
3305 for rev in ancestors: |
|
3306 ui.warn(_(b'skipping ancestor revision %d:%s\n') % (rev, repo[rev])) |
|
3307 |
|
3308 revs = [r for r in revs if r not in ancestors] |
|
3309 |
|
3310 if not revs: |
|
3311 return -1 |
|
3312 |
|
3313 # analyze revs for earlier grafts |
|
3314 ids = {} |
|
3315 for ctx in repo.set(b"%ld", revs): |
|
3316 ids[ctx.hex()] = ctx.rev() |
|
3317 n = ctx.extra().get(b'source') |
|
3318 if n: |
|
3319 ids[n] = ctx.rev() |
|
3320 |
|
3321 # check ancestors for earlier grafts |
|
3322 ui.debug(b'scanning for duplicate grafts\n') |
|
3323 |
|
3324 # The only changesets we can be sure doesn't contain grafts of any |
|
3325 # revs, are the ones that are common ancestors of *all* revs: |
|
3326 for rev in repo.revs(b'only(%d,ancestor(%ld))', repo[b'.'].rev(), revs): |
|
3327 ctx = repo[rev] |
|
3328 n = ctx.extra().get(b'source') |
|
3329 if n in ids: |
|
3330 try: |
|
3331 r = repo[n].rev() |
|
3332 except error.RepoLookupError: |
|
3333 r = None |
|
3334 if r in revs: |
|
3335 ui.warn( |
|
3336 _( |
|
3337 b'skipping revision %d:%s ' |
|
3338 b'(already grafted to %d:%s)\n' |
|
3339 ) |
|
3340 % (r, repo[r], rev, ctx) |
|
3341 ) |
|
3342 revs.remove(r) |
|
3343 elif ids[n] in revs: |
|
3344 if r is None: |
|
3345 ui.warn( |
|
3346 _( |
|
3347 b'skipping already grafted revision %d:%s ' |
|
3348 b'(%d:%s also has unknown origin %s)\n' |
|
3349 ) |
|
3350 % (ids[n], repo[ids[n]], rev, ctx, n[:12]) |
|
3351 ) |
|
3352 else: |
|
3353 ui.warn( |
|
3354 _( |
|
3355 b'skipping already grafted revision %d:%s ' |
|
3356 b'(%d:%s also has origin %d:%s)\n' |
|
3357 ) |
|
3358 % (ids[n], repo[ids[n]], rev, ctx, r, n[:12]) |
|
3359 ) |
|
3360 revs.remove(ids[n]) |
|
3361 elif ctx.hex() in ids: |
|
3362 r = ids[ctx.hex()] |
|
3363 if r in revs: |
|
3364 ui.warn( |
|
3365 _( |
|
3366 b'skipping already grafted revision %d:%s ' |
|
3367 b'(was grafted from %d:%s)\n' |
|
3368 ) |
|
3369 % (r, repo[r], rev, ctx) |
|
3370 ) |
|
3371 revs.remove(r) |
|
3372 if not revs: |
|
3373 return -1 |
|
3374 |
|
3375 if opts.get('no_commit'): |
|
3376 statedata[b'no_commit'] = True |
|
3377 if opts.get('base'): |
|
3378 statedata[b'base'] = opts['base'] |
|
3379 for pos, ctx in enumerate(repo.set(b"%ld", revs)): |
|
3380 desc = b'%d:%s "%s"' % ( |
|
3381 ctx.rev(), |
|
3382 ctx, |
|
3383 ctx.description().split(b'\n', 1)[0], |
|
3384 ) |
|
3385 names = repo.nodetags(ctx.node()) + repo.nodebookmarks(ctx.node()) |
|
3386 if names: |
|
3387 desc += b' (%s)' % b' '.join(names) |
|
3388 ui.status(_(b'grafting %s\n') % desc) |
|
3389 if opts.get('dry_run'): |
|
3390 continue |
|
3391 |
|
3392 source = ctx.extra().get(b'source') |
|
3393 extra = {} |
|
3394 if source: |
|
3395 extra[b'source'] = source |
|
3396 extra[b'intermediate-source'] = ctx.hex() |
|
3397 else: |
|
3398 extra[b'source'] = ctx.hex() |
|
3399 user = ctx.user() |
|
3400 if opts.get('user'): |
|
3401 user = opts['user'] |
|
3402 statedata[b'user'] = user |
|
3403 date = ctx.date() |
|
3404 if opts.get('date'): |
|
3405 date = opts['date'] |
|
3406 statedata[b'date'] = date |
|
3407 message = ctx.description() |
|
3408 if opts.get('log'): |
|
3409 message += b'\n(grafted from %s)' % ctx.hex() |
|
3410 statedata[b'log'] = True |
|
3411 |
|
3412 # we don't merge the first commit when continuing |
|
3413 if not cont: |
|
3414 # perform the graft merge with p1(rev) as 'ancestor' |
|
3415 overrides = {(b'ui', b'forcemerge'): opts.get('tool', b'')} |
|
3416 base = ctx.p1() if basectx is None else basectx |
|
3417 with ui.configoverride(overrides, b'graft'): |
|
3418 stats = mergemod.graft( |
|
3419 repo, ctx, base, [b'local', b'graft', b'parent of graft'] |
|
3420 ) |
|
3421 # report any conflicts |
|
3422 if stats.unresolvedcount > 0: |
|
3423 # write out state for --continue |
|
3424 nodes = [repo[rev].hex() for rev in revs[pos:]] |
|
3425 statedata[b'nodes'] = nodes |
|
3426 stateversion = 1 |
|
3427 graftstate.save(stateversion, statedata) |
|
3428 ui.error(_(b"abort: unresolved conflicts, can't continue\n")) |
|
3429 ui.error(_(b"(use 'hg resolve' and 'hg graft --continue')\n")) |
|
3430 return 1 |
|
3431 else: |
|
3432 cont = False |
|
3433 |
|
3434 # commit if --no-commit is false |
|
3435 if not opts.get('no_commit'): |
|
3436 node = repo.commit( |
|
3437 text=message, user=user, date=date, extra=extra, editor=editor |
|
3438 ) |
|
3439 if node is None: |
|
3440 ui.warn( |
|
3441 _(b'note: graft of %d:%s created no changes to commit\n') |
|
3442 % (ctx.rev(), ctx) |
|
3443 ) |
|
3444 # checking that newnodes exist because old state files won't have it |
|
3445 elif statedata.get(b'newnodes') is not None: |
|
3446 nn = statedata[b'newnodes'] |
|
3447 assert isinstance(nn, list) # list of bytes |
|
3448 nn.append(node) |
|
3449 |
|
3450 # remove state when we complete successfully |
|
3451 if not opts.get('dry_run'): |
|
3452 graftstate.delete() |
|
3453 |
|
3454 return 0 |
|
3455 |
|
3456 |
|
3457 def _stopgraft(ui, repo, graftstate): |
|
3458 """stop the interrupted graft""" |
|
3459 if not graftstate.exists(): |
|
3460 raise error.StateError(_(b"no interrupted graft found")) |
|
3461 pctx = repo[b'.'] |
|
3462 mergemod.clean_update(pctx) |
|
3463 graftstate.delete() |
|
3464 ui.status(_(b"stopped the interrupted graft\n")) |
|
3465 ui.status(_(b"working directory is now at %s\n") % pctx.hex()[:12]) |
|
3466 return 0 |
|
3467 |
3192 |
3468 |
3193 |
3469 statemod.addunfinished( |
3194 statemod.addunfinished( |
3470 b'graft', |
3195 b'graft', |
3471 fname=b'graftstate', |
3196 fname=b'graftstate', |