mirror of
https://github.com/tiktok/sparo.git
synced 2024-11-14 19:35:12 -05:00
feat: add graceful shutdown to ensure consistent git configs
This commit is contained in:
parent
331d60520e
commit
eb6ce3ecf9
4 changed files with 78 additions and 10 deletions
|
@ -7,6 +7,7 @@ import { ArgvService } from '../services/ArgvService';
|
|||
import { GitVersionCompatibility } from '../logic/GitVersionCompatibility';
|
||||
import { TelemetryService } from '../services/TelemetryService';
|
||||
import { GitSparseCheckoutService } from '../services/GitSparseCheckoutService';
|
||||
import { GracefulShutdownService } from '../services/GracefulShutdownService';
|
||||
import { getCommandName } from './commands/util';
|
||||
import { SparoStartupBanner } from './SparoStartupBanner';
|
||||
import type { ILaunchOptions } from '../api/Sparo';
|
||||
|
@ -51,6 +52,10 @@ export class SparoCICommandLine {
|
|||
this._commandsMap.add(getCommandName(cmdInstance.cmd));
|
||||
})
|
||||
);
|
||||
|
||||
const gracefulShutdownService: GracefulShutdownService =
|
||||
await getFromContainerAsync(GracefulShutdownService);
|
||||
gracefulShutdownService.setup();
|
||||
}
|
||||
|
||||
public async runAsync(): Promise<void> {
|
||||
|
|
|
@ -8,6 +8,7 @@ import { ICommand } from './commands/base';
|
|||
import { GitVersionCompatibility } from '../logic/GitVersionCompatibility';
|
||||
import { TelemetryService } from '../services/TelemetryService';
|
||||
import { GitSparseCheckoutService } from '../services/GitSparseCheckoutService';
|
||||
import { GracefulShutdownService } from '../services/GracefulShutdownService';
|
||||
import { getCommandName } from './commands/util';
|
||||
import { SparoStartupBanner } from './SparoStartupBanner';
|
||||
import type { ILaunchOptions } from '../api/Sparo';
|
||||
|
@ -52,6 +53,10 @@ export class SparoCommandLine {
|
|||
this._commandsMap.add(getCommandName(cmdInstance.cmd));
|
||||
})
|
||||
);
|
||||
|
||||
const gracefulShutdownService: GracefulShutdownService =
|
||||
await getFromContainerAsync(GracefulShutdownService);
|
||||
gracefulShutdownService.setup();
|
||||
}
|
||||
|
||||
public async runAsync(): Promise<void> {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { inject } from 'inversify';
|
||||
import { Command } from '../../decorator';
|
||||
import { GitService } from '../../services/GitService';
|
||||
import { GracefulShutdownService } from '../../services/GracefulShutdownService';
|
||||
|
||||
import type { Argv, ArgumentsCamelCase } from 'yargs';
|
||||
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';
|
||||
|
||||
@inject(GitService) private _gitService!: GitService;
|
||||
@inject(GracefulShutdownService) private _gracefulShutdownService!: GracefulShutdownService;
|
||||
public builder(yargs: Argv<{}>): void {
|
||||
/**
|
||||
* 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 fetchArgs: string[] = ['fetch'];
|
||||
|
||||
let remoteFetchGitConfig: string[] | undefined;
|
||||
let restoreSingleBranchCallback: (() => void) | undefined;
|
||||
if (all) {
|
||||
// Temporary revert single branch fetch
|
||||
const currentRemoteFetchGitConfig: string[] | undefined = this._getRemoteFetchGitConfig(remote);
|
||||
if (currentRemoteFetchGitConfig) {
|
||||
this._setAllBranchFetch(remote);
|
||||
remoteFetchGitConfig = currentRemoteFetchGitConfig;
|
||||
}
|
||||
// Temporary revert single branch fetch if necessary
|
||||
restoreSingleBranchCallback = this._revertSingleBranchIfNecessary(remote);
|
||||
|
||||
fetchArgs.push('--all');
|
||||
} else {
|
||||
|
@ -60,15 +58,35 @@ export class FetchCommand implements ICommand<IFetchCommandOptions> {
|
|||
|
||||
gitService.executeGitCommand({ args: fetchArgs });
|
||||
|
||||
if (remoteFetchGitConfig) {
|
||||
this._restoreSingleBranchFetch(remote, remoteFetchGitConfig);
|
||||
}
|
||||
restoreSingleBranchCallback?.();
|
||||
};
|
||||
|
||||
public getHelp(): string {
|
||||
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 {
|
||||
const result: string | undefined = this._gitService.getGitConfig(`remote.${remote}.fetch`, {
|
||||
array: true
|
||||
|
|
40
apps/sparo-lib/src/services/GracefulShutdownService.ts
Normal file
40
apps/sparo-lib/src/services/GracefulShutdownService.ts
Normal 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);
|
||||
}
|
||||
};
|
||||
}
|
Loading…
Reference in a new issue