feat: add graceful shutdown to ensure consistent git configs

This commit is contained in:
Cheng Liu 2024-04-15 15:23:59 -07:00
parent 331d60520e
commit eb6ce3ecf9
No known key found for this signature in database
GPG key ID: EEC8452F7DB85CD6
4 changed files with 78 additions and 10 deletions

View file

@ -7,6 +7,7 @@ import { ArgvService } from '../services/ArgvService';
import { GitVersionCompatibility } from '../logic/GitVersionCompatibility'; import { GitVersionCompatibility } from '../logic/GitVersionCompatibility';
import { TelemetryService } from '../services/TelemetryService'; import { TelemetryService } from '../services/TelemetryService';
import { GitSparseCheckoutService } from '../services/GitSparseCheckoutService'; import { GitSparseCheckoutService } from '../services/GitSparseCheckoutService';
import { GracefulShutdownService } from '../services/GracefulShutdownService';
import { getCommandName } from './commands/util'; import { getCommandName } from './commands/util';
import { SparoStartupBanner } from './SparoStartupBanner'; import { SparoStartupBanner } from './SparoStartupBanner';
import type { ILaunchOptions } from '../api/Sparo'; import type { ILaunchOptions } from '../api/Sparo';
@ -51,6 +52,10 @@ export class SparoCICommandLine {
this._commandsMap.add(getCommandName(cmdInstance.cmd)); this._commandsMap.add(getCommandName(cmdInstance.cmd));
}) })
); );
const gracefulShutdownService: GracefulShutdownService =
await getFromContainerAsync(GracefulShutdownService);
gracefulShutdownService.setup();
} }
public async runAsync(): Promise<void> { public async runAsync(): Promise<void> {

View file

@ -8,6 +8,7 @@ import { ICommand } from './commands/base';
import { GitVersionCompatibility } from '../logic/GitVersionCompatibility'; import { GitVersionCompatibility } from '../logic/GitVersionCompatibility';
import { TelemetryService } from '../services/TelemetryService'; import { TelemetryService } from '../services/TelemetryService';
import { GitSparseCheckoutService } from '../services/GitSparseCheckoutService'; import { GitSparseCheckoutService } from '../services/GitSparseCheckoutService';
import { GracefulShutdownService } from '../services/GracefulShutdownService';
import { getCommandName } from './commands/util'; import { getCommandName } from './commands/util';
import { SparoStartupBanner } from './SparoStartupBanner'; import { SparoStartupBanner } from './SparoStartupBanner';
import type { ILaunchOptions } from '../api/Sparo'; import type { ILaunchOptions } from '../api/Sparo';
@ -52,6 +53,10 @@ export class SparoCommandLine {
this._commandsMap.add(getCommandName(cmdInstance.cmd)); this._commandsMap.add(getCommandName(cmdInstance.cmd));
}) })
); );
const gracefulShutdownService: GracefulShutdownService =
await getFromContainerAsync(GracefulShutdownService);
gracefulShutdownService.setup();
} }
public async runAsync(): Promise<void> { public async runAsync(): Promise<void> {

View file

@ -1,6 +1,7 @@
import { inject } from 'inversify'; import { inject } from 'inversify';
import { Command } from '../../decorator'; import { Command } from '../../decorator';
import { GitService } from '../../services/GitService'; import { GitService } from '../../services/GitService';
import { GracefulShutdownService } from '../../services/GracefulShutdownService';
import type { Argv, ArgumentsCamelCase } from 'yargs'; import type { Argv, ArgumentsCamelCase } from 'yargs';
import type { GitRepoInfo } from 'git-repo-info'; import type { GitRepoInfo } from 'git-repo-info';
@ -19,6 +20,7 @@ export class FetchCommand implements ICommand<IFetchCommandOptions> {
public description: string = 'fetch remote branch to local'; public description: string = 'fetch remote branch to local';
@inject(GitService) private _gitService!: GitService; @inject(GitService) private _gitService!: GitService;
@inject(GracefulShutdownService) private _gracefulShutdownService!: GracefulShutdownService;
public builder(yargs: Argv<{}>): void { public builder(yargs: Argv<{}>): void {
/** /**
* sparo fetch <remote> <branch> [--all] * sparo fetch <remote> <branch> [--all]
@ -44,14 +46,10 @@ export class FetchCommand implements ICommand<IFetchCommandOptions> {
const { all, branch = defaultBranch, remote = this._gitService.getBranchRemote(branch) } = args; const { all, branch = defaultBranch, remote = this._gitService.getBranchRemote(branch) } = args;
const fetchArgs: string[] = ['fetch']; const fetchArgs: string[] = ['fetch'];
let remoteFetchGitConfig: string[] | undefined; let restoreSingleBranchCallback: (() => void) | undefined;
if (all) { if (all) {
// Temporary revert single branch fetch // Temporary revert single branch fetch if necessary
const currentRemoteFetchGitConfig: string[] | undefined = this._getRemoteFetchGitConfig(remote); restoreSingleBranchCallback = this._revertSingleBranchIfNecessary(remote);
if (currentRemoteFetchGitConfig) {
this._setAllBranchFetch(remote);
remoteFetchGitConfig = currentRemoteFetchGitConfig;
}
fetchArgs.push('--all'); fetchArgs.push('--all');
} else { } else {
@ -60,15 +58,35 @@ export class FetchCommand implements ICommand<IFetchCommandOptions> {
gitService.executeGitCommand({ args: fetchArgs }); gitService.executeGitCommand({ args: fetchArgs });
if (remoteFetchGitConfig) { restoreSingleBranchCallback?.();
this._restoreSingleBranchFetch(remote, remoteFetchGitConfig);
}
}; };
public getHelp(): string { public getHelp(): string {
return `fetch help`; return `fetch help`;
} }
private _revertSingleBranchIfNecessary = (remote: string): (() => void) | undefined => {
let remoteFetchGitConfig: string[] | undefined = this._getRemoteFetchGitConfig(remote);
let callback: (() => void) | undefined;
if (remoteFetchGitConfig) {
this._setAllBranchFetch(remote);
callback = () => {
if (remoteFetchGitConfig) {
this._restoreSingleBranchFetch(remote, remoteFetchGitConfig);
// Avoid memory leaking
remoteFetchGitConfig = undefined;
this._gracefulShutdownService.unregisterCallback(callback);
}
};
this._gracefulShutdownService.registerCallback(callback);
}
return callback;
};
private _getRemoteFetchGitConfig(remote: string): string[] | undefined { private _getRemoteFetchGitConfig(remote: string): string[] | undefined {
const result: string | undefined = this._gitService.getGitConfig(`remote.${remote}.fetch`, { const result: string | undefined = this._gitService.getGitConfig(`remote.${remote}.fetch`, {
array: true array: true

View file

@ -0,0 +1,40 @@
import { Service } from '../decorator';
type ICallback = () => void;
/**
* Helper class for managing graceful shutdown callbacks
*
* Example:
* When running "sparo fetch --all", the command will temporarily modify git configs.
* It's essential to register a restore callback via graceful shutdown service to
* prevent inconsistent git configs status if user presses CTRL + C to terminate the
* process in the middle of running.
*/
@Service()
export class GracefulShutdownService {
private _callbacks: Set<ICallback> = new Set<ICallback>();
public setup = (): void => {
process.on('SIGINT', () => this._handleSignal());
};
public registerCallback = (cb: ICallback): void => {
this._callbacks.add(cb);
};
public unregisterCallback = (cb?: ICallback): void => {
if (!cb) {
return;
}
this._callbacks.delete(cb);
};
private _handleSignal = (): void => {
// Keep the implementation simple to run each callbacks synchronously.
for (const cb of Array.from(this._callbacks)) {
cb();
this.unregisterCallback(cb);
}
};
}