Skip to content

Commit 75caad1

Browse files
committed
Makes GitLens XDG-compatible
1 parent 21cbf8a commit 75caad1

File tree

7 files changed

+137
-72
lines changed

7 files changed

+137
-72
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p
1111
- Adds go to home view button to the commit graph title section — closes [#3873](https://github.com/gitkraken/vscode-gitlens/issues/3873)
1212
- Adds a _Contributors_ section to comparison results in the views
1313

14+
### Changed
15+
16+
- Makes GitLens XDG-compatible— closes [#3660](https://github.com/gitkraken/vscode-gitlens/issues/3660)
17+
1418
### Fixed
1519

1620
- Fixes [#3888](https://github.com/gitkraken/vscode-gitlens/issues/#3888) - Graph hover should disappear when right-clicking a row

ThirdPartyNotices.txt

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ This project incorporates components from the projects listed below.
3333
28. signal-utils version 0.20.0 (https://github.com/proposal-signals/signal-utils)
3434
29. slug version 10.0.0 (https://github.com/Trott/slug)
3535
30. sortablejs version 1.15.0 (https://github.com/SortableJS/Sortable)
36+
31. xdg-basedir version 5.1.0 (https://github.com/sindresorhus/xdg-basedir)
3637

3738
%% @gk-nzaytsev/fast-string-truncated-width NOTICES AND INFORMATION BEGIN HERE
3839
=========================================
@@ -2244,4 +2245,19 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22442245
SOFTWARE.
22452246

22462247
=========================================
2247-
END OF sortablejs NOTICES AND INFORMATION
2248+
END OF sortablejs NOTICES AND INFORMATION
2249+
2250+
%% xdg-basedir NOTICES AND INFORMATION BEGIN HERE
2251+
=========================================
2252+
MIT License
2253+
2254+
Copyright (c) Sindre Sorhus <[email protected]> (https://sindresorhus.com)
2255+
2256+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
2257+
2258+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
2259+
2260+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
2261+
2262+
=========================================
2263+
END OF xdg-basedir NOTICES AND INFORMATION

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20030,7 +20030,8 @@
2003020030
"react-dom": "16.8.4",
2003120031
"signal-utils": "0.20.0",
2003220032
"slug": "10.0.0",
20033-
"sortablejs": "1.15.0"
20033+
"sortablejs": "1.15.0",
20034+
"xdg-basedir": "^5.1.0"
2003420035
},
2003520036
"devDependencies": {
2003620037
"@eamodio/eslint-lite-webpack-plugin": "0.2.0",

pnpm-lock.yaml

Lines changed: 9 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/env/node/pathMapping/repositoryLocalPathMappingProvider.ts

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,7 @@ import type { Container } from '../../../container';
44
import type { LocalRepoDataMap } from '../../../pathMapping/models';
55
import type { RepositoryPathMappingProvider } from '../../../pathMapping/repositoryPathMappingProvider';
66
import { Logger } from '../../../system/logger';
7-
import {
8-
acquireSharedFolderWriteLock,
9-
getSharedRepositoryMappingFileUri,
10-
releaseSharedFolderWriteLock,
11-
} from './sharedGKDataFolder';
7+
import SharedGKDataFolderMapper from './sharedGKDataFolder';
128

139
export class RepositoryLocalPathMappingProvider implements RepositoryPathMappingProvider, Disposable {
1410
constructor(private readonly container: Container) {}
@@ -58,7 +54,7 @@ export class RepositoryLocalPathMappingProvider implements RepositoryPathMapping
5854
}
5955

6056
private async loadLocalRepoDataMap() {
61-
const localFileUri = getSharedRepositoryMappingFileUri();
57+
const localFileUri = await SharedGKDataFolderMapper.getSharedRepositoryMappingFileUri();
6258
try {
6359
const data = await workspace.fs.readFile(localFileUri);
6460
this._localRepoDataMap = (JSON.parse(data.toString()) ?? {}) as LocalRepoDataMap;
@@ -86,7 +82,7 @@ export class RepositoryLocalPathMappingProvider implements RepositoryPathMapping
8682
}
8783

8884
private async _writeLocalRepoPath(key: string, localPath: string): Promise<void> {
89-
if (!key || !localPath || !(await acquireSharedFolderWriteLock())) {
85+
if (!key || !localPath || !(await SharedGKDataFolderMapper.acquireSharedFolderWriteLock())) {
9086
return;
9187
}
9288

@@ -103,13 +99,13 @@ export class RepositoryLocalPathMappingProvider implements RepositoryPathMapping
10399
this._localRepoDataMap[key].paths.push(localPath);
104100
}
105101

106-
const localFileUri = getSharedRepositoryMappingFileUri();
102+
const localFileUri = await SharedGKDataFolderMapper.getSharedRepositoryMappingFileUri();
107103
const outputData = new Uint8Array(Buffer.from(JSON.stringify(this._localRepoDataMap)));
108104
try {
109105
await workspace.fs.writeFile(localFileUri, outputData);
110106
} catch (error) {
111107
Logger.error(error, 'writeLocalRepoPath');
112108
}
113-
await releaseSharedFolderWriteLock();
109+
await SharedGKDataFolderMapper.releaseSharedFolderWriteLock();
114110
}
115111
}

src/env/node/pathMapping/sharedGKDataFolder.ts

Lines changed: 88 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,73 +1,118 @@
11
import os from 'os';
22
import path from 'path';
33
import { Uri, workspace } from 'vscode';
4+
import { xdgData } from 'xdg-basedir';
45
import { Logger } from '../../../system/logger';
56
import { wait } from '../../../system/promise';
67
import { getPlatform } from '../platform';
78

8-
export const sharedGKDataFolder = '.gk';
9+
/** @deprecated prefer using XDG paths */
10+
const legacySharedGKDataFolder = path.join(os.homedir(), '.gk');
911

10-
export async function acquireSharedFolderWriteLock(): Promise<boolean> {
11-
const lockFileUri = getSharedLockFileUri();
12+
class SharedGKDataFolderMapper {
13+
private _initPromise: Promise<void> | undefined;
14+
constructor(
15+
// do soft migration, use new folders only for new users (without existing folders)
16+
// eslint-disable-next-line @typescript-eslint/no-deprecated
17+
private sharedGKDataFolder = legacySharedGKDataFolder,
18+
private _isInitialized: boolean = false,
19+
) {}
1220

13-
let stat;
14-
while (true) {
21+
private async _initialize() {
22+
if (this._initPromise) {
23+
throw new Error('cannot be initialized multiple times');
24+
}
1525
try {
16-
stat = await workspace.fs.stat(lockFileUri);
26+
await workspace.fs.stat(Uri.file(this.sharedGKDataFolder));
1727
} catch {
18-
// File does not exist, so we can safely create it
19-
break;
28+
// Path does not exist, so we can safely use xdg paths it
29+
if (xdgData) {
30+
this.sharedGKDataFolder = path.join(xdgData, 'gk');
31+
} else {
32+
this.sharedGKDataFolder = path.join(os.homedir());
33+
}
34+
} finally {
35+
this._isInitialized = true;
2036
}
37+
}
2138

22-
const currentTime = new Date().getTime();
23-
if (currentTime - stat.ctime > 30000) {
24-
// File exists, but the timestamp is older than 30 seconds, so we can safely remove it
25-
break;
39+
private async waitForInitialized() {
40+
if (this._isInitialized) {
41+
return;
2642
}
27-
28-
// File exists, and the timestamp is less than 30 seconds old, so we need to wait for it to be removed
29-
await wait(100);
43+
if (!this._initPromise) {
44+
this._initPromise = this._initialize();
45+
}
46+
await this._initPromise;
3047
}
3148

32-
try {
33-
// write the lockfile to the shared data folder
34-
await workspace.fs.writeFile(lockFileUri, new Uint8Array(0));
35-
} catch (error) {
36-
Logger.error(error, 'acquireSharedFolderWriteLock');
37-
return false;
49+
private async getUri(relativeFilePath: string) {
50+
await this.waitForInitialized();
51+
return Uri.file(path.join(this.sharedGKDataFolder, relativeFilePath));
3852
}
3953

40-
return true;
41-
}
54+
async acquireSharedFolderWriteLock(): Promise<boolean> {
55+
const lockFileUri = await this.getUri('lockfile');
56+
57+
let stat;
58+
while (true) {
59+
try {
60+
stat = await workspace.fs.stat(lockFileUri);
61+
} catch {
62+
// File does not exist, so we can safely create it
63+
break;
64+
}
65+
66+
const currentTime = new Date().getTime();
67+
if (currentTime - stat.ctime > 30000) {
68+
// File exists, but the timestamp is older than 30 seconds, so we can safely remove it
69+
break;
70+
}
71+
72+
// File exists, and the timestamp is less than 30 seconds old, so we need to wait for it to be removed
73+
await wait(100);
74+
}
75+
76+
try {
77+
// write the lockfile to the shared data folder
78+
await workspace.fs.writeFile(lockFileUri, new Uint8Array(0));
79+
} catch (error) {
80+
Logger.error(error, 'acquireSharedFolderWriteLock');
81+
return false;
82+
}
4283

43-
export async function releaseSharedFolderWriteLock(): Promise<boolean> {
44-
try {
45-
const lockFileUri = getSharedLockFileUri();
46-
await workspace.fs.delete(lockFileUri);
47-
} catch (error) {
48-
Logger.error(error, 'releaseSharedFolderWriteLock');
49-
return false;
84+
return true;
5085
}
5186

52-
return true;
53-
}
87+
async releaseSharedFolderWriteLock(): Promise<boolean> {
88+
try {
89+
const lockFileUri = await this.getUri('lockfile');
90+
await workspace.fs.delete(lockFileUri);
91+
} catch (error) {
92+
Logger.error(error, 'releaseSharedFolderWriteLock');
93+
return false;
94+
}
5495

55-
function getSharedLockFileUri() {
56-
return Uri.file(path.join(os.homedir(), sharedGKDataFolder, 'lockfile'));
57-
}
96+
return true;
97+
}
5898

59-
export function getSharedRepositoryMappingFileUri() {
60-
return Uri.file(path.join(os.homedir(), sharedGKDataFolder, 'repoMapping.json'));
61-
}
99+
async getSharedRepositoryMappingFileUri() {
100+
return this.getUri('repoMapping.json');
101+
}
62102

63-
export function getSharedCloudWorkspaceMappingFileUri() {
64-
return Uri.file(path.join(os.homedir(), sharedGKDataFolder, 'cloudWorkspaces.json'));
65-
}
103+
async getSharedCloudWorkspaceMappingFileUri() {
104+
return this.getUri('cloudWorkspaces.json');
105+
}
66106

67-
export function getSharedLocalWorkspaceMappingFileUri() {
68-
return Uri.file(path.join(os.homedir(), sharedGKDataFolder, 'localWorkspaces.json'));
107+
async getSharedLocalWorkspaceMappingFileUri() {
108+
return this.getUri('localWorkspaces.json');
109+
}
69110
}
70111

112+
// export as a singleton
113+
// eslint-disable-next-line import-x/no-default-export
114+
export default new SharedGKDataFolderMapper();
115+
71116
export function getSharedLegacyLocalWorkspaceMappingFileUri() {
72117
return Uri.file(
73118
path.join(

0 commit comments

Comments
 (0)