addons/isl/src/operations/RebaseOperation.tsblame
View source
b69ab311/**
b69ab312 * Copyright (c) Meta Platforms, Inc. and affiliates.
b69ab313 *
b69ab314 * This source code is licensed under the MIT license found in the
b69ab315 * LICENSE file in the root directory of this source tree.
b69ab316 */
b69ab317
b69ab318import type {DagCommitInfo} from '../dag/dagCommitInfo';
b69ab319import type {Dag} from '../previews';
b69ab3110import type {ExactRevset, Hash, OptimisticRevset, SucceedableRevset} from '../types';
b69ab3111
b69ab3112import deepEqual from 'fast-deep-equal';
b69ab3113import {t} from '../i18n';
b69ab3114import {CommitPreview} from '../previews';
b69ab3115import {latestSuccessor} from '../successionUtils';
b69ab3116import {Operation} from './Operation';
b69ab3117
b69ab3118export class RebaseOperation extends Operation {
b69ab3119 constructor(
b69ab3120 private source: SucceedableRevset | ExactRevset | OptimisticRevset,
b69ab3121 private destination: SucceedableRevset | ExactRevset | OptimisticRevset,
b69ab3122 ) {
b69ab3123 super('RebaseOperation');
b69ab3124 }
b69ab3125
b69ab3126 static opName = 'Rebase';
b69ab3127
b69ab3128 equals(other?: Operation | null): boolean {
b69ab3129 return (
b69ab3130 other instanceof RebaseOperation &&
b69ab3131 deepEqual([this.source, this.destination], [other.source, other.destination])
b69ab3132 );
b69ab3133 }
b69ab3134
b69ab3135 getArgs() {
b69ab3136 return ['rebase', '-s', this.source, '-d', this.destination];
b69ab3137 }
b69ab3138
b69ab3139 getInitialInlineProgress(): Array<[string, string]> {
b69ab3140 // TODO: successions
b69ab3141 return [[this.source.revset, t('rebasing...')]];
b69ab3142 }
b69ab3143
b69ab3144 previewDag(dag: Dag): Dag {
b69ab3145 const srcHash = dag.resolve(latestSuccessor(dag, this.source))?.hash;
b69ab3146 const destHash = dag.resolve(latestSuccessor(dag, this.destination))?.hash;
b69ab3147 if (srcHash == null || destHash == null) {
b69ab3148 return dag;
b69ab3149 }
b69ab3150 const src = dag.descendants(srcHash);
b69ab3151 const srcHashes = src.toHashes().toArray();
b69ab3152 const prefix = `${REBASE_PREVIEW_HASH_PREFIX}:${destHash}:`;
b69ab3153 const rewriteHash = (h: Hash) => (src.contains(h) ? prefix + h : h);
b69ab3154 const date = new Date();
b69ab3155 const newCommits = srcHashes.flatMap(h => {
b69ab3156 const info = dag.get(h);
b69ab3157 if (info == null) {
b69ab3158 return [];
b69ab3159 }
b69ab3160 const isRoot = info.hash === srcHash;
b69ab3161 const newInfo: DagCommitInfo = info.merge({
b69ab3162 parents: isRoot ? [destHash] : info.parents,
b69ab3163 date,
b69ab3164 seqNumber: undefined,
b69ab3165 previewType: isRoot ? CommitPreview.REBASE_ROOT : CommitPreview.REBASE_DESCENDANT,
b69ab3166 });
b69ab3167 return [newInfo];
b69ab3168 });
b69ab3169 // Rewrite REBASE_OLD commits to use fake hash so they won't conflict with
b69ab3170 // the rebased commits. Insert new commits with the original hash so they
b69ab3171 // can be interacted.
b69ab3172 return dag
b69ab3173 .replaceWith(src, (h, c) =>
b69ab3174 c?.merge({
b69ab3175 hash: rewriteHash(h),
b69ab3176 parents: c.parents.map(rewriteHash),
b69ab3177 previewType: CommitPreview.REBASE_OLD,
b69ab3178 }),
b69ab3179 )
b69ab3180 .add(newCommits);
b69ab3181 }
b69ab3182
b69ab3183 optimisticDag(dag: Dag): Dag {
b69ab3184 const src = dag.resolve(latestSuccessor(dag, this.source))?.hash;
b69ab3185 const dest = dag.resolve(latestSuccessor(dag, this.destination))?.hash;
b69ab3186 // src already on dest?
b69ab3187 if (src && dest && dag.parentHashes(src).includes(dest)) {
b69ab3188 // The stack might be partially rebased while the rebase is ongoing.
b69ab3189 // For example, graph
b69ab3190 // a--b--c--d--e z
b69ab3191 // Rebasing b (and descendants) to z, we might observe:
b69ab3192 // a--bOld--cOld--d--e z--bNew--cNew
b69ab3193
b69ab3194 // bOld
b69ab3195 const srcOrig = dag.resolve(latestSuccessor(dag.remove(src), this.source))?.hash;
b69ab3196 // bOld + cOld + d + e
b69ab3197 const toRebase = dag.descendants(srcOrig);
b69ab3198 // bNew + cNew + d + e
b69ab3199 const successors = dag.followSuccessors(toRebase);
b69ab31100 // d + e
b69ab31101 const remainingToRebase = toRebase.intersect(successors);
b69ab31102 // cNew (simplified, does not handle merges)
b69ab31103 const newDest = dag.heads(dag.descendants(src).intersect(successors)).toHashes().first();
b69ab31104
b69ab31105 // To test this in a real repo, try adding these configs to slow down rebases:
b69ab31106 // --config "hooks.pretxncommit.slow=sleep 3"
b69ab31107 // --config "rebase.experimental.inmemory=false"
b69ab31108 // and goto the src stack top before rebasing.
b69ab31109 return dag.rebase(remainingToRebase, newDest);
b69ab31110 }
b69ab31111 return dag.rebase(dag.descendants(src), dest);
b69ab31112 }
b69ab31113}
b69ab31114
b69ab31115export const REBASE_PREVIEW_HASH_PREFIX = 'OPTIMISTIC_REBASE_PREVIEW';