Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions dist/git-operations.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ export declare class GitOperations {
* Generate diff between two branches
*/
generateDiff(baseBranch: string, featureBranch: string): GitDiff;
/**
* Resolve branch name to a git reference (handles both local and remote branches)
*/
private resolveBranchRef;
/**
* Validate that specified branches exist
*/
Expand Down
2 changes: 1 addition & 1 deletion dist/git-operations.d.ts.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

45 changes: 42 additions & 3 deletions dist/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -27532,10 +27532,13 @@ class GitOperations {
try {
// Validate both branches exist
this.validateBranches([baseBranch, featureBranch]);
// Resolve branch references (handles local vs remote branches)
const baseRef = this.resolveBranchRef(baseBranch);
const featureRef = this.resolveBranchRef(featureBranch);
// Get commit range
const commits = this.getCommitsBetweenBranches(baseBranch, featureBranch);
const commits = this.getCommitsBetweenBranches(baseRef, featureRef);
// Get file changes
const fileChanges = this.getFileChangesBetweenBranches(baseBranch, featureBranch);
const fileChanges = this.getFileChangesBetweenBranches(baseRef, featureRef);
// Calculate totals
const totalAdditions = fileChanges.reduce((sum, change) => sum + change.additions, 0);
const totalDeletions = fileChanges.reduce((sum, change) => sum + change.deletions, 0);
Expand All @@ -27561,16 +27564,52 @@ class GitOperations {
throw new GitOperationError(`Failed to generate diff: ${error instanceof Error ? error.message : String(error)}`, `git diff ${baseBranch}..${featureBranch}`);
}
}
/**
* Resolve branch name to a git reference (handles both local and remote branches)
*/
resolveBranchRef(branch) {
try {
// First try local branch
this.executeGitCommand(`rev-parse --verify ${branch}`);
return branch;
}
catch {
try {
// Try remote branch
this.executeGitCommand(`rev-parse --verify origin/${branch}`);
return `origin/${branch}`;
}
catch {
// If neither exists, return original name and let git handle the error
return branch;
}
}
}
/**
* Validate that specified branches exist
*/
validateBranches(branches) {
for (const branch of branches) {
try {
// First try to verify local branch
this.executeGitCommand(`rev-parse --verify ${branch}`);
}
catch {
throw new GitOperationError(`Branch '${branch}' does not exist`);
try {
// If local branch doesn't exist, try remote branch
this.executeGitCommand(`rev-parse --verify origin/${branch}`);
core.debug(`Branch '${branch}' found as remote branch 'origin/${branch}'`);
}
catch {
// If neither local nor remote branch exists, try to fetch it
try {
this.executeGitCommand(`fetch origin ${branch}:${branch}`);
core.debug(`Fetched branch '${branch}' from remote`);
}
catch {
throw new GitOperationError(`Branch '${branch}' does not exist locally or on remote`);
}
}
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion dist/index.js.map

Large diffs are not rendered by default.

47 changes: 31 additions & 16 deletions src/__tests__/git-operations.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -172,10 +172,13 @@ describe('GitOperations', () => {

describe('generateDiff', () => {
it('should generate diff between branches', () => {
// Mock branch validation
// Mock branch validation (validateBranches)
mockExecSync
.mockReturnValueOnce('abc123\n') // rev-parse --verify main
.mockReturnValueOnce('def456\n') // rev-parse --verify feature
.mockReturnValueOnce('abc123\n') // validateBranches: rev-parse --verify main (succeeds locally)
.mockReturnValueOnce('def456\n') // validateBranches: rev-parse --verify feature (succeeds locally)
// Mock branch reference resolution (resolveBranchRef)
.mockReturnValueOnce('abc123\n') // resolveBranchRef: rev-parse --verify main (succeeds locally)
.mockReturnValueOnce('def456\n') // resolveBranchRef: rev-parse --verify feature (succeeds locally)
.mockReturnValueOnce(
`abc123|feat: add new feature|John Doe|john@example.com|1640995200|John Doe|john@example.com|1640995200\n`
) // git log
Expand Down Expand Up @@ -230,8 +233,11 @@ describe('GitOperations', () => {

it('should handle empty diff', () => {
mockExecSync
.mockReturnValueOnce('abc123\n') // rev-parse --verify main
.mockReturnValueOnce('def456\n') // rev-parse --verify feature
.mockReturnValueOnce('abc123\n') // validateBranches: rev-parse --verify main (succeeds locally)
.mockReturnValueOnce('def456\n') // validateBranches: rev-parse --verify feature (succeeds locally)
// Mock branch reference resolution (resolveBranchRef)
.mockReturnValueOnce('abc123\n') // resolveBranchRef: rev-parse --verify main (succeeds locally)
.mockReturnValueOnce('def456\n') // resolveBranchRef: rev-parse --verify feature (succeeds locally)
.mockReturnValueOnce('') // git log (empty)
.mockReturnValueOnce(''); // diff --numstat (empty)

Expand All @@ -258,8 +264,8 @@ describe('GitOperations', () => {
describe('validateBranches', () => {
it('should validate existing branches', () => {
mockExecSync
.mockReturnValueOnce('abc123\n') // rev-parse main
.mockReturnValueOnce('def456\n'); // rev-parse feature
.mockReturnValueOnce('abc123\n') // rev-parse main (succeeds locally)
.mockReturnValueOnce('def456\n'); // rev-parse feature (succeeds locally)

expect(() => gitOps.validateBranches(['main', 'feature'])).not.toThrow();
});
Expand All @@ -268,13 +274,17 @@ describe('GitOperations', () => {
// Reset the mock to avoid interference from previous tests
mockExecSync.mockReset();

// First call for 'main' branch - succeeds
// Second call for 'nonexistent' branch - fails
// Mock sequence: local branch check fails, remote branch check fails, fetch fails
mockExecSync
.mockReturnValueOnce('abc123\n') // First call succeeds
.mockReturnValueOnce('abc123\n') // First call for 'main' succeeds
.mockImplementationOnce(() => {
// Second call fails
throw new Error('Branch does not exist');
throw new Error('Branch does not exist'); // Local branch check fails
})
.mockImplementationOnce(() => {
throw new Error('Remote branch does not exist'); // Remote branch check fails
})
.mockImplementationOnce(() => {
throw new Error('Fetch failed'); // Fetch attempt fails
});

// The first call will test 'main' successfully, but the second call will fail on 'nonexistent'
Expand All @@ -283,14 +293,19 @@ describe('GitOperations', () => {
// Reset and test again for the error message
mockExecSync.mockReset();
mockExecSync
.mockReturnValueOnce('abc123\n') // First call succeeds
.mockReturnValueOnce('abc123\n') // First call for 'main' succeeds
.mockImplementationOnce(() => {
// Second call fails
throw new Error('Branch does not exist');
throw new Error('Branch does not exist'); // Local branch check fails
})
.mockImplementationOnce(() => {
throw new Error('Remote branch does not exist'); // Remote branch check fails
})
.mockImplementationOnce(() => {
throw new Error('Fetch failed'); // Fetch attempt fails
});

expect(() => gitOps.validateBranches(['main', 'nonexistent'])).toThrow(
"Branch 'nonexistent' does not exist"
"Branch 'nonexistent' does not exist locally or on remote"
);
});
});
Expand Down
43 changes: 40 additions & 3 deletions src/git-operations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,11 +118,15 @@ export class GitOperations {
// Validate both branches exist
this.validateBranches([baseBranch, featureBranch]);

// Resolve branch references (handles local vs remote branches)
const baseRef = this.resolveBranchRef(baseBranch);
const featureRef = this.resolveBranchRef(featureBranch);

// Get commit range
const commits = this.getCommitsBetweenBranches(baseBranch, featureBranch);
const commits = this.getCommitsBetweenBranches(baseRef, featureRef);

// Get file changes
const fileChanges = this.getFileChangesBetweenBranches(baseBranch, featureBranch);
const fileChanges = this.getFileChangesBetweenBranches(baseRef, featureRef);

// Calculate totals
const totalAdditions = fileChanges.reduce((sum, change) => sum + change.additions, 0);
Expand Down Expand Up @@ -156,15 +160,48 @@ export class GitOperations {
}
}

/**
* Resolve branch name to a git reference (handles both local and remote branches)
*/
private resolveBranchRef(branch: string): string {
try {
// First try local branch
this.executeGitCommand(`rev-parse --verify ${branch}`);
return branch;
} catch {
try {
// Try remote branch
this.executeGitCommand(`rev-parse --verify origin/${branch}`);
return `origin/${branch}`;
} catch {
// If neither exists, return original name and let git handle the error
return branch;
}
}
}

/**
* Validate that specified branches exist
*/
validateBranches(branches: string[]): void {
for (const branch of branches) {
try {
// First try to verify local branch
this.executeGitCommand(`rev-parse --verify ${branch}`);
} catch {
throw new GitOperationError(`Branch '${branch}' does not exist`);
try {
// If local branch doesn't exist, try remote branch
this.executeGitCommand(`rev-parse --verify origin/${branch}`);
core.debug(`Branch '${branch}' found as remote branch 'origin/${branch}'`);
} catch {
// If neither local nor remote branch exists, try to fetch it
try {
this.executeGitCommand(`fetch origin ${branch}:${branch}`);
core.debug(`Fetched branch '${branch}' from remote`);
} catch {
throw new GitOperationError(`Branch '${branch}' does not exist locally or on remote`);
}
}
}
}
}
Expand Down