addons/isl/src/operations/AmendOperation.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 {ImportAmendCommit, ImportStack} from 'shared/types/stack';
b69ab319import type {AmendRestackBehavior} from '../RestackBehavior';
b69ab3110import type {PartialSelection} from '../partialSelection';
b69ab3111import type {
b69ab3112 ApplyUncommittedChangesPreviewsFuncType,
b69ab3113 Dag,
b69ab3114 UncommittedChangesPreviewContext,
b69ab3115} from '../previews';
b69ab3116import type {CommandArg, CommitInfo, Hash, RepoRelativePath, UncommittedChanges} from '../types';
b69ab3117
b69ab3118import {restackBehaviorAtom} from '../RestackBehavior';
b69ab3119import {t} from '../i18n';
b69ab3120import {readAtom} from '../jotaiUtils';
b69ab3121import {authorString} from '../serverAPIState';
b69ab3122import {Operation} from './Operation';
b69ab3123
b69ab3124export class AmendOperation extends Operation {
b69ab3125 /**
b69ab3126 * @param filePathsToAmend if provided, only these file paths will be included in the amend operation. If undefined, ALL uncommitted changes are included. Paths should be relative to repo root.
b69ab3127 * @param message if provided, update commit description to use this title & description
b69ab3128 */
b69ab3129 constructor(
b69ab3130 private filePathsToAmend?: Array<RepoRelativePath>,
b69ab3131 public message?: string,
b69ab3132 public author?: string,
b69ab3133 ) {
b69ab3134 super(filePathsToAmend ? 'AmendFileSubsetOperation' : 'AmendOperation');
b69ab3135
b69ab3136 this.restackBehavior = readAtom(restackBehaviorAtom);
b69ab3137 }
b69ab3138
b69ab3139 restackBehavior: AmendRestackBehavior;
b69ab3140
b69ab3141 static opName = 'Amend';
b69ab3142
b69ab3143 getArgs() {
b69ab3144 const args: Array<CommandArg> = [
b69ab3145 {type: 'config', key: 'amend.autorestack', value: this.restackBehavior},
b69ab3146 'amend',
b69ab3147 '--addremove',
b69ab3148 ];
b69ab3149 if (this.filePathsToAmend) {
b69ab3150 args.push(
b69ab3151 ...this.filePathsToAmend.map(file =>
b69ab3152 // tag file arguments specially so the remote repo can convert them to the proper cwd-relative format.
b69ab3153 ({
b69ab3154 type: 'repo-relative-file' as const,
b69ab3155 path: file,
b69ab3156 }),
b69ab3157 ),
b69ab3158 );
b69ab3159 }
b69ab3160
b69ab3161 if (this.author) {
b69ab3162 args.push('--user', this.author);
b69ab3163 }
b69ab3164 if (this.message) {
b69ab3165 args.push('--message', this.message);
b69ab3166 }
b69ab3167 return args;
b69ab3168 }
b69ab3169
b69ab3170 makeOptimisticUncommittedChangesApplier?(
b69ab3171 context: UncommittedChangesPreviewContext,
b69ab3172 ): ApplyUncommittedChangesPreviewsFuncType | undefined {
b69ab3173 const filesToAmend = new Set(this.filePathsToAmend);
b69ab3174 if (
b69ab3175 context.uncommittedChanges.length === 0 ||
b69ab3176 (filesToAmend.size > 0 &&
b69ab3177 context.uncommittedChanges.every(change => !filesToAmend.has(change.path)))
b69ab3178 ) {
b69ab3179 return undefined;
b69ab3180 }
b69ab3181
b69ab3182 const func: ApplyUncommittedChangesPreviewsFuncType = (changes: UncommittedChanges) => {
b69ab3183 if (this.filePathsToAmend != null) {
b69ab3184 return changes.filter(change => !filesToAmend.has(change.path));
b69ab3185 } else {
b69ab3186 return [];
b69ab3187 }
b69ab3188 };
b69ab3189 return func;
b69ab3190 }
b69ab3191
b69ab3192 // Bump the timestamp and update the commit message.
b69ab3193 optimisticDag(dag: Dag): Dag {
b69ab3194 const head = dag.resolve('.');
b69ab3195 if (head?.hash == null) {
b69ab3196 return dag;
b69ab3197 }
b69ab3198 // XXX: amend's auto restack does not bump timestamp yet. We should fix that
b69ab3199 // and remove includeDescendants here.
b69ab31100 return dag.touch(head.hash, false /* includeDescendants */).replaceWith(head.hash, (_h, c) => {
b69ab31101 if (this.message == null) {
b69ab31102 return c;
b69ab31103 }
b69ab31104 const [title] = this.message.split(/\n+/, 1);
b69ab31105 const description = this.message.slice(title.length);
b69ab31106 // TODO: we should also update `filesSample` after amending.
b69ab31107 // These files are visible in the commit info view during optimistic state.
b69ab31108 return c?.merge({title, description});
b69ab31109 });
b69ab31110 }
b69ab31111}
b69ab31112
b69ab31113export class PartialAmendOperation extends Operation {
b69ab31114 /**
b69ab31115 * See also `AmendOperation`. This operation takes a `PartialSelection` and
b69ab31116 * uses `debugimportstack` under the hood, to achieve `amend -i` effect.
b69ab31117 */
b69ab31118 constructor(
b69ab31119 public message: string | undefined,
b69ab31120 private originalHeadHash: Hash,
b69ab31121 private selection: PartialSelection,
b69ab31122 // We need "selected" or "all" files since `selection` only tracks deselected files.
b69ab31123 private allFiles: Array<RepoRelativePath>,
b69ab31124 ) {
b69ab31125 super('PartialAmendOperation');
b69ab31126 }
b69ab31127
b69ab31128 getArgs(): CommandArg[] {
b69ab31129 return ['debugimportstack'];
b69ab31130 }
b69ab31131
b69ab31132 getStdin(): string | undefined {
b69ab31133 const files = this.selection.calculateImportStackFiles(this.allFiles);
b69ab31134 const commitInfo: ImportAmendCommit = {
b69ab31135 mark: ':1',
b69ab31136 node: this.originalHeadHash,
b69ab31137 files,
b69ab31138 };
b69ab31139 if (this.message) {
b69ab31140 commitInfo.text = this.message;
b69ab31141 }
b69ab31142 const importStack: ImportStack = [
b69ab31143 ['amend', commitInfo],
b69ab31144 ['reset', {mark: ':1'}],
b69ab31145 ];
b69ab31146 return JSON.stringify(importStack);
b69ab31147 }
b69ab31148
b69ab31149 getDescriptionForDisplay() {
b69ab31150 return {
b69ab31151 description: t('Amending selected changes'),
b69ab31152 tooltip: t(
b69ab31153 'This operation does not have a traditional command line equivalent. \n' +
b69ab31154 'You can use `amend -i` on the command line to select changes to amend.',
b69ab31155 ),
b69ab31156 };
b69ab31157 }
b69ab31158}
b69ab31159
b69ab31160/** Choose `PartialAmendOperation` or `AmendOperation` based on input. */
b69ab31161export function getAmendOperation(
b69ab31162 message: string | undefined,
b69ab31163 originalHead: CommitInfo | undefined,
b69ab31164 selection: PartialSelection,
b69ab31165 allFiles: Array<RepoRelativePath>,
b69ab31166): AmendOperation | PartialAmendOperation {
b69ab31167 const originalHeadHash = originalHead?.hash ?? '.';
b69ab31168 const intendedAuthor = readAtom(authorString);
b69ab31169 const authorArg =
b69ab31170 intendedAuthor != null && originalHead?.author !== intendedAuthor ? intendedAuthor : undefined;
b69ab31171 if (selection.hasChunkSelection()) {
b69ab31172 return new PartialAmendOperation(message, originalHeadHash, selection, allFiles);
b69ab31173 } else if (selection.isEverythingSelected(() => allFiles)) {
b69ab31174 return new AmendOperation(undefined, message, authorArg);
b69ab31175 } else {
b69ab31176 const selectedFiles = allFiles.filter(path => selection.isFullyOrPartiallySelected(path));
b69ab31177 return new AmendOperation(selectedFiles, message, authorArg);
b69ab31178 }
b69ab31179}