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: 2 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
All notable changes to this project will be documented in this file.

<!-- git-cliff-unreleased-start -->

## 0.5.7 - **not yet released**

### 🚀 Features
Expand All @@ -14,7 +15,6 @@ All notable changes to this project will be documented in this file.

- Bump version to test beta release ([1322d31](https://github.com/apify/apify-test-tools/commit/1322d31873b6d43e16a68e97bdc358752f813f79)) by [@metalwarrior665](https://github.com/metalwarrior665)


<!-- git-cliff-unreleased-end -->

# Changelog
Expand Down Expand Up @@ -91,4 +91,4 @@ feat: feat: add maxRetriesPerRequest test
### Cli

- fix: parsing commits
- feat: add `--workspace` cli option
- feat: add `--workspace` cli option
26 changes: 25 additions & 1 deletion bin/git.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,35 @@ export const getChangedFiles = (commits: Commit[]) => {
return changedFiles;
};

const SHA_REGEX = /^[0-9a-f]{40}$/i;

/**
*
* @param shaOrCommit Supports both a SHA string or a Commit object in JSON format. Can be empty.
* @returns The SHA string if valid, otherwise throws an error.
*/
export const parseBaseCommit = (shaOrCommit: string | undefined): string | undefined => {
if (!shaOrCommit) return undefined;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't we throw an error here?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can keep it optional. Now the first commit in PR gets empty string

let sha: string;
if (shaOrCommit.startsWith('{')) {
sha = (JSON.parse(shaOrCommit) as Commit).sha;
} else {
sha = shaOrCommit;
}
if (!SHA_REGEX.test(sha)) {
throw new Error(
`Invalid base commit SHA: "${sha}". It should be a 40-character hexadecimal string, instead got input: "${shaOrCommit}".`,
);
}
return sha;
};

/**
* Gets the commits between sourceBranch and targetBranch (exclusive).
* - If baseCommit is provided, only returns commits after the baseCommit.
*/
export const getCommits = ({ sourceBranch, targetBranch, baseCommit: baseCommitSha }: Config): Commit[] => {
export const getCommits = ({ sourceBranch, targetBranch, baseCommit }: Config): Commit[] => {
const baseCommitSha = parseBaseCommit(baseCommit);
const commitsStrings = spawnCommandInGhWorkspace(
`git log --pretty=format:'${GIT_LOG_FORMAT}' ${targetBranch}..${sourceBranch}`,
).split('\n');
Expand Down
2 changes: 0 additions & 2 deletions lib/extend-expect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -291,7 +291,6 @@ const isWithinInterval = <T extends string>(
diffs.pass = false;
diffs.actual.push(`${intervalOption}=${actual}`);
diffs.expected.push(`${intervalOption}=${expected}`);
// eslint-disable-next-line consistent-return
return false;
}
} else if (typeof expected === 'object') {
Expand All @@ -301,7 +300,6 @@ const isWithinInterval = <T extends string>(
diffs.pass = false;
diffs.actual.push(`${intervalOption}=${actual}`);
diffs.expected.push(`${intervalOption}=<${min ?? ''},${max ?? ''}>`);
// eslint-disable-next-line consistent-return
return false;
}
}
Expand Down
75 changes: 56 additions & 19 deletions test/unit/bin/git.test.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
import type { MockInstance } from 'vitest';
import { beforeEach, describe, expect, it, vi } from 'vitest';

import { getChangedFiles, getCommits } from '../../../bin/git.js';
import { getChangedFiles, getCommits, parseBaseCommit } from '../../../bin/git.js';
import * as Utils from '../../../bin/utils.js';

describe('getCommits', () => {
const sourceBranch = 'feature-branch';
const targetBranch = 'main';

const commit1 = 'Commit1»¦«Author1»¦«Date1»¦«First Change On Feature';
const commit2 = 'Commit2»¦«Author1»¦«Date2»¦«Second Change On Feature';
const commit3 = 'Commit3»¦«Author1»¦«Date3»¦«Third Change On Feature';
const sha1 = '1'.repeat(40);
const sha2 = '2'.repeat(40);
const sha3 = '3'.repeat(40);

const commit1 = `${sha1}»¦«Author1»¦«Date1»¦«First Change On Feature`;
const commit2 = `${sha2}»¦«Author1»¦«Date2»¦«Second Change On Feature`;
const commit3 = `${sha3}»¦«Author1»¦«Date3»¦«Third Change On Feature`;

let gitCommandSpy: MockInstance;

Expand All @@ -26,9 +30,9 @@ describe('getCommits', () => {

// Assert
expect(commits).toStrictEqual([
{ sha: 'Commit1', author: 'Author1', date: 'Date1', message: 'First Change On Feature' },
{ sha: 'Commit2', author: 'Author1', date: 'Date2', message: 'Second Change On Feature' },
{ sha: 'Commit3', author: 'Author1', date: 'Date3', message: 'Third Change On Feature' },
{ sha: sha1, author: 'Author1', date: 'Date1', message: 'First Change On Feature' },
{ sha: sha2, author: 'Author1', date: 'Date2', message: 'Second Change On Feature' },
{ sha: sha3, author: 'Author1', date: 'Date3', message: 'Third Change On Feature' },
]);

expect(gitCommandSpy).toHaveBeenCalledTimes(1);
Expand All @@ -39,12 +43,12 @@ describe('getCommits', () => {

it('should return commits after the base commit if provided', () => {
// Act
const commits = getCommits({ sourceBranch, targetBranch, baseCommit: 'Commit1' });
const commits = getCommits({ sourceBranch, targetBranch, baseCommit: sha1 });

// Assert
expect(commits).toStrictEqual([
{ sha: 'Commit2', author: 'Author1', date: 'Date2', message: 'Second Change On Feature' },
{ sha: 'Commit3', author: 'Author1', date: 'Date3', message: 'Third Change On Feature' },
{ sha: sha2, author: 'Author1', date: 'Date2', message: 'Second Change On Feature' },
{ sha: sha3, author: 'Author1', date: 'Date3', message: 'Third Change On Feature' },
]);

expect(gitCommandSpy).toHaveBeenCalledTimes(1);
Expand All @@ -55,13 +59,13 @@ describe('getCommits', () => {

it('should return all commits if base commit is not found', () => {
// Act
const commits = getCommits({ sourceBranch, targetBranch, baseCommit: 'NonExistingCommit' });
const commits = getCommits({ sourceBranch, targetBranch, baseCommit: 'a'.repeat(40) });

// Assert
expect(commits).toStrictEqual([
{ sha: 'Commit1', author: 'Author1', date: 'Date1', message: 'First Change On Feature' },
{ sha: 'Commit2', author: 'Author1', date: 'Date2', message: 'Second Change On Feature' },
{ sha: 'Commit3', author: 'Author1', date: 'Date3', message: 'Third Change On Feature' },
{ sha: sha1, author: 'Author1', date: 'Date1', message: 'First Change On Feature' },
{ sha: sha2, author: 'Author1', date: 'Date2', message: 'Second Change On Feature' },
{ sha: sha3, author: 'Author1', date: 'Date3', message: 'Third Change On Feature' },
]);

expect(gitCommandSpy).toHaveBeenCalledTimes(1);
Expand All @@ -80,9 +84,11 @@ describe('getChangedFiles', () => {

it('should return changed files between commits', () => {
// Arrange
const firstSha = '1'.repeat(40);
const lastSha = '3'.repeat(40);
const commits = [
{ sha: 'Commit1', author: '', date: '', message: '' },
{ sha: 'Commit3', author: '', date: '', message: '' },
{ sha: firstSha, author: '', date: '', message: '' },
{ sha: lastSha, author: '', date: '', message: '' },
];

// Act
Expand All @@ -92,12 +98,13 @@ describe('getChangedFiles', () => {
expect(changedFiles).toStrictEqual(['file1.txt', 'folder/file2.txt']);

expect(gitCommandSpy).toHaveBeenCalledTimes(1);
expect(gitCommandSpy).toHaveBeenCalledWith(`git diff --name-only Commit1~..Commit3`);
expect(gitCommandSpy).toHaveBeenCalledWith(`git diff --name-only ${firstSha}~..${lastSha}`);
});

it('should handle only one commit', () => {
// Arrange
const commits = [{ sha: 'Commit1', author: '', date: '', message: '' }];
const onlySha = '1'.repeat(40);
const commits = [{ sha: onlySha, author: '', date: '', message: '' }];

// Act
const changedFiles = getChangedFiles(commits);
Expand All @@ -106,6 +113,36 @@ describe('getChangedFiles', () => {
expect(changedFiles).toStrictEqual(['file1.txt', 'folder/file2.txt']);

expect(gitCommandSpy).toHaveBeenCalledTimes(1);
expect(gitCommandSpy).toHaveBeenCalledWith(`git diff --name-only Commit1~..Commit1`);
expect(gitCommandSpy).toHaveBeenCalledWith(`git diff --name-only ${onlySha}~..${onlySha}`);
});
});

const VALID_SHA = 'a'.repeat(40);
const VALID_JSON = JSON.stringify({ sha: VALID_SHA, author: 'test', date: 'now', message: 'msg' });

describe('parseBaseCommit', () => {
it('should return undefined for undefined input', () => {
expect(parseBaseCommit(undefined)).toBeUndefined();
});

it('should return undefined for empty string', () => {
expect(parseBaseCommit('')).toBeUndefined();
});

it('should accept a plain SHA string', () => {
expect(parseBaseCommit(VALID_SHA)).toBe(VALID_SHA);
});

it('should extract sha from a JSON commit object', () => {
expect(parseBaseCommit(VALID_JSON)).toBe(VALID_SHA);
});

it('should throw on an invalid SHA string', () => {
expect(() => parseBaseCommit('not-a-sha')).toThrow('Invalid base commit SHA');
});

it('should throw when JSON contains an invalid sha field', () => {
const badJson = JSON.stringify({ sha: 'bad', author: 'test', date: 'now', message: 'msg' });
expect(() => parseBaseCommit(badJson)).toThrow('Invalid base commit SHA');
});
});
Loading