Merge pull request #78 from tiktok/feat-sparo-metric

Feat sparo metric
This commit is contained in:
Adrian Zhang 2024-06-29 11:15:40 +08:00 committed by GitHub
commit 70ecbe4dc3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 114 additions and 45 deletions

View file

@ -21,6 +21,7 @@ export class CommandService {
@inject(HelpTextService) private _helpTextService!: HelpTextService; @inject(HelpTextService) private _helpTextService!: HelpTextService;
@inject(TerminalService) private _terminalService!: TerminalService; @inject(TerminalService) private _terminalService!: TerminalService;
@inject(TelemetryService) private _telemetryService!: TelemetryService; @inject(TelemetryService) private _telemetryService!: TelemetryService;
private _hasInternalError: boolean = false;
public register<O extends {}>(command: ICommand<O>): void { public register<O extends {}>(command: ICommand<O>): void {
const { cmd, description, builder, handler, getHelp } = command; const { cmd, description, builder, handler, getHelp } = command;
@ -36,19 +37,23 @@ export class CommandService {
}, },
async (args) => { async (args) => {
process.exitCode = 1; process.exitCode = 1;
this._hasInternalError = false;
try { try {
terminal.writeVerboseLine(`Invoking command "${commandName}" with args ${JSON.stringify(args)}`); terminal.writeVerboseLine(`Invoking command "${commandName}" with args ${JSON.stringify(args)}`);
const stopwatch: Stopwatch = Stopwatch.start(); const stopwatch: Stopwatch = Stopwatch.start();
await handler(args, terminalService); await handler(args, terminalService);
terminal.writeVerboseLine(`Invoked command "${commandName}" done (${stopwatch.toString()})`); terminal.writeVerboseLine(`Invoked command "${commandName}" done (${stopwatch.toString()})`);
stopwatch.stop(); stopwatch.stop();
this._telemetryService.collectTelemetry({ if (!this._hasInternalError) {
commandName, // Only report success data
args: process.argv.slice(2), this._telemetryService.collectTelemetry({
durationInSeconds: stopwatch.duration, commandName,
startTimestampMs: stopwatch.startTime, args: process.argv.slice(2),
endTimestampMs: stopwatch.endTime durationInSeconds: stopwatch.duration,
}); startTimestampMs: stopwatch.startTime,
endTimestampMs: stopwatch.endTime
});
}
// eslint-disable-next-line require-atomic-updates // eslint-disable-next-line require-atomic-updates
process.exitCode = 0; process.exitCode = 0;
} catch (e) { } catch (e) {
@ -60,4 +65,8 @@ export class CommandService {
); );
this._helpTextService.set(commandName, getHelp()); this._helpTextService.set(commandName, getHelp());
} }
public setHasInternalError(): void {
this._hasInternalError = true;
}
} }

View file

@ -18,7 +18,7 @@ export class GitRemoteFetchConfigService {
@inject(GracefulShutdownService) private _gracefulShutdownService!: GracefulShutdownService; @inject(GracefulShutdownService) private _gracefulShutdownService!: GracefulShutdownService;
public addRemoteBranchIfNotExists(remote: string, branch: string): void { public addRemoteBranchIfNotExists(remote: string, branch: string): void {
const remoteFetchGitConfig: string[] | undefined = this._loadForRemote(remote); const remoteFetchGitConfig: string[] | undefined = this._getRemoteFetchInGitConfig(remote);
if (remoteFetchGitConfig) { if (remoteFetchGitConfig) {
const targetConfig: string = `+refs/heads/${branch}:refs/remotes/${remote}/${branch}`; const targetConfig: string = `+refs/heads/${branch}:refs/remotes/${remote}/${branch}`;
@ -38,7 +38,7 @@ export class GitRemoteFetchConfigService {
} }
public pruneRemoteBranchesInGitConfigAsync = async (remote: string): Promise<void> => { public pruneRemoteBranchesInGitConfigAsync = async (remote: string): Promise<void> => {
const remoteFetchConfig: string[] | undefined = this._loadForRemote(remote); const remoteFetchConfig: string[] | undefined = this._getRemoteFetchInGitConfig(remote);
if (!remoteFetchConfig) { if (!remoteFetchConfig) {
return; return;
} }
@ -50,6 +50,8 @@ export class GitRemoteFetchConfigService {
); );
const checkBranches: string[] = Array.from(branchToValues.keys()).filter((x) => x !== '*'); const checkBranches: string[] = Array.from(branchToValues.keys()).filter((x) => x !== '*');
this._terminalService.terminal.writeLine(`Checking tracking branches...`);
const remoteBranchExistenceInfo: Record<string, boolean> = const remoteBranchExistenceInfo: Record<string, boolean> =
await this._gitService.checkRemoteBranchesExistenceAsync(remote, checkBranches); await this._gitService.checkRemoteBranchesExistenceAsync(remote, checkBranches);
@ -103,12 +105,12 @@ export class GitRemoteFetchConfigService {
* It's used in "sparo fetch --all" command * It's used in "sparo fetch --all" command
*/ */
public revertSingleBranchIfNecessary = (remote: string): (() => void) | undefined => { public revertSingleBranchIfNecessary = (remote: string): (() => void) | undefined => {
let remoteFetchGitConfig: string[] | undefined = this._loadForRemote(remote); let remoteFetchGitConfig: string[] | undefined = this._getRemoteFetchInGitConfig(remote);
let callback: (() => void) | undefined; let callback: (() => void) | undefined;
if (remoteFetchGitConfig && !remoteFetchGitConfig.includes(`+refs/heads/*:refs/remotes/${remote}/*`)) { if (remoteFetchGitConfig && !remoteFetchGitConfig.includes(`+refs/heads/*:refs/remotes/${remote}/*`)) {
this._setAllBranchFetch(remote); this._setAllBranchFetch(remote);
callback = () => { callback = (): void => {
if (remoteFetchGitConfig) { if (remoteFetchGitConfig) {
this._setRemoteFetchInGitConfig(remote, remoteFetchGitConfig); this._setRemoteFetchInGitConfig(remote, remoteFetchGitConfig);
@ -147,7 +149,7 @@ export class GitRemoteFetchConfigService {
return branchToValues; return branchToValues;
} }
private _loadForRemote(remote: string): string[] | undefined { private _getRemoteFetchInGitConfig(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
}); });
@ -160,9 +162,10 @@ export class GitRemoteFetchConfigService {
* So, delete all remote.origin.fetch configuration and restores expected value * So, delete all remote.origin.fetch configuration and restores expected value
*/ */
private _setRemoteFetchInGitConfig(remote: string, remoteFetchGitConfig: string[]): void { private _setRemoteFetchInGitConfig(remote: string, remoteFetchGitConfig: string[]): void {
this._gitService.unsetGitConfig(`remote.${remote}.fetch`); const key: string = `remote.${remote}.fetch`;
this._gitService.unsetGitConfig(key);
for (const value of remoteFetchGitConfig) { for (const value of remoteFetchGitConfig) {
this._gitService.setGitConfig(`remote.${remote}.fetch`, value, { add: true }); this._gitService.setGitConfig(key, value, { add: true });
} }
} }

View file

@ -6,6 +6,7 @@ import { Service } from '../decorator';
import { TerminalService } from './TerminalService'; import { TerminalService } from './TerminalService';
import { Stopwatch } from '../logic/Stopwatch'; import { Stopwatch } from '../logic/Stopwatch';
import { TelemetryService } from './TelemetryService'; import { TelemetryService } from './TelemetryService';
import { CommandService } from './CommandService';
/** /**
* @alpha * @alpha
@ -34,6 +35,7 @@ export class GitService {
private _isSparseCheckoutMode: boolean | undefined; private _isSparseCheckoutMode: boolean | undefined;
@inject(TerminalService) private _terminalService!: TerminalService; @inject(TerminalService) private _terminalService!: TerminalService;
@inject(TelemetryService) private _telemetryService!: TelemetryService; @inject(TelemetryService) private _telemetryService!: TelemetryService;
@inject(CommandService) private _commandService!: CommandService;
public setGitConfig( public setGitConfig(
k: string, k: string,
@ -248,14 +250,18 @@ export class GitService {
this._terminalService.terminal.writeDebugLine(`Invoked git command done (${stopwatch.toString()})`); this._terminalService.terminal.writeDebugLine(`Invoked git command done (${stopwatch.toString()})`);
this._terminalService.writeTaskFooter(); this._terminalService.writeTaskFooter();
stopwatch.stop(); stopwatch.stop();
this._telemetryService.collectTelemetry({ if (result.status === 0) {
commandName: args[0], this._telemetryService.collectTelemetry({
args: args.slice(1), commandName: args[0],
durationInSeconds: stopwatch.duration, args: args.slice(1),
startTimestampMs: stopwatch.startTime, durationInSeconds: stopwatch.duration,
endTimestampMs: stopwatch.endTime, startTimestampMs: stopwatch.startTime,
isRawGitCommand: true endTimestampMs: stopwatch.endTime,
}); isRawGitCommand: true
});
} else {
this._commandService.setHasInternalError();
}
return result; return result;
} }
@ -274,14 +280,16 @@ export class GitService {
}); });
this._terminalService.terminal.writeDebugLine(`Invoked git command done (${stopwatch.toString()})`); this._terminalService.terminal.writeDebugLine(`Invoked git command done (${stopwatch.toString()})`);
stopwatch.stop(); stopwatch.stop();
this._telemetryService.collectTelemetry({ if (result.status === 0) {
commandName: args[0], this._telemetryService.collectTelemetry({
args: args.slice(1), commandName: args[0],
durationInSeconds: stopwatch.duration, args: args.slice(1),
startTimestampMs: stopwatch.startTime, durationInSeconds: stopwatch.duration,
endTimestampMs: stopwatch.endTime, startTimestampMs: stopwatch.startTime,
isRawGitCommand: true endTimestampMs: stopwatch.endTime,
}); isRawGitCommand: true
});
}
this._processResult(result); this._processResult(result);
return result.stdout.toString(); return result.stdout.toString();
} }
@ -487,31 +495,54 @@ Please specify a directory on the command line
public checkRemoteBranchExistenceAsync = async (remote: string, branch: string): Promise<boolean> => { public checkRemoteBranchExistenceAsync = async (remote: string, branch: string): Promise<boolean> => {
const gitPath: string = this.getGitPathOrThrow(); const gitPath: string = this.getGitPathOrThrow();
const currentWorkingDirectory: string = this.getRepoInfo().root; const currentWorkingDirectory: string = this.getRepoInfo().root;
const childProcess: child_process.ChildProcess = Executable.spawn( const isDebug: boolean = this._terminalService.isDebug;
gitPath, const lsRemoteArgs: string[] = ['ls-remote', '--exit-code', '--heads', remote, branch];
['ls-remote', '--exit-code', remote, branch], const { terminal } = this._terminalService;
{ terminal.writeDebugLine(`Running git ${lsRemoteArgs.join(' ')}...`);
currentWorkingDirectory, const childProcess: child_process.ChildProcess = Executable.spawn(gitPath, lsRemoteArgs, {
stdio: ['ignore', 'pipe', 'pipe'] currentWorkingDirectory,
} stdio: ['ignore', 'pipe', 'pipe']
); });
if (!childProcess.stdout || !childProcess.stderr) { if (!childProcess.stdout || !childProcess.stderr) {
this._terminalService.terminal.writeDebugLine(`Failed to spawn git process, fallback to spawnSync`); terminal.writeDebugLine(`Failed to spawn git process, fallback to spawnSync`);
const result: string = this.executeGitCommandAndCaptureOutput({ const result: string = this.executeGitCommandAndCaptureOutput({
args: ['ls-remote', remote, branch] args: ['ls-remote', remote, branch]
}).trim(); }).trim();
return Promise.resolve(!!result); return Promise.resolve(!!result);
} }
if (isDebug) {
childProcess.stdout.on('data', (data: Buffer) => {
const text: string = data.toString();
terminal.writeDebugLine(text);
});
childProcess.stderr.on('data', (data: Buffer) => {
const text: string = data.toString();
terminal.writeDebugLine(text);
});
}
return await new Promise((resolve, reject) => { return await new Promise((resolve, reject) => {
// Only care about exit code since specifying --exit-code // Only care about exit code since specifying --exit-code
childProcess.on('close', (exitCode: number | null) => { childProcess.on('close', (exitCode: number | null) => {
if (exitCode) { // Allow exitCode 128. It indicates permission issue
this._terminalService.terminal.writeDebugLine(`Branch "${branch}" doesn't exist remotely`); switch (exitCode) {
resolve(false); case 0: {
} else { terminal.writeDebugLine(`Branch "${branch}" exists remotely`);
this._terminalService.terminal.writeDebugLine(`Branch "${branch}" exists remotely`); break;
resolve(true); }
case 2: {
terminal.writeDebugLine(`Branch "${branch}" doesn't exist remotely`);
return resolve(false);
}
case 128: {
terminal.writeDebugLine(`Check "${branch}" failed because of permission issue`);
break;
}
default: {
terminal.writeDebugLine(`Check "${branch}" returns unknown exit code ${exitCode}`);
break;
}
} }
resolve(true);
}); });
}); });
}; };

View file

@ -65,6 +65,10 @@ export class TerminalService {
this._terminal.writeLine(Colorize.gray('-'.repeat(this._asciiHeaderWidth))); this._terminal.writeLine(Colorize.gray('-'.repeat(this._asciiHeaderWidth)));
this._terminal.writeLine(); this._terminal.writeLine();
} }
public get isDebug(): boolean {
return this._terminalProvider.debugEnabled;
}
} }
export type { ITerminal }; export type { ITerminal };

View file

@ -0,0 +1,10 @@
{
"changes": [
{
"packageName": "sparo",
"comment": "Improve logic to check existence of a branch",
"type": "none"
}
],
"packageName": "sparo"
}

View file

@ -0,0 +1,10 @@
{
"changes": [
{
"packageName": "sparo",
"comment": "Enhance metric logic to accurately report success data",
"type": "none"
}
],
"packageName": "sparo"
}

View file

@ -122,6 +122,8 @@ export class Sparo {
export class TerminalService { export class TerminalService {
constructor(); constructor();
// (undocumented) // (undocumented)
get isDebug(): boolean;
// (undocumented)
setIsDebug(value: boolean): void; setIsDebug(value: boolean): void;
// (undocumented) // (undocumented)
setIsVerbose(value: boolean): void; setIsVerbose(value: boolean): void;