Skip to content
This repository was archived by the owner on Oct 18, 2024. It is now read-only.

Commit 5fd1641

Browse files
authored
Merge pull request #155 from PizzaFactory/prp-update-to-the-upstream
[Scheduled] Update to the upstream
2 parents 88c43f1 + 9f8a0dd commit 5fd1641

File tree

2 files changed

+173
-30
lines changed

2 files changed

+173
-30
lines changed

extensions/eclipse-che-theia-git-provisioner/src/node/git-configuration-controller.ts

+122-19
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,25 @@ import * as ini from 'ini';
1313
import * as nsfw from 'nsfw';
1414

1515
import { CheGitClient, CheGitService, GIT_USER_EMAIL, GIT_USER_NAME } from '../common/git-protocol';
16-
import { createFile, pathExists, readFile, writeFile } from 'fs-extra';
16+
import {
17+
CheTheiaUserPreferencesSynchronizer,
18+
THEIA_USER_PREFERENCES_PATH,
19+
} from '@eclipse-che/theia-user-preferences-synchronizer/lib/node/che-theia-preferences-synchronizer';
20+
import { Disposable, Emitter } from '@theia/core';
21+
import { basename, dirname, resolve } from 'path';
22+
import {
23+
createFileSync,
24+
ensureDirSync,
25+
existsSync,
26+
pathExistsSync,
27+
readFileSync,
28+
readdirSync,
29+
watch,
30+
writeFileSync,
31+
} from 'fs-extra';
1732
import { inject, injectable } from 'inversify';
1833

19-
import { CheTheiaUserPreferencesSynchronizer } from '@eclipse-che/theia-user-preferences-synchronizer/lib/node/che-theia-preferences-synchronizer';
20-
import { Disposable } from '@theia/core';
2134
import { homedir } from 'os';
22-
import { resolve } from 'path';
2335

2436
export const GIT_USER_CONFIG_PATH = resolve(homedir(), '.gitconfig');
2537
export const GIT_GLOBAL_CONFIG_PATH = '/etc/gitconfig';
@@ -36,28 +48,113 @@ export interface GitConfiguration {
3648

3749
@injectable()
3850
export class GitConfigurationController implements CheGitService {
39-
@inject(CheTheiaUserPreferencesSynchronizer)
40-
protected preferencesService: CheTheiaUserPreferencesSynchronizer;
51+
constructor(
52+
@inject(CheTheiaUserPreferencesSynchronizer) protected preferencesService: CheTheiaUserPreferencesSynchronizer
53+
) {
54+
this.onGitClientSetEvent(async () => {
55+
await this.checkExistsWithTimeout(THEIA_USER_PREFERENCES_PATH, 60000);
56+
this.userGitconfigDirty = this.readConfigurationFromGitConfigFile(GIT_USER_CONFIG_PATH)!;
57+
const preferences = await this.preferencesService.getPreferences();
58+
await this.updateUserGitconfigFromPreferences(preferences);
59+
});
60+
this.onUserGitconfigChangedEvent(() => this.fetchLocalGitconfig());
61+
}
62+
63+
private checkExistsWithTimeout(filePath: string, timeout: number): Promise<void> {
64+
return new Promise((resolvePromise, reject) => {
65+
if (existsSync(filePath)) {
66+
resolvePromise();
67+
return;
68+
}
69+
const timer = setTimeout(() => {
70+
watcher.close();
71+
reject(new Error('File did not exists and was not created during the timeout.'));
72+
}, timeout);
73+
74+
const dir = dirname(filePath);
75+
ensureDirSync(dir);
76+
const pathBasename = basename(filePath);
77+
const watcher = watch(dir, (eventType, filename) => {
78+
if (eventType === 'rename' && filename === pathBasename) {
79+
clearTimeout(timer);
80+
watcher.close();
81+
resolvePromise();
82+
}
83+
});
84+
});
85+
}
86+
87+
private fetchLocalGitconfig(): void {
88+
const userGitconfig = this.readConfigurationFromGitConfigFile(GIT_USER_CONFIG_PATH)!;
89+
this.updateLocalGitconfig(userGitconfig);
90+
this.userGitconfigDirty = userGitconfig;
91+
}
4192

4293
protected preferencesHandler: Disposable | undefined;
4394

4495
protected gitConfigWatcher: nsfw.NSFW | undefined;
4596

4697
protected client: CheGitClient;
4798

99+
private readonly projectsRoot = process.env.PROJECTS_ROOT || process.env.CHE_PROJECTS_ROOT || '/projects';
100+
101+
private readonly onUserGitconfigChangedEmitter = new Emitter();
102+
private readonly onUserGitconfigChangedEvent = this.onUserGitconfigChangedEmitter.event;
103+
104+
private readonly onGitClientSetEmitter = new Emitter();
105+
private readonly onGitClientSetEvent = this.onGitClientSetEmitter.event;
106+
107+
private userGitconfigDirty: GitConfiguration;
108+
109+
private updateLocalGitconfig(gitconfig: GitConfiguration): void {
110+
readdirSync(this.projectsRoot, { withFileTypes: true })
111+
.filter(dir => dir.isDirectory())
112+
.forEach(dir => {
113+
const localGitconfigPath = resolve(this.projectsRoot, dir.name, '.git', 'config');
114+
let localGitconfig: GitConfiguration;
115+
if (existsSync(localGitconfigPath)) {
116+
localGitconfig = ini.parse(readFileSync(localGitconfigPath).toString());
117+
// Add missing values
118+
Object.keys(gitconfig).forEach(key => {
119+
if (
120+
localGitconfig[key] === undefined ||
121+
JSON.stringify(localGitconfig[key]) === JSON.stringify(this.userGitconfigDirty[key])
122+
) {
123+
localGitconfig[key] = gitconfig[key];
124+
}
125+
});
126+
// Remove deleted values
127+
Object.keys(localGitconfig).forEach(key => {
128+
if (
129+
gitconfig[key] === undefined &&
130+
JSON.stringify(localGitconfig[key]) === JSON.stringify(this.userGitconfigDirty[key])
131+
) {
132+
delete localGitconfig[key];
133+
}
134+
});
135+
} else {
136+
createFileSync(localGitconfigPath);
137+
localGitconfig = gitconfig;
138+
}
139+
writeFileSync(localGitconfigPath, ini.stringify(localGitconfig));
140+
});
141+
}
142+
48143
public async watchGitConfigChanges(): Promise<void> {
49144
if (this.gitConfigWatcher) {
50145
return;
51146
}
52147

53-
const gitConfigExists = await pathExists(GIT_USER_CONFIG_PATH);
148+
const gitConfigExists = pathExistsSync(GIT_USER_CONFIG_PATH);
54149
if (!gitConfigExists) {
55-
await createFile(GIT_USER_CONFIG_PATH);
150+
createFileSync(GIT_USER_CONFIG_PATH);
56151
}
57152

58153
this.gitConfigWatcher = await nsfw(GIT_USER_CONFIG_PATH, async (events: nsfw.FileChangeEvent[]) => {
59154
for (const event of events) {
60155
if (event.action === nsfw.actions.MODIFIED) {
156+
this.onUserGitconfigChangedEmitter.fire(undefined);
157+
61158
const userConfig = await this.getUserConfigurationFromGitConfig();
62159
const preferences = await this.preferencesService.getPreferences();
63160

@@ -74,27 +171,27 @@ export class GitConfigurationController implements CheGitService {
74171
async getUserConfigurationFromGitConfig(): Promise<UserConfiguration> {
75172
let name: string | undefined;
76173
let email: string | undefined;
77-
const config = await this.readConfigurationFromGitConfigFile(GIT_USER_CONFIG_PATH);
174+
const config = this.readConfigurationFromGitConfigFile(GIT_USER_CONFIG_PATH);
78175
if (config && config.user) {
79176
name = config.user.name;
80177
email = config.user.email;
81178
}
82179
if (name && email) {
83180
return { name, email };
84181
}
85-
const globalConfig = await this.readConfigurationFromGitConfigFile(GIT_GLOBAL_CONFIG_PATH);
182+
const globalConfig = this.readConfigurationFromGitConfigFile(GIT_GLOBAL_CONFIG_PATH);
86183
if (globalConfig && globalConfig.user) {
87184
name = name ? name : globalConfig.user.name;
88185
email = email ? email : globalConfig.user.email;
89186
}
90187
return { name, email };
91188
}
92189

93-
protected async readConfigurationFromGitConfigFile(path: string): Promise<GitConfiguration | undefined> {
94-
if (!(await pathExists(path))) {
190+
protected readConfigurationFromGitConfigFile(path: string): GitConfiguration | undefined {
191+
if (!pathExistsSync(path)) {
95192
return;
96193
}
97-
const gitConfigContent = await readFile(path, 'utf-8');
194+
const gitConfigContent = readFileSync(path, 'utf-8');
98195
return ini.parse(gitConfigContent);
99196
}
100197

@@ -104,12 +201,16 @@ export class GitConfigurationController implements CheGitService {
104201
}
105202

106203
this.preferencesHandler = this.preferencesService.onUserPreferencesModify(preferences => {
107-
const userConfig = this.getUserConfigurationFromPreferences(preferences);
108-
this.updateGlobalGitConfig(userConfig);
109-
this.client.firePreferencesChanged();
204+
this.updateUserGitconfigFromPreferences(preferences);
110205
});
111206
}
112207

208+
private async updateUserGitconfigFromPreferences(preferences: object): Promise<void> {
209+
const userConfig = this.getUserConfigurationFromPreferences(preferences);
210+
await this.updateUserGitonfigFromUserConfig(userConfig);
211+
this.client.firePreferencesChanged();
212+
}
213+
113214
// eslint-disable-next-line @typescript-eslint/no-explicit-any
114215
protected getUserConfigurationFromPreferences(preferences: any): UserConfiguration {
115216
return {
@@ -118,13 +219,13 @@ export class GitConfigurationController implements CheGitService {
118219
};
119220
}
120221

121-
public async updateGlobalGitConfig(userConfig: UserConfiguration): Promise<void> {
222+
public async updateUserGitonfigFromUserConfig(userConfig: UserConfiguration): Promise<void> {
122223
if (userConfig.name === undefined && userConfig.email === undefined) {
123224
return;
124225
}
125226

126227
// read existing content
127-
let gitConfig = await this.readConfigurationFromGitConfigFile(GIT_USER_CONFIG_PATH);
228+
let gitConfig = this.readConfigurationFromGitConfigFile(GIT_USER_CONFIG_PATH);
128229
if (!gitConfig) {
129230
gitConfig = {};
130231
} else if (!gitConfig.user) {
@@ -142,14 +243,16 @@ export class GitConfigurationController implements CheGitService {
142243
if (this.gitConfigWatcher) {
143244
await this.gitConfigWatcher.stop();
144245
}
145-
await writeFile(GIT_USER_CONFIG_PATH, ini.stringify(gitConfig));
246+
writeFileSync(GIT_USER_CONFIG_PATH, ini.stringify(gitConfig));
247+
this.onUserGitconfigChangedEmitter.fire(undefined);
146248
if (this.gitConfigWatcher) {
147249
await this.gitConfigWatcher.start();
148250
}
149251
}
150252

151253
setClient(client: CheGitClient): void {
152254
this.client = client;
255+
this.onGitClientSetEmitter.fire(undefined);
153256
}
154257

155258
dispose(): void {

extensions/eclipse-che-theia-git-provisioner/tests/git-configuration-controller.spec.ts

+51-11
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import 'reflect-metadata';
1313

1414
import * as fs from 'fs-extra';
15+
import * as ini from 'ini';
1516
import * as path from 'path';
1617

1718
import {
@@ -20,6 +21,7 @@ import {
2021
UserConfiguration,
2122
} from '../src/node/git-configuration-controller';
2223

24+
import { CheGitClient } from '../lib/common/git-protocol';
2325
import { CheTheiaUserPreferencesSynchronizer } from '@eclipse-che/theia-user-preferences-synchronizer/lib/node/che-theia-preferences-synchronizer';
2426
import { Container } from 'inversify';
2527

@@ -28,28 +30,37 @@ describe('Test GitConfigurationController', () => {
2830
let gitConfigurationController: GitConfigurationController;
2931
const cheTheiaUserPreferencesSynchronizerGetpreferencesMock = jest.fn();
3032
const cheTheiaUserPreferencesSynchronizerSetpreferencesMock = jest.fn();
33+
const cheTheiaUserPreferencesSynchronizerOnTheiaUserPreferencesCreatedMock = jest.fn();
3134
const cheTheiaUserPreferencesSynchronizer = {
3235
getPreferences: cheTheiaUserPreferencesSynchronizerGetpreferencesMock,
3336
setPreferences: cheTheiaUserPreferencesSynchronizerSetpreferencesMock,
37+
onTheiaUserPreferencesCreated: cheTheiaUserPreferencesSynchronizerOnTheiaUserPreferencesCreatedMock,
3438
} as any;
39+
cheTheiaUserPreferencesSynchronizerGetpreferencesMock.mockResolvedValue({});
40+
const cheGitClient: CheGitClient = {
41+
firePreferencesChanged: jest.fn(),
42+
};
3543

3644
beforeEach(async () => {
3745
jest.restoreAllMocks();
38-
jest.resetAllMocks();
46+
// jest.resetAllMocks();
47+
jest.spyOn(fs, 'readdirSync').mockReturnValue([]);
48+
jest.spyOn(fs, 'pathExistsSync').mockReturnValue(false);
3949
container = new Container();
4050
container.bind(CheTheiaUserPreferencesSynchronizer).toConstantValue(cheTheiaUserPreferencesSynchronizer);
4151
container.bind(GitConfigurationController).toSelf().inSingletonScope();
4252
gitConfigurationController = container.get(GitConfigurationController);
53+
gitConfigurationController.setClient(cheGitClient);
4354
});
4455

4556
test('check Update', async () => {
4657
const gitLfsConfigPath = path.resolve(__dirname, '_data', 'git-lfs.config');
4758
const gitLfsConfig = await fs.readFile(gitLfsConfigPath, 'utf-8');
48-
const readFileSpy = jest.spyOn(fs, 'readFile') as jest.Mock;
59+
const readFileSpy = jest.spyOn(fs, 'readFileSync') as jest.Mock;
4960
readFileSpy.mockReturnValue(gitLfsConfig);
50-
const pathExistsSpy = jest.spyOn(fs, 'pathExists') as jest.Mock;
61+
const pathExistsSpy = jest.spyOn(fs, 'pathExistsSync') as jest.Mock;
5162
pathExistsSpy.mockReturnValue(true);
52-
const writeFileSpy = jest.spyOn(fs, 'writeFile') as jest.Mock;
63+
const writeFileSpy = jest.spyOn(fs, 'writeFileSync') as jest.Mock;
5364
// do not write anything
5465
writeFileSpy.mockResolvedValue({});
5566

@@ -58,7 +69,7 @@ describe('Test GitConfigurationController', () => {
5869
5970
};
6071

61-
await gitConfigurationController.updateGlobalGitConfig(userConfig);
72+
await gitConfigurationController.updateUserGitonfigFromUserConfig(userConfig);
6273
expect(gitConfigurationController).toBeDefined();
6374

6475
// it should contain lfs data
@@ -74,16 +85,16 @@ describe('Test GitConfigurationController', () => {
7485

7586
const userConfigPath = path.resolve(__dirname, '_data', 'git-user.config');
7687
const userConfig = await fs.readFile(userConfigPath, 'utf-8');
77-
const readFileSpy = jest.spyOn(fs, 'readFile') as jest.Mock;
78-
const pathExistsSpy = jest.spyOn(fs, 'pathExists') as jest.Mock;
88+
const readFileSpy = jest.spyOn(fs, 'readFileSync') as jest.Mock;
89+
const pathExistsSpy = jest.spyOn(fs, 'pathExistsSync') as jest.Mock;
7990

8091
// GIT_USER_CONFIG_PATH
81-
readFileSpy.mockResolvedValueOnce(gitLfsConfig);
82-
pathExistsSpy.mockResolvedValueOnce(true);
92+
readFileSpy.mockReturnValueOnce(gitLfsConfig);
93+
pathExistsSpy.mockReturnValueOnce(true);
8394

8495
// GIT_GLOBAL_CONFIG_PATH
85-
readFileSpy.mockResolvedValueOnce(userConfig);
86-
pathExistsSpy.mockResolvedValueOnce(true);
96+
readFileSpy.mockReturnValueOnce(userConfig);
97+
pathExistsSpy.mockReturnValueOnce(true);
8798

8899
const userConfiguration = await gitConfigurationController.getUserConfigurationFromGitConfig();
89100

@@ -92,4 +103,33 @@ describe('Test GitConfigurationController', () => {
92103
93104
});
94105
});
106+
107+
test('check updateLocalGitconfig', async () => {
108+
const gitConfigurationControllerProto = Object.getPrototypeOf(gitConfigurationController);
109+
const userGitconfigContent = fs.readFileSync(path.resolve(__dirname, '_data', 'git-user.config')).toString();
110+
const lfsGitconfigContent = fs.readFileSync(path.resolve(__dirname, '_data', 'git-lfs.config')).toString();
111+
gitConfigurationControllerProto.userGitconfigDirty = ini.parse(userGitconfigContent);
112+
const dir = {
113+
isFile: () => false,
114+
isDirectory: () => true,
115+
isBlockDevice: () => true,
116+
isCharacterDevice: () => true,
117+
isSymbolicLink: () => true,
118+
isFIFO: () => true,
119+
isSocket: () => true,
120+
name: 'dirName',
121+
};
122+
jest.spyOn(fs, 'readdirSync').mockReturnValueOnce([dir]);
123+
const gitUserConfigPath = path.resolve(__dirname, '_data', 'git-user.config');
124+
jest.spyOn(path, 'resolve').mockReturnValueOnce(gitUserConfigPath);
125+
const writeFileSpy = jest.spyOn(fs, 'writeFileSync') as jest.Mock;
126+
// do not write anything
127+
writeFileSpy.mockReturnValue({});
128+
129+
gitConfigurationControllerProto.updateLocalGitconfig(ini.parse(userGitconfigContent.concat(lfsGitconfigContent)));
130+
131+
expect(writeFileSpy).toBeCalledWith(gitUserConfigPath, expect.stringContaining('lfs'));
132+
expect(writeFileSpy).toBeCalledWith(gitUserConfigPath, expect.stringContaining('dummy'));
133+
expect(writeFileSpy).toBeCalledWith(gitUserConfigPath, expect.stringContaining('[email protected]'));
134+
});
95135
});

0 commit comments

Comments
 (0)