Skip to content

Commit

Permalink
Merge pull request #155 from PizzaFactory/prp-update-to-the-upstream
Browse files Browse the repository at this point in the history
[Scheduled] Update to the upstream
  • Loading branch information
monaka committed Jun 4, 2022
2 parents 88c43f1 + 9f8a0dd commit 5fd1641
Show file tree
Hide file tree
Showing 2 changed files with 173 additions and 30 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,25 @@ import * as ini from 'ini';
import * as nsfw from 'nsfw';

import { CheGitClient, CheGitService, GIT_USER_EMAIL, GIT_USER_NAME } from '../common/git-protocol';
import { createFile, pathExists, readFile, writeFile } from 'fs-extra';
import {
CheTheiaUserPreferencesSynchronizer,
THEIA_USER_PREFERENCES_PATH,
} from '@eclipse-che/theia-user-preferences-synchronizer/lib/node/che-theia-preferences-synchronizer';
import { Disposable, Emitter } from '@theia/core';
import { basename, dirname, resolve } from 'path';
import {
createFileSync,
ensureDirSync,
existsSync,
pathExistsSync,
readFileSync,
readdirSync,
watch,
writeFileSync,
} from 'fs-extra';
import { inject, injectable } from 'inversify';

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

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

@injectable()
export class GitConfigurationController implements CheGitService {
@inject(CheTheiaUserPreferencesSynchronizer)
protected preferencesService: CheTheiaUserPreferencesSynchronizer;
constructor(
@inject(CheTheiaUserPreferencesSynchronizer) protected preferencesService: CheTheiaUserPreferencesSynchronizer
) {
this.onGitClientSetEvent(async () => {
await this.checkExistsWithTimeout(THEIA_USER_PREFERENCES_PATH, 60000);
this.userGitconfigDirty = this.readConfigurationFromGitConfigFile(GIT_USER_CONFIG_PATH)!;
const preferences = await this.preferencesService.getPreferences();
await this.updateUserGitconfigFromPreferences(preferences);
});
this.onUserGitconfigChangedEvent(() => this.fetchLocalGitconfig());
}

private checkExistsWithTimeout(filePath: string, timeout: number): Promise<void> {
return new Promise((resolvePromise, reject) => {
if (existsSync(filePath)) {
resolvePromise();
return;
}
const timer = setTimeout(() => {
watcher.close();
reject(new Error('File did not exists and was not created during the timeout.'));
}, timeout);

const dir = dirname(filePath);
ensureDirSync(dir);
const pathBasename = basename(filePath);
const watcher = watch(dir, (eventType, filename) => {
if (eventType === 'rename' && filename === pathBasename) {
clearTimeout(timer);
watcher.close();
resolvePromise();
}
});
});
}

private fetchLocalGitconfig(): void {
const userGitconfig = this.readConfigurationFromGitConfigFile(GIT_USER_CONFIG_PATH)!;
this.updateLocalGitconfig(userGitconfig);
this.userGitconfigDirty = userGitconfig;
}

protected preferencesHandler: Disposable | undefined;

protected gitConfigWatcher: nsfw.NSFW | undefined;

protected client: CheGitClient;

private readonly projectsRoot = process.env.PROJECTS_ROOT || process.env.CHE_PROJECTS_ROOT || '/projects';

private readonly onUserGitconfigChangedEmitter = new Emitter();
private readonly onUserGitconfigChangedEvent = this.onUserGitconfigChangedEmitter.event;

private readonly onGitClientSetEmitter = new Emitter();
private readonly onGitClientSetEvent = this.onGitClientSetEmitter.event;

private userGitconfigDirty: GitConfiguration;

private updateLocalGitconfig(gitconfig: GitConfiguration): void {
readdirSync(this.projectsRoot, { withFileTypes: true })
.filter(dir => dir.isDirectory())
.forEach(dir => {
const localGitconfigPath = resolve(this.projectsRoot, dir.name, '.git', 'config');
let localGitconfig: GitConfiguration;
if (existsSync(localGitconfigPath)) {
localGitconfig = ini.parse(readFileSync(localGitconfigPath).toString());
// Add missing values
Object.keys(gitconfig).forEach(key => {
if (
localGitconfig[key] === undefined ||
JSON.stringify(localGitconfig[key]) === JSON.stringify(this.userGitconfigDirty[key])
) {
localGitconfig[key] = gitconfig[key];
}
});
// Remove deleted values
Object.keys(localGitconfig).forEach(key => {
if (
gitconfig[key] === undefined &&
JSON.stringify(localGitconfig[key]) === JSON.stringify(this.userGitconfigDirty[key])
) {
delete localGitconfig[key];
}
});
} else {
createFileSync(localGitconfigPath);
localGitconfig = gitconfig;
}
writeFileSync(localGitconfigPath, ini.stringify(localGitconfig));
});
}

public async watchGitConfigChanges(): Promise<void> {
if (this.gitConfigWatcher) {
return;
}

const gitConfigExists = await pathExists(GIT_USER_CONFIG_PATH);
const gitConfigExists = pathExistsSync(GIT_USER_CONFIG_PATH);
if (!gitConfigExists) {
await createFile(GIT_USER_CONFIG_PATH);
createFileSync(GIT_USER_CONFIG_PATH);
}

this.gitConfigWatcher = await nsfw(GIT_USER_CONFIG_PATH, async (events: nsfw.FileChangeEvent[]) => {
for (const event of events) {
if (event.action === nsfw.actions.MODIFIED) {
this.onUserGitconfigChangedEmitter.fire(undefined);

const userConfig = await this.getUserConfigurationFromGitConfig();
const preferences = await this.preferencesService.getPreferences();

Expand All @@ -74,27 +171,27 @@ export class GitConfigurationController implements CheGitService {
async getUserConfigurationFromGitConfig(): Promise<UserConfiguration> {
let name: string | undefined;
let email: string | undefined;
const config = await this.readConfigurationFromGitConfigFile(GIT_USER_CONFIG_PATH);
const config = this.readConfigurationFromGitConfigFile(GIT_USER_CONFIG_PATH);
if (config && config.user) {
name = config.user.name;
email = config.user.email;
}
if (name && email) {
return { name, email };
}
const globalConfig = await this.readConfigurationFromGitConfigFile(GIT_GLOBAL_CONFIG_PATH);
const globalConfig = this.readConfigurationFromGitConfigFile(GIT_GLOBAL_CONFIG_PATH);
if (globalConfig && globalConfig.user) {
name = name ? name : globalConfig.user.name;
email = email ? email : globalConfig.user.email;
}
return { name, email };
}

protected async readConfigurationFromGitConfigFile(path: string): Promise<GitConfiguration | undefined> {
if (!(await pathExists(path))) {
protected readConfigurationFromGitConfigFile(path: string): GitConfiguration | undefined {
if (!pathExistsSync(path)) {
return;
}
const gitConfigContent = await readFile(path, 'utf-8');
const gitConfigContent = readFileSync(path, 'utf-8');
return ini.parse(gitConfigContent);
}

Expand All @@ -104,12 +201,16 @@ export class GitConfigurationController implements CheGitService {
}

this.preferencesHandler = this.preferencesService.onUserPreferencesModify(preferences => {
const userConfig = this.getUserConfigurationFromPreferences(preferences);
this.updateGlobalGitConfig(userConfig);
this.client.firePreferencesChanged();
this.updateUserGitconfigFromPreferences(preferences);
});
}

private async updateUserGitconfigFromPreferences(preferences: object): Promise<void> {
const userConfig = this.getUserConfigurationFromPreferences(preferences);
await this.updateUserGitonfigFromUserConfig(userConfig);
this.client.firePreferencesChanged();
}

// eslint-disable-next-line @typescript-eslint/no-explicit-any
protected getUserConfigurationFromPreferences(preferences: any): UserConfiguration {
return {
Expand All @@ -118,13 +219,13 @@ export class GitConfigurationController implements CheGitService {
};
}

public async updateGlobalGitConfig(userConfig: UserConfiguration): Promise<void> {
public async updateUserGitonfigFromUserConfig(userConfig: UserConfiguration): Promise<void> {
if (userConfig.name === undefined && userConfig.email === undefined) {
return;
}

// read existing content
let gitConfig = await this.readConfigurationFromGitConfigFile(GIT_USER_CONFIG_PATH);
let gitConfig = this.readConfigurationFromGitConfigFile(GIT_USER_CONFIG_PATH);
if (!gitConfig) {
gitConfig = {};
} else if (!gitConfig.user) {
Expand All @@ -142,14 +243,16 @@ export class GitConfigurationController implements CheGitService {
if (this.gitConfigWatcher) {
await this.gitConfigWatcher.stop();
}
await writeFile(GIT_USER_CONFIG_PATH, ini.stringify(gitConfig));
writeFileSync(GIT_USER_CONFIG_PATH, ini.stringify(gitConfig));
this.onUserGitconfigChangedEmitter.fire(undefined);
if (this.gitConfigWatcher) {
await this.gitConfigWatcher.start();
}
}

setClient(client: CheGitClient): void {
this.client = client;
this.onGitClientSetEmitter.fire(undefined);
}

dispose(): void {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import 'reflect-metadata';

import * as fs from 'fs-extra';
import * as ini from 'ini';
import * as path from 'path';

import {
Expand All @@ -20,6 +21,7 @@ import {
UserConfiguration,
} from '../src/node/git-configuration-controller';

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

Expand All @@ -28,28 +30,37 @@ describe('Test GitConfigurationController', () => {
let gitConfigurationController: GitConfigurationController;
const cheTheiaUserPreferencesSynchronizerGetpreferencesMock = jest.fn();
const cheTheiaUserPreferencesSynchronizerSetpreferencesMock = jest.fn();
const cheTheiaUserPreferencesSynchronizerOnTheiaUserPreferencesCreatedMock = jest.fn();
const cheTheiaUserPreferencesSynchronizer = {
getPreferences: cheTheiaUserPreferencesSynchronizerGetpreferencesMock,
setPreferences: cheTheiaUserPreferencesSynchronizerSetpreferencesMock,
onTheiaUserPreferencesCreated: cheTheiaUserPreferencesSynchronizerOnTheiaUserPreferencesCreatedMock,
} as any;
cheTheiaUserPreferencesSynchronizerGetpreferencesMock.mockResolvedValue({});
const cheGitClient: CheGitClient = {
firePreferencesChanged: jest.fn(),
};

beforeEach(async () => {
jest.restoreAllMocks();
jest.resetAllMocks();
// jest.resetAllMocks();
jest.spyOn(fs, 'readdirSync').mockReturnValue([]);
jest.spyOn(fs, 'pathExistsSync').mockReturnValue(false);
container = new Container();
container.bind(CheTheiaUserPreferencesSynchronizer).toConstantValue(cheTheiaUserPreferencesSynchronizer);
container.bind(GitConfigurationController).toSelf().inSingletonScope();
gitConfigurationController = container.get(GitConfigurationController);
gitConfigurationController.setClient(cheGitClient);
});

test('check Update', async () => {
const gitLfsConfigPath = path.resolve(__dirname, '_data', 'git-lfs.config');
const gitLfsConfig = await fs.readFile(gitLfsConfigPath, 'utf-8');
const readFileSpy = jest.spyOn(fs, 'readFile') as jest.Mock;
const readFileSpy = jest.spyOn(fs, 'readFileSync') as jest.Mock;
readFileSpy.mockReturnValue(gitLfsConfig);
const pathExistsSpy = jest.spyOn(fs, 'pathExists') as jest.Mock;
const pathExistsSpy = jest.spyOn(fs, 'pathExistsSync') as jest.Mock;
pathExistsSpy.mockReturnValue(true);
const writeFileSpy = jest.spyOn(fs, 'writeFile') as jest.Mock;
const writeFileSpy = jest.spyOn(fs, 'writeFileSync') as jest.Mock;
// do not write anything
writeFileSpy.mockResolvedValue({});

Expand All @@ -58,7 +69,7 @@ describe('Test GitConfigurationController', () => {
email: 'my@fake.email',
};

await gitConfigurationController.updateGlobalGitConfig(userConfig);
await gitConfigurationController.updateUserGitonfigFromUserConfig(userConfig);
expect(gitConfigurationController).toBeDefined();

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

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

// GIT_USER_CONFIG_PATH
readFileSpy.mockResolvedValueOnce(gitLfsConfig);
pathExistsSpy.mockResolvedValueOnce(true);
readFileSpy.mockReturnValueOnce(gitLfsConfig);
pathExistsSpy.mockReturnValueOnce(true);

// GIT_GLOBAL_CONFIG_PATH
readFileSpy.mockResolvedValueOnce(userConfig);
pathExistsSpy.mockResolvedValueOnce(true);
readFileSpy.mockReturnValueOnce(userConfig);
pathExistsSpy.mockReturnValueOnce(true);

const userConfiguration = await gitConfigurationController.getUserConfigurationFromGitConfig();

Expand All @@ -92,4 +103,33 @@ describe('Test GitConfigurationController', () => {
email: 'my@fake.email',
});
});

test('check updateLocalGitconfig', async () => {
const gitConfigurationControllerProto = Object.getPrototypeOf(gitConfigurationController);
const userGitconfigContent = fs.readFileSync(path.resolve(__dirname, '_data', 'git-user.config')).toString();
const lfsGitconfigContent = fs.readFileSync(path.resolve(__dirname, '_data', 'git-lfs.config')).toString();
gitConfigurationControllerProto.userGitconfigDirty = ini.parse(userGitconfigContent);
const dir = {
isFile: () => false,
isDirectory: () => true,
isBlockDevice: () => true,
isCharacterDevice: () => true,
isSymbolicLink: () => true,
isFIFO: () => true,
isSocket: () => true,
name: 'dirName',
};
jest.spyOn(fs, 'readdirSync').mockReturnValueOnce([dir]);
const gitUserConfigPath = path.resolve(__dirname, '_data', 'git-user.config');
jest.spyOn(path, 'resolve').mockReturnValueOnce(gitUserConfigPath);
const writeFileSpy = jest.spyOn(fs, 'writeFileSync') as jest.Mock;
// do not write anything
writeFileSpy.mockReturnValue({});

gitConfigurationControllerProto.updateLocalGitconfig(ini.parse(userGitconfigContent.concat(lfsGitconfigContent)));

expect(writeFileSpy).toBeCalledWith(gitUserConfigPath, expect.stringContaining('lfs'));
expect(writeFileSpy).toBeCalledWith(gitUserConfigPath, expect.stringContaining('dummy'));
expect(writeFileSpy).toBeCalledWith(gitUserConfigPath, expect.stringContaining('my@fake.email'));
});
});

0 comments on commit 5fd1641

Please sign in to comment.