Skip to content

Commit

Permalink
feat: Add support for parsing pull requests from bitbucket
Browse files Browse the repository at this point in the history
Signed-off-by: Yoriyasu Yano <[email protected]>
  • Loading branch information
yorinasub17 committed Oct 30, 2023
1 parent 5a7c49e commit 0d65d22
Show file tree
Hide file tree
Showing 8 changed files with 648 additions and 62 deletions.
2 changes: 1 addition & 1 deletion src/engine/patch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export function parseUnifiedDiff(diffTxt: string): IHunk[] {
updatedLength: parseInt(cg.updatedLength),
diffOperations: [],
};
} else if (curHunk != null) {
} else if (curHunk != null && line != "") {
let op: LineOp;
switch (line[0]) {
default:
Expand Down
68 changes: 68 additions & 0 deletions src/sourcer/from.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,74 @@
// Copyright (c) Fensak, LLC.
// SPDX-License-Identifier: AGPL-3.0-or-later OR BUSL-1.1

import YAML from "yaml";
import toml from "toml";
import JSON5 from "json5";

import { IChangeSetMetadata, IPatch } from "../engine/patch_types.ts";

export enum SourcePlatform {
GitHub = "gh",
BitBucket = "bb",
}

/**
* Represents a repository hosted on a remote VCS (e.g., GitHub or BitBucket).
* @property owner The owner of the repository.
* @property name The name of the repository.
*/
export type Repository = {
owner: string;
name: string;
};

/**
* Represents the decoded patches for the Pull Request. This also includes a mapping from patch IDs to the URL to
* retrieve the file contents.
* @property patchList The list of file patches that are included in this PR.
* @property patchFetchMap A mapping from a URL hash to the URL to fetch the contents for the file. The URL hash is
* the sha256 hash of the URL with a random salt.
*/
export type PullRequestPatches = {
metadata: IChangeSetMetadata;
patchList: IPatch[];
patchFetchMap: Record<string, URL>;
};

// eslint-disable-next-line no-var,@typescript-eslint/no-explicit-any
export type Parser = (s: string) => any;

export function objectParserFromFilename(fname: string): Parser | null {
// Get the file extension to determine the file type
const m = /(?:\.([^.]+))?$/.exec(fname);
if (m === null) {
return null;
}
const ext = m[1];

const supportedObjectExtensions = ["json", "json5", "yaml", "yml", "toml"];
if (!supportedObjectExtensions.includes(ext)) {
return null;
}

// At this point, we know the object can be parsed out of the file so start to pull down the contents.
// eslint-disable-next-line no-var,@typescript-eslint/no-explicit-any
switch (ext) {
default:
// Throw error becauset this should never happen given the check for supportedObjectExtensions.
throw new Error(`unsupported file extension ${ext} for ${fname}`);

case "json":
return JSON.parse;

case "json5":
return JSON5.parse;

case "yaml":
case "yml":
return YAML.parse;

case "toml":
return toml.parse;
}
}
130 changes: 130 additions & 0 deletions src/sourcer/from_bitbucket.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
// Copyright (c) Fensak, LLC.
// SPDX-License-Identifier: AGPL-3.0-or-later OR BUSL-1.1

import { expect, test } from "@jest/globals";

import { BitBucket } from "../bbstd/index.ts";
import { PatchOp, LineOp, IObjectDiff } from "../engine/patch_types.ts";

import type { Repository } from "./from.ts";
import { patchFromBitBucketPullRequest } from "./from_bitbucket.ts";

const bitbucket = new BitBucket();
const testRepo: Repository = {
owner: "fensak-test",
name: "test-fensak-automated-readme-only",
};

test("a single file change from BitBucket is parsed correctly", async () => {
// View PR at
// https://bitbucket.org/fensak-test/test-fensak-automated-readme-only/pull-requests/1
const patches = await patchFromBitBucketPullRequest(bitbucket, testRepo, 1);
expect(patches.metadata).toEqual({
sourceBranch: "test/update-readme",
targetBranch: "main",
linkedPRs: [],
});
expect(patches.patchList.length).toEqual(1);

// Check top level patch
const patch = patches.patchList[0];
expect(patch.path).toEqual("README.md");
expect(patch.op).toEqual(PatchOp.Modified);
expect(patch.diff.length).toEqual(1);
expect(patch.objectDiff).toBeNull();

// Check patch hunks
const hunk = patch.diff[0];
expect(hunk.originalStart).toEqual(1);
expect(hunk.originalLength).toEqual(3);
expect(hunk.updatedStart).toEqual(1);
expect(hunk.updatedLength).toEqual(5);
expect(hunk.diffOperations).toEqual([
{
op: LineOp.Untouched,
text: "# test-fensak-automated",
newText: "",
},
{
op: LineOp.Untouched,
text: "",
newText: "",
},
{
op: LineOp.Untouched,
text: "A test repo for automated testing.",
newText: "",
},
{
op: LineOp.Insert,
text: "",
newText: "",
},
{
op: LineOp.Insert,
text: "<!-- nonce change -->",
newText: "",
},
]);
});

test("single config file change from BitBucket is parsed correctly", async () => {
// View PR at
// https://bitbucket.org/fensak-test/test-fensak-automated-readme-only/pull-requests/2
const patches = await patchFromBitBucketPullRequest(bitbucket, testRepo, 2);
expect(patches.metadata).toEqual({
sourceBranch: "test/update-conf",
targetBranch: "main",
linkedPRs: [],
});
expect(patches.patchList.length).toEqual(1);

// Check top level patch
const patch = patches.patchList[0];
expect(patch.path).toEqual("conf.json");
expect(patch.op).toEqual(PatchOp.Modified);
expect(patch.diff.length).toEqual(1);

// Check patch hunks
const hunk = patch.diff[0];
expect(hunk.originalStart).toEqual(1);
expect(hunk.originalLength).toEqual(3);
expect(hunk.updatedStart).toEqual(1);
expect(hunk.updatedLength).toEqual(3);
expect(hunk.diffOperations).toEqual([
{
op: LineOp.Untouched,
text: "{",
newText: "",
},
{
op: LineOp.Modified,
text: ` "my-config": true`,
newText: ` "my-config": false`,
},
{
op: LineOp.Untouched,
text: "}",
newText: "",
},
]);

// Check object diffs
const maybeObjDiff = patch.objectDiff;
expect(maybeObjDiff).not.toBeNull();
const objDiff = maybeObjDiff as IObjectDiff;
expect(objDiff.previous).toEqual({
"my-config": true,
});
expect(objDiff.current).toEqual({
"my-config": false,
});
expect(objDiff.diff).toEqual([
{
type: "CHANGE",
path: ["my-config"],
value: false,
oldValue: true,
},
]);
});
Loading

0 comments on commit 0d65d22

Please sign in to comment.