3.8 KB126 lines
Blame
1/**
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
5 * LICENSE file in the root directory of this source tree.
6 */
7
8import type {ChangedFile, CommitInfo, FilesSample, Hash, Result} from './types';
9
10import {Button} from 'isl-components/Button';
11import {Tooltip} from 'isl-components/Tooltip';
12import {useEffect, useState} from 'react';
13import {CancellationToken} from 'shared/CancellationToken';
14import {ComparisonType} from 'shared/Comparison';
15import {LRU} from 'shared/LRU';
16import serverAPI from './ClientToServerAPI';
17import {ChangedFiles} from './UncommittedChanges';
18import {t, T} from './i18n';
19
20// Cache fetches in progress so we don't double fetch
21const commitFilesCache = new LRU<Hash, Promise<Result<FilesSample>>>(10);
22export const __TEST__ = {commitFilesCache};
23
24/**
25 * The basic CommitInfo we fetch in bulk only contains the first 25 files,
26 * and is missing file statuses.
27 * But we want to be able to scroll through pages of files,
28 * and also see their statuses (added, removed, etc).
29 * So all files for the currently selected commit,
30 * to augment the subset we already have.
31 * Public commits don't show changed files by default for performance,
32 * but instead show a button used to fetch all the files.
33 */
34export function ChangedFilesWithFetching({commit}: {commit: CommitInfo}) {
35 const [fetchedAllFiles, setFetchedAllFiles] = useState<FilesSample | undefined>(undefined);
36
37 const [showingPublicWarning, setShowPublicWarning] = useState(commit.phase === 'public');
38 useEffect(() => {
39 setShowPublicWarning(commit.phase === 'public');
40 }, [commit.hash, commit.phase]);
41
42 useEffect(() => {
43 if (showingPublicWarning === true) {
44 return;
45 }
46
47 setFetchedAllFiles(undefined);
48 const cancel = new CancellationToken();
49 getChangedFilesForHash(commit.hash, undefined).then(result => {
50 if (cancel.isCancelled) {
51 return;
52 }
53 if (result.value != null) {
54 setFetchedAllFiles(result.value);
55 }
56 });
57 return () => {
58 cancel.cancel();
59 };
60 }, [commit.hash, showingPublicWarning]);
61
62 if (showingPublicWarning) {
63 return (
64 <Tooltip
65 title={t(
66 'Changed files are not loaded for public commits by default, for performance. Click to load changed files.',
67 )}>
68 <Button onClick={() => setShowPublicWarning(false)}>
69 <T>Load changed files</T>
70 </Button>
71 </Tooltip>
72 );
73 }
74
75 return (
76 <ChangedFiles
77 filesSubset={
78 fetchedAllFiles?.filesSample ??
79 commit.filePathsSample.map(
80 (filePath): ChangedFile => ({
81 path: filePath,
82 // default to 'modified' as a best guess.
83 // TODO: should this be a special loading status that shows a spinner?
84 status: 'M' as const,
85 }),
86 )
87 }
88 totalFiles={fetchedAllFiles?.totalFileCount ?? commit.totalFileCount}
89 comparison={
90 commit.isDot
91 ? {type: ComparisonType.HeadChanges}
92 : {
93 type: ComparisonType.Committed,
94 hash: commit.hash,
95 }
96 }
97 />
98 );
99}
100
101/**
102 * Get changed files in a given commit.
103 * A small subset of the files may have already been fetched,
104 * or in some cases no files may be cached yet and all files need to be fetched asynchronously. */
105export function getChangedFilesForHash(
106 hash: Hash,
107 limit?: number | undefined,
108): Promise<Result<FilesSample>> {
109 const foundPromise = commitFilesCache.get(hash);
110 if (foundPromise != null) {
111 return foundPromise;
112 }
113 serverAPI.postMessage({
114 type: 'fetchCommitChangedFiles',
115 hash,
116 limit,
117 });
118
119 const resultPromise = serverAPI
120 .nextMessageMatching('fetchedCommitChangedFiles', message => message.hash === hash)
121 .then(result => result.result);
122 commitFilesCache.set(hash, resultPromise);
123
124 return resultPromise;
125}
126