Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for getting commits of line range #61

Merged
merged 1 commit into from Oct 2, 2020
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 4 additions & 0 deletions README.md
Expand Up @@ -135,6 +135,10 @@ Show only commits in the specified branch or revision range.

By default uses the current branch and defaults to `HEAD` (i.e. the whole history leading to the current commit).

### fileLineRange

Optional field for getting only the commits that affected a specific line range of a given file.

### file

Optional file filter for the `git log` command
Expand Down
22 changes: 21 additions & 1 deletion src/index.ts
Expand Up @@ -29,6 +29,20 @@ export type CommitField = keyof typeof fieldMap;
const notOptFields = ["status", "files"] as const;
type NotOptField = typeof notOptFields[number];

export interface FileLineRange {
/** Will be pass as -L <startLine>,<endLine>:<file> */

/** The file to get the commits for */
file: string;
/** The number of the first line in the desired range */
startLine: number;
/**
* Either the absolute line number for the end of the desired range,
* or the offset from the startLine
*/
endLine: number | string;
}

const defaultFields = [
"abbrevHash",
"hash",
Expand Down Expand Up @@ -82,6 +96,8 @@ export interface GitlogOptions<Fields extends string = DefaultField> {
* the whole history leading to the current commit).
*/
branch?: string;
/** Range of lines for a given file to find the commits for */
fileLineRange?: FileLineRange;
/** File filter for the git log command */
file?: string;
/** Limit the commits output to ones with author header lines that match the specified pattern. */
Expand Down Expand Up @@ -263,10 +279,14 @@ function createCommand<T extends CommitField | DefaultField = DefaultField>(
}

// File and file status
if (options.nameStatus) {
if (options.nameStatus && !options.fileLineRange) {
command += " --name-status";
}

if (options.fileLineRange) {
command += ` -L ${options.fileLineRange.startLine},${options.fileLineRange.endLine}:${options.fileLineRange.file}`;
}

if (options.file) {
command += ` -- ${options.file}`;
}
Expand Down
17 changes: 17 additions & 0 deletions test/create-repo.sh
Expand Up @@ -58,6 +58,23 @@ git checkout master
git merge --no-edit --no-ff new-merge-branch
git branch -d new-merge-branch

# Modify specific lines
touch fileToModify
# Add contents large enough to break up diffs
for i in {1..20}
do
printf "$i\n" >> fileToModify

done
git add fileToModify
git commit -m "added long content file"
sed -i '' -e 's/2/4/g' ./fileToModify
git add fileToModify
git commit -m "Modify multiple parts of the file and come close to, but not the first line"
sed -i '' -e 's/40/44/' ./fileToModify
git add fileToModify
git commit -m "Modify end of the file"

# git symbolic-ref HEAD refs/heads/test-branch
# rm .git/index
# git clen -fdx
Expand Down
91 changes: 56 additions & 35 deletions test/gitlog.test.ts
Expand Up @@ -58,23 +58,23 @@ describe("gitlog", () => {
);
});

it("returns 22 commits from repository with all=false", (done) => {
it("returns 25 commits from repository with all=false", (done) => {
gitlog(
{ repo: testRepoLocation, all: false, number: 100 },
(err, commits) => {
expect(err).toBeNull();
expect(commits.length).toBe(22);
expect(commits.length).toBe(25);
done();
}
);
});

it("returns 23 commits from repository with all=true", (done) => {
it("returns 26 commits from repository with all=true", (done) => {
gitlog(
{ repo: testRepoLocation, all: true, number: 100 },
(err, commits) => {
expect(err).toBeNull();
expect(commits.length).toBe(23);
expect(commits.length).toBe(26);
done();
}
);
Expand Down Expand Up @@ -153,17 +153,6 @@ describe("gitlog", () => {
expect(commits[0].files).not.toBeDefined();
});

it("returns nameStatus fields", () => {
const commits = gitlog({ repo: testRepoLocation });

expect(commits[0].abbrevHash).toBeDefined();
expect(commits[0].subject).toBeDefined();
expect(commits[0].authorName).toBeDefined();
expect(commits[0].hash).toBeDefined();
expect(commits[0].status).toBeDefined();
expect(commits[0].files).toBeDefined();
});

it('returns fields with "since" limit', () => {
const commits = gitlog({ repo: testRepoLocation, since: "1 minutes ago" });
expect(commits).toHaveLength(10);
Expand Down Expand Up @@ -237,40 +226,51 @@ describe("gitlog", () => {
});
});

it("returns A status for files that are added", () => {
const commits = gitlog({ repo: testRepoLocation });
expect(commits[1].status[0]).toBe("A");
});

it("returns C100 status for files that are copied", () => {
const commits = gitlog({ repo: testRepoLocation, findCopiesHarder: true });
expect(commits[1].status[0]).toBe("C100");
expect(commits[4].status[0]).toBe("C100");
});

it("returns merge commits files when includeMergeCommitFiles is true", () => {
const commits = gitlog({
repo: testRepoLocation,
includeMergeCommitFiles: true,
});
expect(commits[0].files[0]).toBe("foo");
expect(commits[3].files[0]).toBe("foo");
});

it("returns M status for files that are modified", () => {
const commits = gitlog({ repo: testRepoLocation });
expect(commits[3].status[0]).toBe("M");
});
describe("Only repo option", () => {
let commits: any[];
beforeAll(() => {
commits = gitlog({ repo: testRepoLocation });
});

it("returns D status for files that are deleted", () => {
const commits = gitlog({ repo: testRepoLocation });
expect(commits[4].status[0]).toBe("D");
});
it("returns nameStatus fields", () => {
expect(commits[0].abbrevHash).toBeDefined();
expect(commits[0].subject).toBeDefined();
expect(commits[0].authorName).toBeDefined();
expect(commits[0].hash).toBeDefined();
expect(commits[0].status).toBeDefined();
expect(commits[0].files).toBeDefined();
});

it("returns author name correctly", () => {
const commits = gitlog({ repo: testRepoLocation });
it("returns A status for files that are added", () => {
expect(commits[4].status[0]).toBe("A");
});

expect.assertions(10);
commits.forEach((commit) => {
expect(commit.authorName).toBe("Your Name");
it("returns M status for files that are modified", () => {
expect(commits[6].status[0]).toBe("M");
});

it("returns D status for files that are deleted", () => {
expect(commits[7].status[0]).toBe("D");
});

it("returns author name correctly", () => {
expect.assertions(10);
commits.forEach((commit) => {
expect(commit.authorName).toBe("Your Name");
});
});
});

Expand Down Expand Up @@ -299,6 +299,27 @@ describe("gitlog", () => {
expect(commits[0].rawBody).toBeDefined();
});

it("should be able to get commit counts for a specific line only", () => {
const commitsForFirstLine = gitlog({
repo: testRepoLocation,
fileLineRange: {
file: "fileToModify",
startLine: 1,
endLine: 1,
},
});
expect(commitsForFirstLine.length).toBe(1);
const commitsForLastLine = gitlog({
repo: testRepoLocation,
fileLineRange: {
file: "fileToModify",
startLine: 20,
endLine: 20,
},
});
expect(commitsForLastLine.length).toBe(3);
});

afterAll(() => {
execInTestDir(`${__dirname}/delete-repo.sh`);
});
Expand Down