Skip to content

Commit

Permalink
Merge pull request #61 from SalmonMode/file-line-range
Browse files Browse the repository at this point in the history
Add support for getting commits of line range
  • Loading branch information
hipstersmoothie committed Oct 2, 2020
2 parents e224f24 + af715c4 commit f26a134
Show file tree
Hide file tree
Showing 4 changed files with 98 additions and 36 deletions.
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

0 comments on commit f26a134

Please sign in to comment.