25개 이상의 토픽을 선택하실 수 없습니다. Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

263 lines
8.3 KiB

  1. import type { Disposable, Event, ExtensionContext, SecretStorageChangeEvent } from 'vscode';
  2. import { EventEmitter } from 'vscode';
  3. import type { ViewShowBranchComparison } from './config';
  4. import type { StoredSearchQuery } from './git/search';
  5. import type { Subscription } from './subscription';
  6. import { debug } from './system/decorators/log';
  7. import type { TrackedUsage, TrackedUsageKeys } from './usageTracker';
  8. import type { CompletedActions } from './webviews/home/protocol';
  9. export type StorageChangeEvent =
  10. | {
  11. /**
  12. * The key of the stored value that has changed.
  13. */
  14. readonly key: keyof (GlobalStorage & DeprecatedGlobalStorage);
  15. readonly workspace: false;
  16. }
  17. | {
  18. /**
  19. * The key of the stored value that has changed.
  20. */
  21. readonly key: keyof (WorkspaceStorage & DeprecatedWorkspaceStorage);
  22. readonly workspace: true;
  23. };
  24. export class Storage implements Disposable {
  25. private _onDidChange = new EventEmitter<StorageChangeEvent>();
  26. get onDidChange(): Event<StorageChangeEvent> {
  27. return this._onDidChange.event;
  28. }
  29. private _onDidChangeSecrets = new EventEmitter<SecretStorageChangeEvent>();
  30. get onDidChangeSecrets(): Event<SecretStorageChangeEvent> {
  31. return this._onDidChangeSecrets.event;
  32. }
  33. private readonly _disposable: Disposable;
  34. constructor(private readonly context: ExtensionContext) {
  35. this._disposable = this.context.secrets.onDidChange(e => this._onDidChangeSecrets.fire(e));
  36. }
  37. dispose(): void {
  38. this._disposable.dispose();
  39. }
  40. get<T extends keyof GlobalStorage>(key: T): GlobalStorage[T] | undefined;
  41. /** @deprecated */
  42. get<T extends keyof DeprecatedGlobalStorage>(key: T): DeprecatedGlobalStorage[T] | undefined;
  43. get<T extends keyof GlobalStorage>(key: T, defaultValue: GlobalStorage[T]): GlobalStorage[T];
  44. @debug({ logThreshold: 50 })
  45. get(key: keyof (GlobalStorage & DeprecatedGlobalStorage), defaultValue?: unknown): unknown | undefined {
  46. return this.context.globalState.get(`gitlens:${key}`, defaultValue);
  47. }
  48. @debug({ logThreshold: 250 })
  49. async delete(key: keyof (GlobalStorage & DeprecatedGlobalStorage)): Promise<void> {
  50. await this.context.globalState.update(`gitlens:${key}`, undefined);
  51. this._onDidChange.fire({ key: key, workspace: false });
  52. }
  53. @debug({ args: { 1: false }, logThreshold: 250 })
  54. async store<T extends keyof GlobalStorage>(key: T, value: GlobalStorage[T] | undefined): Promise<void> {
  55. await this.context.globalState.update(`gitlens:${key}`, value);
  56. this._onDidChange.fire({ key: key, workspace: false });
  57. }
  58. @debug({ args: false, logThreshold: 250 })
  59. async getSecret(key: SecretKeys): Promise<string | undefined> {
  60. return this.context.secrets.get(key);
  61. }
  62. @debug({ args: false, logThreshold: 250 })
  63. async deleteSecret(key: SecretKeys): Promise<void> {
  64. return this.context.secrets.delete(key);
  65. }
  66. @debug({ args: false, logThreshold: 250 })
  67. async storeSecret(key: SecretKeys, value: string): Promise<void> {
  68. return this.context.secrets.store(key, value);
  69. }
  70. getWorkspace<T extends keyof WorkspaceStorage>(key: T): WorkspaceStorage[T] | undefined;
  71. /** @deprecated */
  72. getWorkspace<T extends keyof DeprecatedWorkspaceStorage>(key: T): DeprecatedWorkspaceStorage[T] | undefined;
  73. getWorkspace<T extends keyof WorkspaceStorage>(key: T, defaultValue: WorkspaceStorage[T]): WorkspaceStorage[T];
  74. @debug({ logThreshold: 25 })
  75. getWorkspace(
  76. key: keyof (WorkspaceStorage & DeprecatedWorkspaceStorage),
  77. defaultValue?: unknown,
  78. ): unknown | undefined {
  79. return this.context.workspaceState.get(`gitlens:${key}`, defaultValue);
  80. }
  81. @debug({ logThreshold: 250 })
  82. async deleteWorkspace(key: keyof (WorkspaceStorage & DeprecatedWorkspaceStorage)): Promise<void> {
  83. await this.context.workspaceState.update(`gitlens:${key}`, undefined);
  84. this._onDidChange.fire({ key: key, workspace: true });
  85. }
  86. @debug({ args: { 1: false }, logThreshold: 250 })
  87. async storeWorkspace(key: keyof WorkspaceStorage, value: unknown | undefined): Promise<void> {
  88. await this.context.workspaceState.update(`gitlens:${key}`, value);
  89. this._onDidChange.fire({ key: key, workspace: true });
  90. }
  91. }
  92. export type SecretKeys = string;
  93. export const enum SyncedStorageKeys {
  94. Version = 'gitlens:synced:version',
  95. PreReleaseVersion = 'gitlens:synced:preVersion',
  96. HomeViewWelcomeVisible = 'gitlens:views:welcome:visible',
  97. }
  98. export type DeprecatedGlobalStorage = {
  99. /** @deprecated */
  100. [key in `disallow:connection:${string}`]: any;
  101. };
  102. export type GlobalStorage = {
  103. avatars: [string, StoredAvatar][];
  104. 'deepLinks:pending': StoredDeepLinkContext;
  105. 'home:actions:completed': CompletedActions[];
  106. 'home:steps:completed': string[];
  107. 'home:sections:dismissed': string[];
  108. 'home:status:pinned': boolean;
  109. 'home:banners:dismissed': string[];
  110. pendingWelcomeOnFocus: boolean;
  111. pendingWhatsNewOnFocus: boolean;
  112. 'plus:migratedAuthentication': boolean;
  113. 'plus:discountNotificationShown': boolean;
  114. // Don't change this key name ('premium`) as its the stored subscription
  115. 'premium:subscription': Stored<Subscription>;
  116. 'synced:version': string;
  117. // Keep the pre-release version separate from the released version
  118. 'synced:preVersion': string;
  119. usages: Record<TrackedUsageKeys, TrackedUsage>;
  120. version: string;
  121. // Keep the pre-release version separate from the released version
  122. preVersion: string;
  123. 'views:layout': StoredViewsLayout;
  124. 'views:welcome:visible': boolean;
  125. 'views:commitDetails:dismissed': string[];
  126. } & { [key in `provider:authentication:skip:${string}`]: boolean };
  127. export type DeprecatedWorkspaceStorage = {
  128. /** @deprecated use `graph:filtersByRepo.excludeRefs` */
  129. 'graph:hiddenRefs': Record<string, StoredGraphExcludedRef>;
  130. /** @deprecated use `views:searchAndCompare:pinned` */
  131. 'pinned:comparisons': Record<string, DeprecatedPinnedComparison>;
  132. };
  133. export type WorkspaceStorage = {
  134. assumeRepositoriesOnStartup?: boolean;
  135. 'branch:comparisons': StoredBranchComparisons;
  136. 'gitComandPalette:usage': RecentUsage;
  137. gitPath: string;
  138. 'graph:banners:dismissed': Record<string, boolean>;
  139. 'graph:columns': Record<string, StoredGraphColumn>;
  140. 'graph:filtersByRepo': Record<string, StoredGraphFilters>;
  141. 'remote:default': string;
  142. 'starred:branches': StoredStarred;
  143. 'starred:repositories': StoredStarred;
  144. 'views:repositories:autoRefresh': boolean;
  145. 'views:searchAndCompare:keepResults': boolean;
  146. 'views:searchAndCompare:pinned': StoredPinnedItems;
  147. 'views:commitDetails:autolinksExpanded': boolean;
  148. } & { [key in `connected:${string}`]: boolean };
  149. export type StoredViewsLayout = 'gitlens' | 'scm';
  150. export interface Stored<T, SchemaVersion extends number = 1> {
  151. v: SchemaVersion;
  152. data: T;
  153. }
  154. export interface StoredAvatar {
  155. uri: string;
  156. timestamp: number;
  157. }
  158. export interface StoredBranchComparison {
  159. ref: string;
  160. notation: '..' | '...' | undefined;
  161. type: Exclude<ViewShowBranchComparison, false> | undefined;
  162. }
  163. export interface StoredBranchComparisons {
  164. [id: string]: string | StoredBranchComparison;
  165. }
  166. export interface StoredDeepLinkContext {
  167. url?: string | undefined;
  168. repoPath?: string | undefined;
  169. }
  170. export interface StoredGraphColumn {
  171. isHidden?: boolean;
  172. width?: number;
  173. }
  174. export interface StoredGraphFilters {
  175. includeOnlyRefs?: Record<string, StoredGraphIncludeOnlyRef>;
  176. excludeRefs?: Record<string, StoredGraphExcludedRef>;
  177. excludeTypes?: Record<string, boolean>;
  178. }
  179. export type StoredGraphRefType = 'head' | 'remote' | 'tag';
  180. export interface StoredGraphExcludedRef {
  181. id: string;
  182. type: StoredGraphRefType;
  183. name: string;
  184. owner?: string;
  185. }
  186. export interface StoredGraphIncludeOnlyRef {
  187. id: string;
  188. type: StoredGraphRefType;
  189. name: string;
  190. owner?: string;
  191. }
  192. export interface StoredNamedRef {
  193. label?: string;
  194. ref: string;
  195. }
  196. export interface StoredPinnedComparison {
  197. type: 'comparison';
  198. timestamp: number;
  199. path: string;
  200. ref1: StoredNamedRef;
  201. ref2: StoredNamedRef;
  202. notation?: '..' | '...';
  203. }
  204. export interface StoredPinnedSearch {
  205. type: 'search';
  206. timestamp: number;
  207. path: string;
  208. labels: {
  209. label: string;
  210. queryLabel:
  211. | string
  212. | {
  213. label: string;
  214. resultsType?: { singular: string; plural: string };
  215. };
  216. };
  217. search: StoredSearchQuery;
  218. }
  219. export type StoredPinnedItem = StoredPinnedComparison | StoredPinnedSearch;
  220. export type StoredPinnedItems = Record<string, StoredPinnedItem>;
  221. export type StoredStarred = Record<string, boolean>;
  222. export type RecentUsage = Record<string, number>;
  223. interface DeprecatedPinnedComparison {
  224. path: string;
  225. ref1: StoredNamedRef;
  226. ref2: StoredNamedRef;
  227. notation?: '..' | '...';
  228. }