addons/isl/src/stackEdit/common.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 {RecordOf} from 'immutable';
b69ab319import type {Author, Hash, RepoPath} from 'shared/types/common';
b69ab3110import type {FileFlag} from 'shared/types/stack';
b69ab3111
b69ab3112import {Map as ImMap, Set as ImSet, List, Record, is} from 'immutable';
b69ab3113
b69ab3114/** Check if a path at the given commit is a rename. */
b69ab3115export function isRename(commit: CommitState, path: RepoPath): boolean {
b69ab3116 const files = commit.files;
b69ab3117 const copyFromPath = files.get(path)?.copyFrom;
b69ab3118 if (copyFromPath == null) {
b69ab3119 return false;
b69ab3120 }
b69ab3121 return isAbsent(files.get(copyFromPath));
b69ab3122}
b69ab3123
b69ab3124/** Test if a file is absent. */
b69ab3125export function isAbsent(file: FileState | FileMetadata | undefined): boolean {
b69ab3126 if (file == null) {
b69ab3127 return true;
b69ab3128 }
b69ab3129 return file.flags === ABSENT_FLAG;
b69ab3130}
b69ab3131
b69ab3132/** Test if a file has utf-8 content. */
b69ab3133export function isUtf8(file: FileState): boolean {
b69ab3134 return typeof file.data === 'string' || file.data instanceof FileIdx;
b69ab3135}
b69ab3136
b69ab3137/** Test if 2 files have the same content, ignoring "copyFrom". */
b69ab3138export function isContentSame(file1: FileState, file2: FileState): boolean {
b69ab3139 return is(file1.data, file2.data) && (file1.flags ?? '') === (file2.flags ?? '');
b69ab3140}
b69ab3141
b69ab3142/** Extract metadata */
b69ab3143export function toMetadata(file: FileState): FileMetadata {
b69ab3144 return FileMetadata({copyFrom: file.copyFrom, flags: file.flags});
b69ab3145}
b69ab3146
b69ab3147type DateTupleProps = {
b69ab3148 /** UTC Unix timestamp in seconds. */
b69ab3149 unix: number;
b69ab3150 /** Timezone offset in minutes. */
b69ab3151 tz: number;
b69ab3152};
b69ab3153
b69ab3154export const DateTuple = Record<DateTupleProps>({unix: 0, tz: 0});
b69ab3155export type DateTuple = RecordOf<DateTupleProps>;
b69ab3156
b69ab3157/** Mutable commit state. */
b69ab3158export type CommitStateProps = {
b69ab3159 rev: CommitRev;
b69ab3160 /** Original hashes. Used for "predecessor" information. */
b69ab3161 originalNodes: ImSet<Hash>;
b69ab3162 /**
b69ab3163 * Unique identifier within the stack. Useful for React animation.
b69ab3164 *
b69ab3165 * Note this should not be a random string, since we expect the CommitState[]
b69ab3166 * state to be purely derived from the initial ExportStack. It makes it easier
b69ab3167 * to check what commits are actually modified by just comparing CommitStates.
b69ab3168 * The "skip unchanged commits" logic is used by `calculateImportStack()`.
b69ab3169 *
b69ab3170 * We use commit hashes initially. When there is a split or add a new commit,
b69ab3171 * we assign new keys in a predicable (non-random) way. This property is
b69ab3172 * never empty, unlike `originalNodes`.
b69ab3173 */
b69ab3174 key: string;
b69ab3175 author: Author;
b69ab3176 date: DateTuple;
b69ab3177 /** Commit message. */
b69ab3178 text: string;
b69ab3179 /**
b69ab3180 * - hash: commit hash is immutable; this commit and ancestors
b69ab3181 * cannot be edited in any way.
b69ab3182 * - content: file contents are immutable; commit hash can change
b69ab3183 * if ancestors are changed.
b69ab3184 * - diff: file changes (diff) are immutable; file contents or
b69ab3185 * commit hash can change if ancestors are changed.
b69ab3186 * - none: nothing is immutable; this commit can be edited.
b69ab3187 */
b69ab3188 immutableKind: 'hash' | 'content' | 'diff' | 'none';
b69ab3189 /** Parent commits. */
b69ab3190 parents: List<CommitRev>;
b69ab3191 /** Changed files. */
b69ab3192 files: ImMap<RepoPath, FileState>;
b69ab3193};
b69ab3194
b69ab3195export const CommitState = Record<CommitStateProps>({
b69ab3196 rev: 0 as CommitRev,
b69ab3197 originalNodes: ImSet(),
b69ab3198 key: '',
b69ab3199 author: '',
b69ab31100 date: DateTuple(),
b69ab31101 text: '',
b69ab31102 immutableKind: 'none',
b69ab31103 parents: List(),
b69ab31104 files: ImMap(),
b69ab31105});
b69ab31106export type CommitState = RecordOf<CommitStateProps>;
b69ab31107
b69ab31108/**
b69ab31109 * Similar to `ExportFile` but `data` can be lazy by redirecting to a rev in a file stack.
b69ab31110 * Besides, supports "absent" state.
b69ab31111 */
b69ab31112type FileStateProps = {
b69ab31113 data: string | Base85 | FileIdx | DataRef;
b69ab31114} & FileMetadataProps;
b69ab31115
b69ab31116/**
b69ab31117 * File metadata properties without file content.
b69ab31118 */
b69ab31119type FileMetadataProps = {
b69ab31120 /** If present, this file is copied (or renamed) from another file. */
b69ab31121 copyFrom?: RepoPath;
b69ab31122 /**
b69ab31123 * If present, whether this file is special (symlink, submodule, deleted,
b69ab31124 * executable).
b69ab31125 */
b69ab31126 flags?: FileFlag;
b69ab31127};
b69ab31128
b69ab31129type Base85Props = {dataBase85: string};
b69ab31130export const Base85 = Record<Base85Props>({dataBase85: ''});
b69ab31131export type Base85 = RecordOf<Base85Props>;
b69ab31132
b69ab31133type DataRefProps = {node: Hash; path: RepoPath};
b69ab31134export const DataRef = Record<DataRefProps>({node: '', path: ''});
b69ab31135export type DataRef = RecordOf<DataRefProps>;
b69ab31136
b69ab31137export const FileState = Record<FileStateProps>({data: '', copyFrom: undefined, flags: ''});
b69ab31138export type FileState = RecordOf<FileStateProps>;
b69ab31139
b69ab31140export const FileMetadata = Record<FileMetadataProps>({copyFrom: undefined, flags: ''});
b69ab31141export type FileMetadata = RecordOf<FileMetadataProps>;
b69ab31142
b69ab31143export type FileStackIndex = number;
b69ab31144
b69ab31145type FileIdxProps = {
b69ab31146 fileIdx: FileStackIndex;
b69ab31147 fileRev: FileRev;
b69ab31148};
b69ab31149
b69ab31150type CommitIdxProps = {
b69ab31151 rev: CommitRev;
b69ab31152 path: RepoPath;
b69ab31153};
b69ab31154
b69ab31155export const FileIdx = Record<FileIdxProps>({fileIdx: 0, fileRev: 0 as FileRev});
b69ab31156export type FileIdx = RecordOf<FileIdxProps>;
b69ab31157
b69ab31158export const CommitIdx = Record<CommitIdxProps>({rev: -1 as CommitRev, path: ''});
b69ab31159export type CommitIdx = RecordOf<CommitIdxProps>;
b69ab31160
b69ab31161export const ABSENT_FLAG = 'a';
b69ab31162
b69ab31163/**
b69ab31164 * Represents an absent (or deleted) file.
b69ab31165 *
b69ab31166 * Helps simplify `null` handling logic. Since `data` is a regular
b69ab31167 * string, an absent file can be compared (data-wise) with its
b69ab31168 * adjacent versions and edited. This makes it easier to, for example,
b69ab31169 * split a newly added file.
b69ab31170 */
b69ab31171export const ABSENT_FILE = FileState({
b69ab31172 data: '',
b69ab31173 flags: ABSENT_FLAG,
b69ab31174});
b69ab31175
b69ab31176/** A revision number used in the `FileStackState`. Identifies a version of a multi-version file. */
b69ab31177export type FileRev = number & {__brand: 'FileStackRev'};
b69ab31178
b69ab31179/** A revision number used in the `CommitStackState`. Identifies a commit in the stack. */
b69ab31180export type CommitRev = number & {__branded: 'CommitRev'};
b69ab31181
b69ab31182// Re-export
b69ab31183export type {FileFlag};