mobbdev 1.2.4 → 1.2.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/args/commands/upload_ai_blame.mjs +20 -43
- package/dist/index.mjs +912 -901
- package/package.json +3 -3
- package/src/post_install/binary.mjs +1 -1
package/dist/index.mjs
CHANGED
|
@@ -846,13 +846,12 @@ var init_client_generates = __esm({
|
|
|
846
846
|
}
|
|
847
847
|
`;
|
|
848
848
|
AnalyzeCommitForExtensionAiBlameDocument = `
|
|
849
|
-
mutation AnalyzeCommitForExtensionAIBlame($repositoryURL: String!, $commitSha: String!, $organizationId: String!, $commitTimestamp: Timestamp
|
|
849
|
+
mutation AnalyzeCommitForExtensionAIBlame($repositoryURL: String!, $commitSha: String!, $organizationId: String!, $commitTimestamp: Timestamp) {
|
|
850
850
|
analyzeCommitForAIBlame(
|
|
851
851
|
repositoryURL: $repositoryURL
|
|
852
852
|
commitSha: $commitSha
|
|
853
853
|
organizationId: $organizationId
|
|
854
854
|
commitTimestamp: $commitTimestamp
|
|
855
|
-
parentCommits: $parentCommits
|
|
856
855
|
) {
|
|
857
856
|
__typename
|
|
858
857
|
... on ProcessAIBlameFinalResult {
|
|
@@ -1623,6 +1622,7 @@ var init_issue = __esm({
|
|
|
1623
1622
|
MAX_SOURCE_CODE_FILE_SIZE_IN_BYTES = 1e5;
|
|
1624
1623
|
VulnerabilityReportIssueSharedStateZ = z9.object({
|
|
1625
1624
|
id: z9.string().uuid(),
|
|
1625
|
+
createdAt: z9.string(),
|
|
1626
1626
|
isArchived: z9.boolean(),
|
|
1627
1627
|
ticketIntegrationId: z9.string().uuid().nullable(),
|
|
1628
1628
|
ticketIntegrations: z9.array(
|
|
@@ -1849,6 +1849,7 @@ var init_types = __esm({
|
|
|
1849
1849
|
});
|
|
1850
1850
|
IssueSharedStateZ2 = z11.object({
|
|
1851
1851
|
id: z11.string().uuid(),
|
|
1852
|
+
createdAt: z11.string(),
|
|
1852
1853
|
isArchived: z11.boolean(),
|
|
1853
1854
|
ticketIntegrationId: z11.string().uuid().nullable(),
|
|
1854
1855
|
ticketIntegrations: z11.array(
|
|
@@ -3799,34 +3800,6 @@ ${rootContent}`;
|
|
|
3799
3800
|
throw new Error(errorMessage);
|
|
3800
3801
|
}
|
|
3801
3802
|
}
|
|
3802
|
-
/**
|
|
3803
|
-
* Gets timestamps for parent commits in a single git call.
|
|
3804
|
-
* @param parentShas Array of parent commit SHAs
|
|
3805
|
-
* @returns Array of parent commits with timestamps, or undefined if unavailable
|
|
3806
|
-
*/
|
|
3807
|
-
async getParentCommitTimestamps(parentShas) {
|
|
3808
|
-
if (parentShas.length === 0) {
|
|
3809
|
-
return void 0;
|
|
3810
|
-
}
|
|
3811
|
-
try {
|
|
3812
|
-
const output = await this.git.raw([
|
|
3813
|
-
"log",
|
|
3814
|
-
"--format=%H %cI",
|
|
3815
|
-
"--no-walk",
|
|
3816
|
-
...parentShas
|
|
3817
|
-
]);
|
|
3818
|
-
const parentCommits = output.trim().split("\n").filter(Boolean).map((line) => {
|
|
3819
|
-
const [sha, ts] = line.split(" ");
|
|
3820
|
-
return { sha: sha ?? "", timestamp: new Date(ts ?? "") };
|
|
3821
|
-
}).filter((p) => p.sha !== "");
|
|
3822
|
-
return parentCommits.length > 0 ? parentCommits : void 0;
|
|
3823
|
-
} catch {
|
|
3824
|
-
this.log("[GitService] Could not get parent commit timestamps", "debug", {
|
|
3825
|
-
parentShas
|
|
3826
|
-
});
|
|
3827
|
-
return void 0;
|
|
3828
|
-
}
|
|
3829
|
-
}
|
|
3830
3803
|
/**
|
|
3831
3804
|
* Gets local commit data including diff, timestamp, and parent commits.
|
|
3832
3805
|
* Used by Tracy extension to send commit data directly without requiring SCM token.
|
|
@@ -3871,18 +3844,14 @@ ${rootContent}`;
|
|
|
3871
3844
|
}
|
|
3872
3845
|
const timestampStr = metadataLines[0];
|
|
3873
3846
|
const timestamp = new Date(timestampStr);
|
|
3874
|
-
const parentShas = (metadataLines[1] ?? "").trim().split(/\s+/).filter(Boolean);
|
|
3875
|
-
const parentCommits = await this.getParentCommitTimestamps(parentShas);
|
|
3876
3847
|
this.log("[GitService] Local commit data retrieved", "debug", {
|
|
3877
3848
|
commitSha,
|
|
3878
3849
|
diffSizeBytes,
|
|
3879
|
-
timestamp: timestamp.toISOString()
|
|
3880
|
-
parentCommitCount: parentCommits?.length ?? 0
|
|
3850
|
+
timestamp: timestamp.toISOString()
|
|
3881
3851
|
});
|
|
3882
3852
|
return {
|
|
3883
3853
|
diff,
|
|
3884
|
-
timestamp
|
|
3885
|
-
parentCommits
|
|
3854
|
+
timestamp
|
|
3886
3855
|
};
|
|
3887
3856
|
} catch (error) {
|
|
3888
3857
|
const errorMessage = `Failed to get local commit data: ${error.message}`;
|
|
@@ -6107,6 +6076,34 @@ var GetReferenceResultZ = z13.object({
|
|
|
6107
6076
|
type: z13.nativeEnum(ReferenceType)
|
|
6108
6077
|
});
|
|
6109
6078
|
|
|
6079
|
+
// src/features/analysis/scm/utils/diffUtils.ts
|
|
6080
|
+
import parseDiff from "parse-diff";
|
|
6081
|
+
function parseAddedLinesByFile(diff) {
|
|
6082
|
+
const result = /* @__PURE__ */ new Map();
|
|
6083
|
+
const parsedDiff = parseDiff(diff);
|
|
6084
|
+
for (const file of parsedDiff) {
|
|
6085
|
+
if (!file.to || file.to === "/dev/null") {
|
|
6086
|
+
continue;
|
|
6087
|
+
}
|
|
6088
|
+
const filePath = file.to;
|
|
6089
|
+
const addedLines = [];
|
|
6090
|
+
if (file.chunks) {
|
|
6091
|
+
for (const chunk of file.chunks) {
|
|
6092
|
+
for (const change of chunk.changes) {
|
|
6093
|
+
if (change.type === "add") {
|
|
6094
|
+
const addChange = change;
|
|
6095
|
+
addedLines.push(addChange.ln);
|
|
6096
|
+
}
|
|
6097
|
+
}
|
|
6098
|
+
}
|
|
6099
|
+
}
|
|
6100
|
+
if (addedLines.length > 0) {
|
|
6101
|
+
result.set(filePath, addedLines);
|
|
6102
|
+
}
|
|
6103
|
+
}
|
|
6104
|
+
return result;
|
|
6105
|
+
}
|
|
6106
|
+
|
|
6110
6107
|
// src/features/analysis/scm/utils/scm.ts
|
|
6111
6108
|
var safeBody = (body, maxBodyLength) => {
|
|
6112
6109
|
const truncationNotice = "\n\n... Message was cut here because it is too long";
|
|
@@ -6199,6 +6196,36 @@ function getCommitIssueUrl(params) {
|
|
|
6199
6196
|
analysisId
|
|
6200
6197
|
})}/commit?${searchParams.toString()}`;
|
|
6201
6198
|
}
|
|
6199
|
+
function extractLinearTicketsFromBody(body, seen) {
|
|
6200
|
+
const tickets = [];
|
|
6201
|
+
const htmlPattern = /<a href="(https:\/\/linear\.app\/[^"]+)">([A-Z]+-\d+)<\/a>/g;
|
|
6202
|
+
let match;
|
|
6203
|
+
while ((match = htmlPattern.exec(body)) !== null) {
|
|
6204
|
+
const ticket = parseLinearTicket(match[1], match[2]);
|
|
6205
|
+
if (ticket && !seen.has(`${ticket.name}|${ticket.url}`)) {
|
|
6206
|
+
seen.add(`${ticket.name}|${ticket.url}`);
|
|
6207
|
+
tickets.push(ticket);
|
|
6208
|
+
}
|
|
6209
|
+
}
|
|
6210
|
+
const markdownPattern = /\[([A-Z]+-\d+)\]\((https:\/\/linear\.app\/[^)]+)\)/g;
|
|
6211
|
+
while ((match = markdownPattern.exec(body)) !== null) {
|
|
6212
|
+
const ticket = parseLinearTicket(match[2], match[1]);
|
|
6213
|
+
if (ticket && !seen.has(`${ticket.name}|${ticket.url}`)) {
|
|
6214
|
+
seen.add(`${ticket.name}|${ticket.url}`);
|
|
6215
|
+
tickets.push(ticket);
|
|
6216
|
+
}
|
|
6217
|
+
}
|
|
6218
|
+
return tickets;
|
|
6219
|
+
}
|
|
6220
|
+
function parseLinearTicket(url, name) {
|
|
6221
|
+
if (!name || !url) {
|
|
6222
|
+
return null;
|
|
6223
|
+
}
|
|
6224
|
+
const urlParts = url.split("/");
|
|
6225
|
+
const titleSlug = urlParts[urlParts.length - 1] || "";
|
|
6226
|
+
const title = titleSlug.replace(/-/g, " ");
|
|
6227
|
+
return { name, title, url };
|
|
6228
|
+
}
|
|
6202
6229
|
var userNamePattern = /^(https?:\/\/)([^@]+@)?([^/]+\/.+)$/;
|
|
6203
6230
|
var sshPattern = /^git@([\w.-]+):([\w./-]+)$/;
|
|
6204
6231
|
function normalizeUrl(repoUrl) {
|
|
@@ -6892,9 +6919,6 @@ async function getAdoSdk(params) {
|
|
|
6892
6919
|
return commitRes.value;
|
|
6893
6920
|
}
|
|
6894
6921
|
throw new RefNotFoundError(`ref: ${ref} does not exist`);
|
|
6895
|
-
},
|
|
6896
|
-
getAdoBlameRanges() {
|
|
6897
|
-
return [];
|
|
6898
6922
|
}
|
|
6899
6923
|
};
|
|
6900
6924
|
}
|
|
@@ -7014,7 +7038,6 @@ var SCMLib = class {
|
|
|
7014
7038
|
* IMPORTANT: Sort order must remain consistent across paginated requests
|
|
7015
7039
|
* for cursor-based pagination to work correctly.
|
|
7016
7040
|
*
|
|
7017
|
-
* Default implementation uses getSubmitRequests and applies filters/sorting in-memory.
|
|
7018
7041
|
* Override in subclasses for provider-specific optimizations (e.g., GitHub Search API).
|
|
7019
7042
|
*
|
|
7020
7043
|
* @param params - Search parameters including filters, sort, and pagination
|
|
@@ -7096,6 +7119,14 @@ var SCMLib = class {
|
|
|
7096
7119
|
static async getIsValidBranchName(branchName) {
|
|
7097
7120
|
return isValidBranchName(branchName);
|
|
7098
7121
|
}
|
|
7122
|
+
/**
|
|
7123
|
+
* Extract Linear ticket links from PR/MR comments.
|
|
7124
|
+
* Default implementation returns empty array - subclasses can override.
|
|
7125
|
+
* Public so it can be reused by backend services.
|
|
7126
|
+
*/
|
|
7127
|
+
extractLinearTicketsFromComments(_comments) {
|
|
7128
|
+
return [];
|
|
7129
|
+
}
|
|
7099
7130
|
_validateAccessTokenAndUrl() {
|
|
7100
7131
|
this._validateAccessToken();
|
|
7101
7132
|
this._validateUrl();
|
|
@@ -7248,10 +7279,6 @@ var AdoSCMLib = class extends SCMLib {
|
|
|
7248
7279
|
throw new Error(`unknown state ${state}`);
|
|
7249
7280
|
}
|
|
7250
7281
|
}
|
|
7251
|
-
async getRepoBlameRanges(_ref, _path) {
|
|
7252
|
-
const adoSdk = await this.getAdoSdk();
|
|
7253
|
-
return await adoSdk.getAdoBlameRanges();
|
|
7254
|
-
}
|
|
7255
7282
|
async getReferenceData(ref) {
|
|
7256
7283
|
this._validateUrl();
|
|
7257
7284
|
const adoSdk = await this.getAdoSdk();
|
|
@@ -7310,9 +7337,6 @@ var AdoSCMLib = class extends SCMLib {
|
|
|
7310
7337
|
async getSubmitRequestDiff(_submitRequestId) {
|
|
7311
7338
|
throw new Error("getSubmitRequestDiff not implemented for ADO");
|
|
7312
7339
|
}
|
|
7313
|
-
async getSubmitRequests(_repoUrl) {
|
|
7314
|
-
throw new Error("getSubmitRequests not implemented for ADO");
|
|
7315
|
-
}
|
|
7316
7340
|
async searchSubmitRequests(_params) {
|
|
7317
7341
|
throw new Error("searchSubmitRequests not implemented for ADO");
|
|
7318
7342
|
}
|
|
@@ -7324,6 +7348,12 @@ var AdoSCMLib = class extends SCMLib {
|
|
|
7324
7348
|
async getPullRequestMetrics(_prNumber) {
|
|
7325
7349
|
throw new Error("getPullRequestMetrics not implemented for ADO");
|
|
7326
7350
|
}
|
|
7351
|
+
async getRecentCommits(_since) {
|
|
7352
|
+
throw new Error("getRecentCommits not implemented for ADO");
|
|
7353
|
+
}
|
|
7354
|
+
async getRateLimitStatus() {
|
|
7355
|
+
return null;
|
|
7356
|
+
}
|
|
7327
7357
|
};
|
|
7328
7358
|
|
|
7329
7359
|
// src/features/analysis/scm/bitbucket/bitbucket.ts
|
|
@@ -7841,9 +7871,6 @@ var BitbucketSCMLib = class extends SCMLib {
|
|
|
7841
7871
|
throw new Error(`unknown state ${pullRequestRes.state} `);
|
|
7842
7872
|
}
|
|
7843
7873
|
}
|
|
7844
|
-
async getRepoBlameRanges(_ref, _path) {
|
|
7845
|
-
return [];
|
|
7846
|
-
}
|
|
7847
7874
|
async getReferenceData(ref) {
|
|
7848
7875
|
this._validateUrl();
|
|
7849
7876
|
return this.bitbucketSdk.getReferenceData({ url: this.url, ref });
|
|
@@ -7892,9 +7919,6 @@ var BitbucketSCMLib = class extends SCMLib {
|
|
|
7892
7919
|
async getSubmitRequestDiff(_submitRequestId) {
|
|
7893
7920
|
throw new Error("getSubmitRequestDiff not implemented for Bitbucket");
|
|
7894
7921
|
}
|
|
7895
|
-
async getSubmitRequests(_repoUrl) {
|
|
7896
|
-
throw new Error("getSubmitRequests not implemented for Bitbucket");
|
|
7897
|
-
}
|
|
7898
7922
|
async searchSubmitRequests(_params) {
|
|
7899
7923
|
throw new Error("searchSubmitRequests not implemented for Bitbucket");
|
|
7900
7924
|
}
|
|
@@ -7906,6 +7930,12 @@ var BitbucketSCMLib = class extends SCMLib {
|
|
|
7906
7930
|
async getPullRequestMetrics(_prNumber) {
|
|
7907
7931
|
throw new Error("getPullRequestMetrics not implemented for Bitbucket");
|
|
7908
7932
|
}
|
|
7933
|
+
async getRecentCommits(_since) {
|
|
7934
|
+
throw new Error("getRecentCommits not implemented for Bitbucket");
|
|
7935
|
+
}
|
|
7936
|
+
async getRateLimitStatus() {
|
|
7937
|
+
return null;
|
|
7938
|
+
}
|
|
7909
7939
|
};
|
|
7910
7940
|
|
|
7911
7941
|
// src/features/analysis/scm/constants.ts
|
|
@@ -7918,7 +7948,7 @@ init_env();
|
|
|
7918
7948
|
|
|
7919
7949
|
// src/features/analysis/scm/github/GithubSCMLib.ts
|
|
7920
7950
|
init_env();
|
|
7921
|
-
import
|
|
7951
|
+
import pLimit from "p-limit";
|
|
7922
7952
|
import { z as z21 } from "zod";
|
|
7923
7953
|
init_client_generates();
|
|
7924
7954
|
|
|
@@ -7937,59 +7967,6 @@ function parseCursorSafe(cursor, defaultValue = 0, maxValue = MAX_CURSOR_VALUE)
|
|
|
7937
7967
|
|
|
7938
7968
|
// src/features/analysis/scm/github/github.ts
|
|
7939
7969
|
import { RequestError } from "@octokit/request-error";
|
|
7940
|
-
import pLimit from "p-limit";
|
|
7941
|
-
|
|
7942
|
-
// src/utils/contextLogger.ts
|
|
7943
|
-
import debugModule from "debug";
|
|
7944
|
-
var debug3 = debugModule("mobb:shared");
|
|
7945
|
-
var _contextLogger = null;
|
|
7946
|
-
var createContextLogger = async () => {
|
|
7947
|
-
if (_contextLogger) return _contextLogger;
|
|
7948
|
-
try {
|
|
7949
|
-
let logger2;
|
|
7950
|
-
try {
|
|
7951
|
-
let module;
|
|
7952
|
-
try {
|
|
7953
|
-
const buildPath = "../../../../../tscommon/backend/build/src/utils/logger";
|
|
7954
|
-
module = await import(buildPath);
|
|
7955
|
-
} catch (e) {
|
|
7956
|
-
const sourcePath = "../../../../../tscommon/backend/src/utils/logger";
|
|
7957
|
-
module = await import(sourcePath);
|
|
7958
|
-
}
|
|
7959
|
-
logger2 = module.logger;
|
|
7960
|
-
} catch {
|
|
7961
|
-
}
|
|
7962
|
-
if (logger2) {
|
|
7963
|
-
_contextLogger = {
|
|
7964
|
-
info: (message, data) => data ? logger2.info(data, message) : logger2.info(message),
|
|
7965
|
-
debug: (message, data) => data ? logger2.debug(data, message) : logger2.debug(message),
|
|
7966
|
-
error: (message, data) => data ? logger2.error(data, message) : logger2.error(message)
|
|
7967
|
-
};
|
|
7968
|
-
return _contextLogger;
|
|
7969
|
-
}
|
|
7970
|
-
} catch {
|
|
7971
|
-
}
|
|
7972
|
-
_contextLogger = {
|
|
7973
|
-
info: (message, data) => debug3(message, data),
|
|
7974
|
-
debug: (message, data) => debug3(message, data),
|
|
7975
|
-
error: (message, data) => debug3(message, data)
|
|
7976
|
-
};
|
|
7977
|
-
return _contextLogger;
|
|
7978
|
-
};
|
|
7979
|
-
var contextLogger = {
|
|
7980
|
-
info: async (message, data) => {
|
|
7981
|
-
const logger2 = await createContextLogger();
|
|
7982
|
-
return logger2.info(message, data);
|
|
7983
|
-
},
|
|
7984
|
-
debug: async (message, data) => {
|
|
7985
|
-
const logger2 = await createContextLogger();
|
|
7986
|
-
return logger2.debug(message, data);
|
|
7987
|
-
},
|
|
7988
|
-
error: async (message, data) => {
|
|
7989
|
-
const logger2 = await createContextLogger();
|
|
7990
|
-
return logger2.error(message, data);
|
|
7991
|
-
}
|
|
7992
|
-
};
|
|
7993
7970
|
|
|
7994
7971
|
// src/features/analysis/scm/github/consts.ts
|
|
7995
7972
|
var POST_COMMENT_PATH = "POST /repos/{owner}/{repo}/pulls/{pull_number}/comments";
|
|
@@ -8007,35 +7984,6 @@ var GET_A_REPOSITORY_PUBLIC_KEY = "GET /repos/{owner}/{repo}/actions/secrets/pub
|
|
|
8007
7984
|
var GET_USER = "GET /user";
|
|
8008
7985
|
var GET_USER_REPOS = "GET /user/repos";
|
|
8009
7986
|
var GET_REPO_BRANCHES = "GET /repos/{owner}/{repo}/branches";
|
|
8010
|
-
var GET_BLAME_DOCUMENT = `
|
|
8011
|
-
query GetBlame(
|
|
8012
|
-
$owner: String!
|
|
8013
|
-
$repo: String!
|
|
8014
|
-
$ref: String!
|
|
8015
|
-
$path: String!
|
|
8016
|
-
) {
|
|
8017
|
-
repository(name: $repo, owner: $owner) {
|
|
8018
|
-
# branch name
|
|
8019
|
-
object(expression: $ref) {
|
|
8020
|
-
# cast Target to a Commit
|
|
8021
|
-
... on Commit {
|
|
8022
|
-
# full repo-relative path to blame file
|
|
8023
|
-
blame(path: $path) {
|
|
8024
|
-
ranges {
|
|
8025
|
-
commit {
|
|
8026
|
-
oid
|
|
8027
|
-
}
|
|
8028
|
-
startingLine
|
|
8029
|
-
endingLine
|
|
8030
|
-
age
|
|
8031
|
-
}
|
|
8032
|
-
}
|
|
8033
|
-
}
|
|
8034
|
-
|
|
8035
|
-
}
|
|
8036
|
-
}
|
|
8037
|
-
}
|
|
8038
|
-
`;
|
|
8039
7987
|
var GITHUB_GRAPHQL_FRAGMENTS = {
|
|
8040
7988
|
/**
|
|
8041
7989
|
* Fragment for fetching PR additions/deletions.
|
|
@@ -8059,30 +8007,6 @@ var GITHUB_GRAPHQL_FRAGMENTS = {
|
|
|
8059
8007
|
body
|
|
8060
8008
|
}
|
|
8061
8009
|
}
|
|
8062
|
-
`,
|
|
8063
|
-
/**
|
|
8064
|
-
* Fragment for fetching blame data.
|
|
8065
|
-
* Use with object(expression: $ref) on Commit type.
|
|
8066
|
-
* Note: $path placeholder must be replaced with actual file path.
|
|
8067
|
-
*/
|
|
8068
|
-
BLAME_RANGES: `
|
|
8069
|
-
blame(path: "$path") {
|
|
8070
|
-
ranges {
|
|
8071
|
-
startingLine
|
|
8072
|
-
endingLine
|
|
8073
|
-
commit {
|
|
8074
|
-
oid
|
|
8075
|
-
}
|
|
8076
|
-
}
|
|
8077
|
-
}
|
|
8078
|
-
`,
|
|
8079
|
-
/**
|
|
8080
|
-
* Fragment for fetching commit timestamp.
|
|
8081
|
-
* Use with object(oid: $sha) on Commit type.
|
|
8082
|
-
*/
|
|
8083
|
-
COMMIT_TIMESTAMP: `
|
|
8084
|
-
oid
|
|
8085
|
-
committedDate
|
|
8086
8010
|
`
|
|
8087
8011
|
};
|
|
8088
8012
|
var GET_PR_METRICS_QUERY = `
|
|
@@ -8294,112 +8218,6 @@ async function githubValidateParams(url, accessToken) {
|
|
|
8294
8218
|
|
|
8295
8219
|
// src/features/analysis/scm/github/github.ts
|
|
8296
8220
|
var MAX_GH_PR_BODY_LENGTH = 65536;
|
|
8297
|
-
var BLAME_LARGE_FILE_THRESHOLD_BYTES = 1e6;
|
|
8298
|
-
var BLAME_THRESHOLD_REDUCTION_BYTES = 1e5;
|
|
8299
|
-
var BLAME_MIN_THRESHOLD_BYTES = 1e5;
|
|
8300
|
-
var GRAPHQL_INPUT_PATTERNS = {
|
|
8301
|
-
// File paths: most printable ASCII chars, unicode letters/numbers
|
|
8302
|
-
// Allows: letters, numbers, spaces, common punctuation, path separators
|
|
8303
|
-
// Disallows: control characters, null bytes
|
|
8304
|
-
path: /^[\p{L}\p{N}\p{Zs}\-._/@+#~%()[\]{}=!,;'&]+$/u,
|
|
8305
|
-
// Git refs: branch/tag names follow git-check-ref-format rules
|
|
8306
|
-
// Allows: letters, numbers, slashes, dots, hyphens, underscores
|
|
8307
|
-
// Can also be "ref:path" format for expressions
|
|
8308
|
-
ref: /^[\p{L}\p{N}\-._/:@]+$/u,
|
|
8309
|
-
// Git SHAs: strictly hexadecimal (short or full)
|
|
8310
|
-
sha: /^[0-9a-fA-F]+$/
|
|
8311
|
-
};
|
|
8312
|
-
function validateGraphQLInput(value, type2) {
|
|
8313
|
-
const pattern = GRAPHQL_INPUT_PATTERNS[type2];
|
|
8314
|
-
if (!pattern.test(value)) {
|
|
8315
|
-
void contextLogger.info(
|
|
8316
|
-
"[GraphQL] Input contains unexpected characters, proceeding with escaping",
|
|
8317
|
-
{
|
|
8318
|
-
type: type2,
|
|
8319
|
-
valueLength: value.length,
|
|
8320
|
-
// Log first 100 chars to help debug without exposing full value
|
|
8321
|
-
valueSample: value.slice(0, 100)
|
|
8322
|
-
}
|
|
8323
|
-
);
|
|
8324
|
-
return false;
|
|
8325
|
-
}
|
|
8326
|
-
return true;
|
|
8327
|
-
}
|
|
8328
|
-
function escapeGraphQLString(value) {
|
|
8329
|
-
return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/\t/g, "\\t").replace(/\f/g, "\\f").replace(/[\b]/g, "\\b");
|
|
8330
|
-
}
|
|
8331
|
-
function safeGraphQLString(value, type2) {
|
|
8332
|
-
validateGraphQLInput(value, type2);
|
|
8333
|
-
return escapeGraphQLString(value);
|
|
8334
|
-
}
|
|
8335
|
-
function extractBlameRanges(data) {
|
|
8336
|
-
const fileData = data;
|
|
8337
|
-
if (fileData.blame?.ranges) {
|
|
8338
|
-
return fileData.blame.ranges.map((range) => ({
|
|
8339
|
-
startingLine: range.startingLine,
|
|
8340
|
-
endingLine: range.endingLine,
|
|
8341
|
-
commitSha: range.commit.oid
|
|
8342
|
-
}));
|
|
8343
|
-
}
|
|
8344
|
-
return void 0;
|
|
8345
|
-
}
|
|
8346
|
-
function buildBlameFragment(ref) {
|
|
8347
|
-
const escapedRef = safeGraphQLString(ref, "ref");
|
|
8348
|
-
return (path25, index) => {
|
|
8349
|
-
const escapedPath = safeGraphQLString(path25, "path");
|
|
8350
|
-
return `
|
|
8351
|
-
file${index}: object(expression: "${escapedRef}") {
|
|
8352
|
-
... on Commit {
|
|
8353
|
-
${GITHUB_GRAPHQL_FRAGMENTS.BLAME_RANGES.replace("$path", escapedPath)}
|
|
8354
|
-
}
|
|
8355
|
-
}`;
|
|
8356
|
-
};
|
|
8357
|
-
}
|
|
8358
|
-
function createBatchesByTotalSize(files, threshold) {
|
|
8359
|
-
const batches = [];
|
|
8360
|
-
let currentBatch = [];
|
|
8361
|
-
let currentBatchSize = 0;
|
|
8362
|
-
for (const file of files) {
|
|
8363
|
-
if (currentBatchSize + file.size > threshold && currentBatch.length > 0) {
|
|
8364
|
-
batches.push(currentBatch);
|
|
8365
|
-
currentBatch = [];
|
|
8366
|
-
currentBatchSize = 0;
|
|
8367
|
-
}
|
|
8368
|
-
currentBatch.push(file);
|
|
8369
|
-
currentBatchSize += file.size;
|
|
8370
|
-
}
|
|
8371
|
-
if (currentBatch.length > 0) {
|
|
8372
|
-
batches.push(currentBatch);
|
|
8373
|
-
}
|
|
8374
|
-
return batches;
|
|
8375
|
-
}
|
|
8376
|
-
async function fetchBlameForBatch(octokit, owner, repo, ref, files) {
|
|
8377
|
-
if (files.length === 0) {
|
|
8378
|
-
return /* @__PURE__ */ new Map();
|
|
8379
|
-
}
|
|
8380
|
-
return executeBatchGraphQL(octokit, owner, repo, {
|
|
8381
|
-
items: files.map((f) => f.path),
|
|
8382
|
-
aliasPrefix: "file",
|
|
8383
|
-
buildFragment: buildBlameFragment(ref),
|
|
8384
|
-
extractResult: extractBlameRanges
|
|
8385
|
-
});
|
|
8386
|
-
}
|
|
8387
|
-
async function processBlameAttempt(params) {
|
|
8388
|
-
const { octokit, owner, repo, ref, batches, concurrency } = params;
|
|
8389
|
-
const result = /* @__PURE__ */ new Map();
|
|
8390
|
-
const limit = pLimit(concurrency);
|
|
8391
|
-
const batchResults = await Promise.all(
|
|
8392
|
-
batches.map(
|
|
8393
|
-
(batch) => limit(() => fetchBlameForBatch(octokit, owner, repo, ref, batch))
|
|
8394
|
-
)
|
|
8395
|
-
);
|
|
8396
|
-
for (const batchResult of batchResults) {
|
|
8397
|
-
for (const [path25, blameData] of batchResult) {
|
|
8398
|
-
result.set(path25, blameData);
|
|
8399
|
-
}
|
|
8400
|
-
}
|
|
8401
|
-
return result;
|
|
8402
|
-
}
|
|
8403
8221
|
async function executeBatchGraphQL(octokit, owner, repo, config2) {
|
|
8404
8222
|
const { items, aliasPrefix, buildFragment, extractResult } = config2;
|
|
8405
8223
|
if (items.length === 0) {
|
|
@@ -8714,29 +8532,6 @@ function getGithubSdk(params = {}) {
|
|
|
8714
8532
|
sha: res.data.sha
|
|
8715
8533
|
};
|
|
8716
8534
|
},
|
|
8717
|
-
async getGithubBlameRanges(params2) {
|
|
8718
|
-
const { ref, gitHubUrl, path: path25 } = params2;
|
|
8719
|
-
const { owner, repo } = parseGithubOwnerAndRepo(gitHubUrl);
|
|
8720
|
-
const res = await octokit.graphql(
|
|
8721
|
-
GET_BLAME_DOCUMENT,
|
|
8722
|
-
{
|
|
8723
|
-
owner,
|
|
8724
|
-
repo,
|
|
8725
|
-
path: path25,
|
|
8726
|
-
ref
|
|
8727
|
-
}
|
|
8728
|
-
);
|
|
8729
|
-
if (!res?.repository?.object?.blame?.ranges) {
|
|
8730
|
-
return [];
|
|
8731
|
-
}
|
|
8732
|
-
return res.repository.object.blame.ranges.map(
|
|
8733
|
-
(range) => ({
|
|
8734
|
-
startingLine: range.startingLine,
|
|
8735
|
-
endingLine: range.endingLine,
|
|
8736
|
-
commitSha: range.commit.oid
|
|
8737
|
-
})
|
|
8738
|
-
);
|
|
8739
|
-
},
|
|
8740
8535
|
/**
|
|
8741
8536
|
* Fetches commits for multiple PRs in a single GraphQL request.
|
|
8742
8537
|
* This is much more efficient than making N separate REST API calls.
|
|
@@ -9020,301 +8815,75 @@ function getGithubSdk(params = {}) {
|
|
|
9020
8815
|
}
|
|
9021
8816
|
});
|
|
9022
8817
|
},
|
|
9023
|
-
|
|
9024
|
-
|
|
9025
|
-
|
|
9026
|
-
|
|
9027
|
-
|
|
9028
|
-
|
|
9029
|
-
|
|
9030
|
-
aliasPrefix: "blob",
|
|
9031
|
-
buildFragment: (sha, index) => {
|
|
9032
|
-
const escapedSha = safeGraphQLString(sha, "sha");
|
|
9033
|
-
return `
|
|
9034
|
-
blob${index}: object(oid: "${escapedSha}") {
|
|
9035
|
-
... on Blob {
|
|
9036
|
-
byteSize
|
|
9037
|
-
}
|
|
9038
|
-
}`;
|
|
9039
|
-
},
|
|
9040
|
-
extractResult: (data) => {
|
|
9041
|
-
const blobData = data;
|
|
9042
|
-
if (blobData.byteSize !== void 0) {
|
|
9043
|
-
return blobData.byteSize;
|
|
9044
|
-
}
|
|
9045
|
-
return void 0;
|
|
8818
|
+
async getPRMetricsGraphQL(params2) {
|
|
8819
|
+
const res = await octokit.graphql(
|
|
8820
|
+
GET_PR_METRICS_QUERY,
|
|
8821
|
+
{
|
|
8822
|
+
owner: params2.owner,
|
|
8823
|
+
repo: params2.repo,
|
|
8824
|
+
prNumber: params2.prNumber
|
|
9046
8825
|
}
|
|
9047
|
-
|
|
8826
|
+
);
|
|
8827
|
+
return res;
|
|
9048
8828
|
},
|
|
9049
8829
|
/**
|
|
9050
|
-
*
|
|
9051
|
-
*
|
|
9052
|
-
*
|
|
9053
|
-
* Optimized to handle large files with retry logic:
|
|
9054
|
-
* - Files above threshold are processed individually with rate limiting
|
|
9055
|
-
* - On failure, retries with reduced threshold (-100KB) and concurrency (-1)
|
|
9056
|
-
* - Continues until success or threshold < 100KB
|
|
9057
|
-
*
|
|
9058
|
-
* @param params.files - Array of files with path and blobSha for size lookup
|
|
9059
|
-
* @param params.concurrency - Max concurrent requests for large files (default: 2)
|
|
8830
|
+
* Search PRs using GitHub's Search API with sorting
|
|
8831
|
+
* https://docs.github.com/en/rest/search/search?apiVersion=2022-11-28#search-issues-and-pull-requests
|
|
9060
8832
|
*/
|
|
9061
|
-
async
|
|
8833
|
+
async searchPullRequests(params2) {
|
|
9062
8834
|
const {
|
|
9063
8835
|
owner,
|
|
9064
8836
|
repo,
|
|
9065
|
-
|
|
9066
|
-
|
|
9067
|
-
|
|
8837
|
+
updatedAfter,
|
|
8838
|
+
state = "all",
|
|
8839
|
+
sort = { field: "updated", order: "desc" },
|
|
8840
|
+
perPage = 10,
|
|
8841
|
+
page = 1
|
|
9068
8842
|
} = params2;
|
|
9069
|
-
|
|
9070
|
-
|
|
8843
|
+
let query = `repo:${owner}/${repo} is:pr`;
|
|
8844
|
+
if (updatedAfter) {
|
|
8845
|
+
const dateStr = updatedAfter.toISOString().split("T")[0];
|
|
8846
|
+
query += ` updated:>=${dateStr}`;
|
|
9071
8847
|
}
|
|
9072
|
-
|
|
9073
|
-
|
|
9074
|
-
|
|
9075
|
-
|
|
9076
|
-
|
|
9077
|
-
|
|
9078
|
-
|
|
8848
|
+
if (state !== "all") {
|
|
8849
|
+
query += ` is:${state}`;
|
|
8850
|
+
}
|
|
8851
|
+
const githubSortField = sort.field === "updated" || sort.field === "created" ? sort.field : "comments";
|
|
8852
|
+
const response = await octokit.rest.search.issuesAndPullRequests({
|
|
8853
|
+
q: query,
|
|
8854
|
+
sort: githubSortField,
|
|
8855
|
+
order: sort.order,
|
|
8856
|
+
per_page: perPage,
|
|
8857
|
+
page
|
|
9079
8858
|
});
|
|
8859
|
+
return {
|
|
8860
|
+
items: response.data.items,
|
|
8861
|
+
totalCount: response.data.total_count,
|
|
8862
|
+
hasMore: page * perPage < response.data.total_count
|
|
8863
|
+
};
|
|
9080
8864
|
},
|
|
9081
8865
|
/**
|
|
9082
|
-
*
|
|
9083
|
-
|
|
9084
|
-
async fetchFilesWithSizes(owner, repo, files) {
|
|
9085
|
-
const blobShas = files.map((f) => f.blobSha);
|
|
9086
|
-
const blobSizes = await this.getBlobSizesBatch({ owner, repo, blobShas });
|
|
9087
|
-
return files.map((file) => ({
|
|
9088
|
-
...file,
|
|
9089
|
-
size: blobSizes.get(file.blobSha) ?? 0
|
|
9090
|
-
}));
|
|
9091
|
-
},
|
|
9092
|
-
/**
|
|
9093
|
-
* Executes blame fetching with retry logic on failure.
|
|
9094
|
-
* Reduces threshold and concurrency on each retry attempt.
|
|
8866
|
+
* Search repositories using GitHub's Search API.
|
|
8867
|
+
* Docs: https://docs.github.com/en/rest/search/search?apiVersion=2022-11-28#search-repositories
|
|
9095
8868
|
*/
|
|
9096
|
-
async
|
|
9097
|
-
const {
|
|
9098
|
-
|
|
9099
|
-
|
|
9100
|
-
|
|
9101
|
-
|
|
9102
|
-
|
|
9103
|
-
|
|
9104
|
-
|
|
9105
|
-
attempt,
|
|
9106
|
-
threshold,
|
|
9107
|
-
concurrency,
|
|
9108
|
-
filesWithSizes.length,
|
|
9109
|
-
batches.length,
|
|
9110
|
-
owner,
|
|
9111
|
-
repo,
|
|
9112
|
-
ref
|
|
9113
|
-
);
|
|
9114
|
-
try {
|
|
9115
|
-
const result = await processBlameAttempt({
|
|
9116
|
-
octokit,
|
|
9117
|
-
owner,
|
|
9118
|
-
repo,
|
|
9119
|
-
ref,
|
|
9120
|
-
batches,
|
|
9121
|
-
concurrency
|
|
9122
|
-
});
|
|
9123
|
-
this.logBlameAttemptSuccess(attempt, result.size, owner, repo);
|
|
9124
|
-
return result;
|
|
9125
|
-
} catch (error) {
|
|
9126
|
-
lastError = error instanceof Error ? error : new Error(String(error));
|
|
9127
|
-
this.logBlameAttemptFailure(
|
|
9128
|
-
attempt,
|
|
9129
|
-
threshold,
|
|
9130
|
-
concurrency,
|
|
9131
|
-
lastError.message,
|
|
9132
|
-
owner,
|
|
9133
|
-
repo
|
|
9134
|
-
);
|
|
9135
|
-
threshold -= BLAME_THRESHOLD_REDUCTION_BYTES;
|
|
9136
|
-
concurrency = Math.max(1, concurrency - 1);
|
|
9137
|
-
attempt++;
|
|
9138
|
-
}
|
|
8869
|
+
async searchRepositories(params2) {
|
|
8870
|
+
const {
|
|
8871
|
+
org,
|
|
8872
|
+
sort = { field: "updated", order: "desc" },
|
|
8873
|
+
perPage = 10,
|
|
8874
|
+
page = 1
|
|
8875
|
+
} = params2;
|
|
8876
|
+
if (!org) {
|
|
8877
|
+
throw new Error("Organization is required for repository search");
|
|
9139
8878
|
}
|
|
9140
|
-
|
|
9141
|
-
|
|
9142
|
-
|
|
9143
|
-
|
|
9144
|
-
|
|
9145
|
-
|
|
9146
|
-
|
|
9147
|
-
|
|
9148
|
-
/**
|
|
9149
|
-
* Logs the start of a blame batch attempt.
|
|
9150
|
-
*/
|
|
9151
|
-
logBlameAttemptStart(attempt, threshold, concurrency, totalFiles, batchCount, owner, repo, ref) {
|
|
9152
|
-
void contextLogger.debug("[getBlameBatch] Processing attempt", {
|
|
9153
|
-
attempt,
|
|
9154
|
-
threshold,
|
|
9155
|
-
concurrency,
|
|
9156
|
-
totalFiles,
|
|
9157
|
-
batchCount,
|
|
9158
|
-
repo: `${owner}/${repo}`,
|
|
9159
|
-
ref
|
|
9160
|
-
});
|
|
9161
|
-
},
|
|
9162
|
-
/**
|
|
9163
|
-
* Logs a successful blame batch attempt.
|
|
9164
|
-
*/
|
|
9165
|
-
logBlameAttemptSuccess(attempt, filesProcessed, owner, repo) {
|
|
9166
|
-
void contextLogger.debug("[getBlameBatch] Successfully processed batch", {
|
|
9167
|
-
attempt,
|
|
9168
|
-
filesProcessed,
|
|
9169
|
-
repo: `${owner}/${repo}`
|
|
9170
|
-
});
|
|
9171
|
-
},
|
|
9172
|
-
/**
|
|
9173
|
-
* Logs a failed blame batch attempt.
|
|
9174
|
-
*/
|
|
9175
|
-
logBlameAttemptFailure(attempt, threshold, concurrency, errorMessage, owner, repo) {
|
|
9176
|
-
void contextLogger.debug(
|
|
9177
|
-
"[getBlameBatch] Attempt failed, retrying with reduced threshold",
|
|
9178
|
-
{
|
|
9179
|
-
attempt,
|
|
9180
|
-
threshold,
|
|
9181
|
-
concurrency,
|
|
9182
|
-
error: errorMessage,
|
|
9183
|
-
repo: `${owner}/${repo}`
|
|
9184
|
-
}
|
|
9185
|
-
);
|
|
9186
|
-
},
|
|
9187
|
-
/**
|
|
9188
|
-
* Batch fetch blame data for multiple files via GraphQL (legacy interface).
|
|
9189
|
-
* This is a convenience wrapper that accepts file paths without blob SHAs.
|
|
9190
|
-
* Note: This does NOT perform size-based optimization. Use getBlameBatch with
|
|
9191
|
-
* files array including blobSha for optimized large file handling.
|
|
9192
|
-
*/
|
|
9193
|
-
async getBlameBatchByPaths(params2) {
|
|
9194
|
-
const escapedRef = safeGraphQLString(params2.ref, "ref");
|
|
9195
|
-
return executeBatchGraphQL(octokit, params2.owner, params2.repo, {
|
|
9196
|
-
items: params2.filePaths,
|
|
9197
|
-
aliasPrefix: "file",
|
|
9198
|
-
buildFragment: (path25, index) => {
|
|
9199
|
-
const escapedPath = safeGraphQLString(path25, "path");
|
|
9200
|
-
return `
|
|
9201
|
-
file${index}: object(expression: "${escapedRef}") {
|
|
9202
|
-
... on Commit {
|
|
9203
|
-
${GITHUB_GRAPHQL_FRAGMENTS.BLAME_RANGES.replace("$path", escapedPath)}
|
|
9204
|
-
}
|
|
9205
|
-
}`;
|
|
9206
|
-
},
|
|
9207
|
-
extractResult: (data) => {
|
|
9208
|
-
const fileData = data;
|
|
9209
|
-
if (fileData.blame?.ranges) {
|
|
9210
|
-
return fileData.blame.ranges.map((range) => ({
|
|
9211
|
-
startingLine: range.startingLine,
|
|
9212
|
-
endingLine: range.endingLine,
|
|
9213
|
-
commitSha: range.commit.oid
|
|
9214
|
-
}));
|
|
9215
|
-
}
|
|
9216
|
-
return void 0;
|
|
9217
|
-
}
|
|
9218
|
-
});
|
|
9219
|
-
},
|
|
9220
|
-
/**
|
|
9221
|
-
* Batch fetch commit timestamps for multiple commits via GraphQL.
|
|
9222
|
-
* Uses GITHUB_GRAPHQL_FRAGMENTS.COMMIT_TIMESTAMP for the field selection.
|
|
9223
|
-
*/
|
|
9224
|
-
async getCommitsBatch(params2) {
|
|
9225
|
-
return executeBatchGraphQL(octokit, params2.owner, params2.repo, {
|
|
9226
|
-
items: params2.commitShas,
|
|
9227
|
-
aliasPrefix: "commit",
|
|
9228
|
-
buildFragment: (sha, index) => {
|
|
9229
|
-
const escapedSha = safeGraphQLString(sha, "sha");
|
|
9230
|
-
return `
|
|
9231
|
-
commit${index}: object(oid: "${escapedSha}") {
|
|
9232
|
-
... on Commit {
|
|
9233
|
-
${GITHUB_GRAPHQL_FRAGMENTS.COMMIT_TIMESTAMP}
|
|
9234
|
-
}
|
|
9235
|
-
}`;
|
|
9236
|
-
},
|
|
9237
|
-
extractResult: (data) => {
|
|
9238
|
-
const commitData = data;
|
|
9239
|
-
if (commitData.oid && commitData.committedDate) {
|
|
9240
|
-
return {
|
|
9241
|
-
sha: commitData.oid,
|
|
9242
|
-
timestamp: new Date(commitData.committedDate)
|
|
9243
|
-
};
|
|
9244
|
-
}
|
|
9245
|
-
return void 0;
|
|
9246
|
-
}
|
|
9247
|
-
});
|
|
9248
|
-
},
|
|
9249
|
-
async getPRMetricsGraphQL(params2) {
|
|
9250
|
-
const res = await octokit.graphql(
|
|
9251
|
-
GET_PR_METRICS_QUERY,
|
|
9252
|
-
{
|
|
9253
|
-
owner: params2.owner,
|
|
9254
|
-
repo: params2.repo,
|
|
9255
|
-
prNumber: params2.prNumber
|
|
9256
|
-
}
|
|
9257
|
-
);
|
|
9258
|
-
return res;
|
|
9259
|
-
},
|
|
9260
|
-
/**
|
|
9261
|
-
* Search PRs using GitHub's Search API with sorting
|
|
9262
|
-
* https://docs.github.com/en/rest/search/search?apiVersion=2022-11-28#search-issues-and-pull-requests
|
|
9263
|
-
*/
|
|
9264
|
-
async searchPullRequests(params2) {
|
|
9265
|
-
const {
|
|
9266
|
-
owner,
|
|
9267
|
-
repo,
|
|
9268
|
-
updatedAfter,
|
|
9269
|
-
state = "all",
|
|
9270
|
-
sort = { field: "updated", order: "desc" },
|
|
9271
|
-
perPage = 10,
|
|
9272
|
-
page = 1
|
|
9273
|
-
} = params2;
|
|
9274
|
-
let query = `repo:${owner}/${repo} is:pr`;
|
|
9275
|
-
if (updatedAfter) {
|
|
9276
|
-
const dateStr = updatedAfter.toISOString().split("T")[0];
|
|
9277
|
-
query += ` updated:>=${dateStr}`;
|
|
9278
|
-
}
|
|
9279
|
-
if (state !== "all") {
|
|
9280
|
-
query += ` is:${state}`;
|
|
9281
|
-
}
|
|
9282
|
-
const githubSortField = sort.field === "updated" || sort.field === "created" ? sort.field : "comments";
|
|
9283
|
-
const response = await octokit.rest.search.issuesAndPullRequests({
|
|
9284
|
-
q: query,
|
|
9285
|
-
sort: githubSortField,
|
|
9286
|
-
order: sort.order,
|
|
9287
|
-
per_page: perPage,
|
|
9288
|
-
page
|
|
9289
|
-
});
|
|
9290
|
-
return {
|
|
9291
|
-
items: response.data.items,
|
|
9292
|
-
totalCount: response.data.total_count,
|
|
9293
|
-
hasMore: page * perPage < response.data.total_count
|
|
9294
|
-
};
|
|
9295
|
-
},
|
|
9296
|
-
/**
|
|
9297
|
-
* Search repositories using GitHub's Search API.
|
|
9298
|
-
* Docs: https://docs.github.com/en/rest/search/search?apiVersion=2022-11-28#search-repositories
|
|
9299
|
-
*/
|
|
9300
|
-
async searchRepositories(params2) {
|
|
9301
|
-
const {
|
|
9302
|
-
org,
|
|
9303
|
-
sort = { field: "updated", order: "desc" },
|
|
9304
|
-
perPage = 10,
|
|
9305
|
-
page = 1
|
|
9306
|
-
} = params2;
|
|
9307
|
-
if (!org) {
|
|
9308
|
-
throw new Error("Organization is required for repository search");
|
|
9309
|
-
}
|
|
9310
|
-
const query = `org:${org}`;
|
|
9311
|
-
const githubSortField = sort.field === "name" ? void 0 : "updated";
|
|
9312
|
-
const response = await octokit.rest.search.repos({
|
|
9313
|
-
q: query,
|
|
9314
|
-
sort: githubSortField,
|
|
9315
|
-
order: sort.order,
|
|
9316
|
-
per_page: perPage,
|
|
9317
|
-
page
|
|
8879
|
+
const query = `org:${org}`;
|
|
8880
|
+
const githubSortField = sort.field === "name" ? void 0 : "updated";
|
|
8881
|
+
const response = await octokit.rest.search.repos({
|
|
8882
|
+
q: query,
|
|
8883
|
+
sort: githubSortField,
|
|
8884
|
+
order: sort.order,
|
|
8885
|
+
per_page: perPage,
|
|
8886
|
+
page
|
|
9318
8887
|
});
|
|
9319
8888
|
return {
|
|
9320
8889
|
items: response.data.items,
|
|
@@ -9450,10 +9019,26 @@ var GithubSCMLib = class _GithubSCMLib extends SCMLib {
|
|
|
9450
9019
|
async getRecentCommits(since) {
|
|
9451
9020
|
this._validateAccessTokenAndUrl();
|
|
9452
9021
|
const { owner, repo } = parseGithubOwnerAndRepo(this.url);
|
|
9453
|
-
|
|
9022
|
+
const result = await this.githubSdk.getRecentCommits({ owner, repo, since });
|
|
9023
|
+
return {
|
|
9024
|
+
data: result.data.map((c) => ({
|
|
9025
|
+
sha: c.sha,
|
|
9026
|
+
commit: {
|
|
9027
|
+
committer: c.commit.committer ? { date: c.commit.committer.date } : void 0,
|
|
9028
|
+
author: c.commit.author ? { email: c.commit.author.email, name: c.commit.author.name } : void 0,
|
|
9029
|
+
message: c.commit.message
|
|
9030
|
+
},
|
|
9031
|
+
parents: c.parents?.map((p) => ({ sha: p.sha }))
|
|
9032
|
+
}))
|
|
9033
|
+
};
|
|
9454
9034
|
}
|
|
9455
9035
|
async getRateLimitStatus() {
|
|
9456
|
-
|
|
9036
|
+
const result = await this.githubSdk.getRateLimitStatus();
|
|
9037
|
+
return {
|
|
9038
|
+
remaining: result.remaining,
|
|
9039
|
+
reset: result.reset,
|
|
9040
|
+
limit: result.limit
|
|
9041
|
+
};
|
|
9457
9042
|
}
|
|
9458
9043
|
get scmLibType() {
|
|
9459
9044
|
return "GITHUB" /* GITHUB */;
|
|
@@ -9511,14 +9096,6 @@ var GithubSCMLib = class _GithubSCMLib extends SCMLib {
|
|
|
9511
9096
|
markdownComment: comment
|
|
9512
9097
|
});
|
|
9513
9098
|
}
|
|
9514
|
-
async getRepoBlameRanges(ref, path25) {
|
|
9515
|
-
this._validateUrl();
|
|
9516
|
-
return await this.githubSdk.getGithubBlameRanges({
|
|
9517
|
-
ref,
|
|
9518
|
-
path: path25,
|
|
9519
|
-
gitHubUrl: this.url
|
|
9520
|
-
});
|
|
9521
|
-
}
|
|
9522
9099
|
async getReferenceData(ref) {
|
|
9523
9100
|
this._validateUrl();
|
|
9524
9101
|
return await this.githubSdk.getGithubReferenceData({
|
|
@@ -9608,37 +9185,6 @@ var GithubSCMLib = class _GithubSCMLib extends SCMLib {
|
|
|
9608
9185
|
commitSha
|
|
9609
9186
|
});
|
|
9610
9187
|
const commitTimestamp = commit.commit.committer?.date ? new Date(commit.commit.committer.date) : new Date(commit.commit.author?.date || Date.now());
|
|
9611
|
-
let parentCommits;
|
|
9612
|
-
if (commit.parents && commit.parents.length > 0) {
|
|
9613
|
-
if (options?.parentCommitTimestamps) {
|
|
9614
|
-
parentCommits = commit.parents.map((p) => options.parentCommitTimestamps.get(p.sha)).filter((p) => p !== void 0);
|
|
9615
|
-
} else {
|
|
9616
|
-
try {
|
|
9617
|
-
parentCommits = await Promise.all(
|
|
9618
|
-
commit.parents.map(async (parent) => {
|
|
9619
|
-
const parentCommit = await this.githubSdk.getCommit({
|
|
9620
|
-
owner,
|
|
9621
|
-
repo,
|
|
9622
|
-
commitSha: parent.sha
|
|
9623
|
-
});
|
|
9624
|
-
const parentTimestamp = parentCommit.data.committer?.date ? new Date(parentCommit.data.committer.date) : new Date(Date.now());
|
|
9625
|
-
return {
|
|
9626
|
-
sha: parent.sha,
|
|
9627
|
-
timestamp: parentTimestamp
|
|
9628
|
-
};
|
|
9629
|
-
})
|
|
9630
|
-
);
|
|
9631
|
-
} catch (error) {
|
|
9632
|
-
console.error("Failed to fetch parent commit timestamps", {
|
|
9633
|
-
error,
|
|
9634
|
-
commitSha,
|
|
9635
|
-
owner,
|
|
9636
|
-
repo
|
|
9637
|
-
});
|
|
9638
|
-
parentCommits = void 0;
|
|
9639
|
-
}
|
|
9640
|
-
}
|
|
9641
|
-
}
|
|
9642
9188
|
let repositoryCreatedAt = options?.repositoryCreatedAt;
|
|
9643
9189
|
if (repositoryCreatedAt === void 0) {
|
|
9644
9190
|
try {
|
|
@@ -9660,7 +9206,6 @@ var GithubSCMLib = class _GithubSCMLib extends SCMLib {
|
|
|
9660
9206
|
authorName: commit.commit.author?.name,
|
|
9661
9207
|
authorEmail: commit.commit.author?.email,
|
|
9662
9208
|
message: commit.commit.message,
|
|
9663
|
-
parentCommits,
|
|
9664
9209
|
repositoryCreatedAt
|
|
9665
9210
|
};
|
|
9666
9211
|
}
|
|
@@ -9668,46 +9213,31 @@ var GithubSCMLib = class _GithubSCMLib extends SCMLib {
|
|
|
9668
9213
|
this._validateAccessTokenAndUrl();
|
|
9669
9214
|
const { owner, repo } = parseGithubOwnerAndRepo(this.url);
|
|
9670
9215
|
const prNumber = Number(submitRequestId);
|
|
9671
|
-
const [prRes, commitsRes,
|
|
9216
|
+
const [prRes, commitsRes, repoData, prDiff] = await Promise.all([
|
|
9672
9217
|
this.githubSdk.getPr({ owner, repo, pull_number: prNumber }),
|
|
9673
9218
|
this.githubSdk.getPrCommits({ owner, repo, pull_number: prNumber }),
|
|
9674
|
-
this.githubSdk.
|
|
9675
|
-
this.
|
|
9219
|
+
this.githubSdk.getRepository({ owner, repo }),
|
|
9220
|
+
this.getPrDiff({ pull_number: prNumber })
|
|
9676
9221
|
]);
|
|
9677
9222
|
const pr = prRes.data;
|
|
9678
9223
|
const repositoryCreatedAt = repoData.data.created_at ? new Date(repoData.data.created_at) : void 0;
|
|
9679
|
-
const
|
|
9680
|
-
for (const commit of commitsRes.data) {
|
|
9681
|
-
if (commit.parents) {
|
|
9682
|
-
for (const parent of commit.parents) {
|
|
9683
|
-
allParentShas.add(parent.sha);
|
|
9684
|
-
}
|
|
9685
|
-
}
|
|
9686
|
-
}
|
|
9687
|
-
const [parentCommitTimestamps, prDiff] = await Promise.all([
|
|
9688
|
-
this.githubSdk.getCommitsBatch({
|
|
9689
|
-
owner,
|
|
9690
|
-
repo,
|
|
9691
|
-
commitShas: Array.from(allParentShas)
|
|
9692
|
-
}),
|
|
9693
|
-
this.getPrDiff({ pull_number: prNumber })
|
|
9694
|
-
]);
|
|
9695
|
-
const limit = pLimit2(GITHUB_API_CONCURRENCY);
|
|
9224
|
+
const limit = pLimit(GITHUB_API_CONCURRENCY);
|
|
9696
9225
|
const commits = await Promise.all(
|
|
9697
9226
|
commitsRes.data.map(
|
|
9698
9227
|
(commit) => limit(
|
|
9699
9228
|
() => this.getCommitDiff(commit.sha, {
|
|
9700
|
-
repositoryCreatedAt
|
|
9701
|
-
parentCommitTimestamps
|
|
9229
|
+
repositoryCreatedAt
|
|
9702
9230
|
})
|
|
9703
9231
|
)
|
|
9704
9232
|
)
|
|
9705
9233
|
);
|
|
9706
|
-
const
|
|
9707
|
-
|
|
9708
|
-
|
|
9709
|
-
|
|
9710
|
-
|
|
9234
|
+
const addedLinesByFile = parseAddedLinesByFile(prDiff);
|
|
9235
|
+
const diffLines = [];
|
|
9236
|
+
for (const [file, lines] of addedLinesByFile) {
|
|
9237
|
+
for (const line of lines) {
|
|
9238
|
+
diffLines.push({ file, line });
|
|
9239
|
+
}
|
|
9240
|
+
}
|
|
9711
9241
|
return {
|
|
9712
9242
|
diff: prDiff,
|
|
9713
9243
|
createdAt: new Date(pr.created_at),
|
|
@@ -9724,47 +9254,6 @@ var GithubSCMLib = class _GithubSCMLib extends SCMLib {
|
|
|
9724
9254
|
diffLines
|
|
9725
9255
|
};
|
|
9726
9256
|
}
|
|
9727
|
-
async getSubmitRequests(repoUrl) {
|
|
9728
|
-
this._validateAccessToken();
|
|
9729
|
-
const { owner, repo } = parseGithubOwnerAndRepo(repoUrl);
|
|
9730
|
-
const pullsRes = await this.githubSdk.getRepoPullRequests({ owner, repo });
|
|
9731
|
-
const prNumbers = pullsRes.data.map((pr) => pr.number);
|
|
9732
|
-
const [additionsDeletionsMap, commentsMap] = await Promise.all([
|
|
9733
|
-
this.githubSdk.getPrAdditionsDeletionsBatch({ owner, repo, prNumbers }),
|
|
9734
|
-
this.githubSdk.getPrCommentsBatch({ owner, repo, prNumbers })
|
|
9735
|
-
]);
|
|
9736
|
-
const submitRequests = pullsRes.data.map((pr) => {
|
|
9737
|
-
let status = "open";
|
|
9738
|
-
if (pr.state === "closed") {
|
|
9739
|
-
status = pr.merged_at ? "merged" : "closed";
|
|
9740
|
-
} else if (pr.draft) {
|
|
9741
|
-
status = "draft";
|
|
9742
|
-
}
|
|
9743
|
-
const changedLinesData = additionsDeletionsMap.get(pr.number);
|
|
9744
|
-
const changedLines = changedLinesData ? {
|
|
9745
|
-
added: changedLinesData.additions,
|
|
9746
|
-
removed: changedLinesData.deletions
|
|
9747
|
-
} : { added: 0, removed: 0 };
|
|
9748
|
-
const comments = commentsMap.get(pr.number) || [];
|
|
9749
|
-
const tickets = _GithubSCMLib.extractLinearTicketsFromComments(comments);
|
|
9750
|
-
return {
|
|
9751
|
-
submitRequestId: String(pr.number),
|
|
9752
|
-
submitRequestNumber: pr.number,
|
|
9753
|
-
title: pr.title,
|
|
9754
|
-
status,
|
|
9755
|
-
sourceBranch: pr.head.ref,
|
|
9756
|
-
targetBranch: pr.base.ref,
|
|
9757
|
-
authorName: pr.user?.name || pr.user?.login,
|
|
9758
|
-
authorEmail: pr.user?.email || void 0,
|
|
9759
|
-
createdAt: new Date(pr.created_at),
|
|
9760
|
-
updatedAt: new Date(pr.updated_at),
|
|
9761
|
-
description: pr.body || void 0,
|
|
9762
|
-
tickets,
|
|
9763
|
-
changedLines
|
|
9764
|
-
};
|
|
9765
|
-
});
|
|
9766
|
-
return submitRequests;
|
|
9767
|
-
}
|
|
9768
9257
|
/**
|
|
9769
9258
|
* Override searchSubmitRequests to use GitHub's Search API for efficient pagination.
|
|
9770
9259
|
* This is much faster than fetching all PRs and filtering in-memory.
|
|
@@ -9983,146 +9472,26 @@ var GithubSCMLib = class _GithubSCMLib extends SCMLib {
|
|
|
9983
9472
|
};
|
|
9984
9473
|
}
|
|
9985
9474
|
/**
|
|
9986
|
-
*
|
|
9987
|
-
*
|
|
9475
|
+
* Extract Linear ticket links from pre-fetched comments (pure function, no API calls)
|
|
9476
|
+
* Instance method that overrides base class - can also be called statically for backwards compatibility.
|
|
9988
9477
|
*/
|
|
9989
|
-
|
|
9990
|
-
|
|
9991
|
-
return null;
|
|
9992
|
-
}
|
|
9993
|
-
const urlParts = url.split("/");
|
|
9994
|
-
const titleSlug = urlParts[urlParts.length - 1] || "";
|
|
9995
|
-
const title = titleSlug.replace(/-/g, " ");
|
|
9996
|
-
return { name, title, url };
|
|
9478
|
+
extractLinearTicketsFromComments(comments) {
|
|
9479
|
+
return _GithubSCMLib._extractLinearTicketsFromCommentsImpl(comments);
|
|
9997
9480
|
}
|
|
9998
9481
|
/**
|
|
9999
|
-
*
|
|
10000
|
-
*
|
|
9482
|
+
* Static implementation for backwards compatibility and reuse.
|
|
9483
|
+
* Called by both the instance method and direct static calls.
|
|
10001
9484
|
*/
|
|
10002
|
-
static
|
|
9485
|
+
static _extractLinearTicketsFromCommentsImpl(comments) {
|
|
10003
9486
|
const tickets = [];
|
|
10004
9487
|
const seen = /* @__PURE__ */ new Set();
|
|
10005
9488
|
for (const comment of comments) {
|
|
10006
9489
|
if (comment.author?.login === "linear[bot]" || comment.author?.type === "Bot") {
|
|
10007
|
-
|
|
10008
|
-
const htmlPattern = /<a href="(https:\/\/linear\.app\/[^"]+)">([A-Z]+-\d+)<\/a>/g;
|
|
10009
|
-
let match;
|
|
10010
|
-
while ((match = htmlPattern.exec(body)) !== null) {
|
|
10011
|
-
const ticket = _GithubSCMLib._parseLinearTicket(match[1], match[2]);
|
|
10012
|
-
if (ticket && !seen.has(`${ticket.name}|${ticket.url}`)) {
|
|
10013
|
-
seen.add(`${ticket.name}|${ticket.url}`);
|
|
10014
|
-
tickets.push(ticket);
|
|
10015
|
-
}
|
|
10016
|
-
}
|
|
10017
|
-
const markdownPattern = /\[([A-Z]+-\d+)\]\((https:\/\/linear\.app\/[^)]+)\)/g;
|
|
10018
|
-
while ((match = markdownPattern.exec(body)) !== null) {
|
|
10019
|
-
const ticket = _GithubSCMLib._parseLinearTicket(match[2], match[1]);
|
|
10020
|
-
if (ticket && !seen.has(`${ticket.name}|${ticket.url}`)) {
|
|
10021
|
-
seen.add(`${ticket.name}|${ticket.url}`);
|
|
10022
|
-
tickets.push(ticket);
|
|
10023
|
-
}
|
|
10024
|
-
}
|
|
9490
|
+
tickets.push(...extractLinearTicketsFromBody(comment.body || "", seen));
|
|
10025
9491
|
}
|
|
10026
9492
|
}
|
|
10027
9493
|
return tickets;
|
|
10028
9494
|
}
|
|
10029
|
-
/**
|
|
10030
|
-
* Optimized helper to parse added line numbers from a unified diff patch
|
|
10031
|
-
* Single-pass parsing for minimal CPU usage
|
|
10032
|
-
*/
|
|
10033
|
-
_parseAddedLinesFromPatch(patch) {
|
|
10034
|
-
const addedLines = [];
|
|
10035
|
-
const lines = patch.split("\n");
|
|
10036
|
-
let currentLineNumber = 0;
|
|
10037
|
-
for (const line of lines) {
|
|
10038
|
-
if (line.startsWith("@@")) {
|
|
10039
|
-
const match = line.match(/^@@ -\d+(?:,\d+)? \+(\d+)/);
|
|
10040
|
-
if (match?.[1]) {
|
|
10041
|
-
currentLineNumber = parseInt(match[1], 10);
|
|
10042
|
-
}
|
|
10043
|
-
continue;
|
|
10044
|
-
}
|
|
10045
|
-
if (line.startsWith("+") && !line.startsWith("+++")) {
|
|
10046
|
-
addedLines.push(currentLineNumber);
|
|
10047
|
-
currentLineNumber++;
|
|
10048
|
-
} else if (!line.startsWith("-")) {
|
|
10049
|
-
currentLineNumber++;
|
|
10050
|
-
}
|
|
10051
|
-
}
|
|
10052
|
-
return addedLines;
|
|
10053
|
-
}
|
|
10054
|
-
/**
|
|
10055
|
-
* Process blame data for a single file to attribute lines to commits
|
|
10056
|
-
* Uses pre-fetched blame data instead of making API calls
|
|
10057
|
-
*/
|
|
10058
|
-
_processFileBlameSafe(file, blameData, prCommitShas) {
|
|
10059
|
-
const addedLines = this._parseAddedLinesFromPatch(file.patch);
|
|
10060
|
-
const addedLinesSet = new Set(addedLines);
|
|
10061
|
-
const fileAttributions = [];
|
|
10062
|
-
for (const blameRange of blameData) {
|
|
10063
|
-
if (!prCommitShas.has(blameRange.commitSha)) {
|
|
10064
|
-
continue;
|
|
10065
|
-
}
|
|
10066
|
-
for (let lineNum = blameRange.startingLine; lineNum <= blameRange.endingLine; lineNum++) {
|
|
10067
|
-
if (addedLinesSet.has(lineNum)) {
|
|
10068
|
-
fileAttributions.push({
|
|
10069
|
-
file: file.filename,
|
|
10070
|
-
line: lineNum,
|
|
10071
|
-
commitSha: blameRange.commitSha
|
|
10072
|
-
});
|
|
10073
|
-
}
|
|
10074
|
-
}
|
|
10075
|
-
}
|
|
10076
|
-
return fileAttributions;
|
|
10077
|
-
}
|
|
10078
|
-
/**
|
|
10079
|
-
* Optimized helper to attribute PR lines to commits using blame API
|
|
10080
|
-
* Batch blame queries for minimal API call time (1 call instead of M calls)
|
|
10081
|
-
*
|
|
10082
|
-
* Uses size-based batching to handle large files:
|
|
10083
|
-
* - Files > 1MB are processed individually with rate limiting
|
|
10084
|
-
* - Smaller files are batched together in a single request
|
|
10085
|
-
* This prevents GitHub API timeouts (~10s) on large generated files.
|
|
10086
|
-
*/
|
|
10087
|
-
async _attributeLinesViaBlame(params) {
|
|
10088
|
-
const { headSha, changedFiles, prCommits } = params;
|
|
10089
|
-
const prCommitShas = new Set(prCommits.map((c) => c.commitSha));
|
|
10090
|
-
const filesWithAdditions = changedFiles.filter((file) => {
|
|
10091
|
-
if (!file.patch || file.patch.trim().length === 0) {
|
|
10092
|
-
return false;
|
|
10093
|
-
}
|
|
10094
|
-
if (!file.sha) {
|
|
10095
|
-
return false;
|
|
10096
|
-
}
|
|
10097
|
-
return true;
|
|
10098
|
-
});
|
|
10099
|
-
if (filesWithAdditions.length === 0) {
|
|
10100
|
-
return [];
|
|
10101
|
-
}
|
|
10102
|
-
const { owner, repo } = parseGithubOwnerAndRepo(this.url);
|
|
10103
|
-
const blameMap = await this.githubSdk.getBlameBatch({
|
|
10104
|
-
owner,
|
|
10105
|
-
repo,
|
|
10106
|
-
ref: headSha,
|
|
10107
|
-
// Use commit SHA directly from PR.head.sha
|
|
10108
|
-
files: filesWithAdditions.map((f) => ({
|
|
10109
|
-
path: f.filename,
|
|
10110
|
-
blobSha: f.sha
|
|
10111
|
-
})),
|
|
10112
|
-
concurrency: GITHUB_API_CONCURRENCY
|
|
10113
|
-
});
|
|
10114
|
-
const allAttributions = [];
|
|
10115
|
-
for (const file of filesWithAdditions) {
|
|
10116
|
-
const blameData = blameMap.get(file.filename) || [];
|
|
10117
|
-
const fileAttributions = this._processFileBlameSafe(
|
|
10118
|
-
file,
|
|
10119
|
-
blameData,
|
|
10120
|
-
prCommitShas
|
|
10121
|
-
);
|
|
10122
|
-
allAttributions.push(...fileAttributions);
|
|
10123
|
-
}
|
|
10124
|
-
return allAttributions;
|
|
10125
|
-
}
|
|
10126
9495
|
};
|
|
10127
9496
|
|
|
10128
9497
|
// src/features/analysis/scm/gitlab/gitlab.ts
|
|
@@ -10135,11 +9504,72 @@ import {
|
|
|
10135
9504
|
Gitlab
|
|
10136
9505
|
} from "@gitbeaker/rest";
|
|
10137
9506
|
import Debug3 from "debug";
|
|
9507
|
+
import pLimit2 from "p-limit";
|
|
10138
9508
|
import {
|
|
10139
9509
|
Agent,
|
|
10140
9510
|
fetch as undiciFetch,
|
|
10141
9511
|
ProxyAgent as ProxyAgent2
|
|
10142
9512
|
} from "undici";
|
|
9513
|
+
|
|
9514
|
+
// src/utils/contextLogger.ts
|
|
9515
|
+
import debugModule from "debug";
|
|
9516
|
+
var debug3 = debugModule("mobb:shared");
|
|
9517
|
+
var _contextLogger = null;
|
|
9518
|
+
var createContextLogger = async () => {
|
|
9519
|
+
if (_contextLogger) return _contextLogger;
|
|
9520
|
+
try {
|
|
9521
|
+
let logger2;
|
|
9522
|
+
try {
|
|
9523
|
+
let module;
|
|
9524
|
+
try {
|
|
9525
|
+
const buildPath = "../../../../../tscommon/backend/build/src/utils/logger";
|
|
9526
|
+
module = await import(buildPath);
|
|
9527
|
+
} catch (e) {
|
|
9528
|
+
const sourcePath = "../../../../../tscommon/backend/src/utils/logger";
|
|
9529
|
+
module = await import(sourcePath);
|
|
9530
|
+
}
|
|
9531
|
+
logger2 = module.logger;
|
|
9532
|
+
} catch {
|
|
9533
|
+
}
|
|
9534
|
+
if (logger2) {
|
|
9535
|
+
_contextLogger = {
|
|
9536
|
+
info: (message, data) => data ? logger2.info(data, message) : logger2.info(message),
|
|
9537
|
+
warn: (message, data) => data ? logger2.warn(data, message) : logger2.warn(message),
|
|
9538
|
+
debug: (message, data) => data ? logger2.debug(data, message) : logger2.debug(message),
|
|
9539
|
+
error: (message, data) => data ? logger2.error(data, message) : logger2.error(message)
|
|
9540
|
+
};
|
|
9541
|
+
return _contextLogger;
|
|
9542
|
+
}
|
|
9543
|
+
} catch {
|
|
9544
|
+
}
|
|
9545
|
+
_contextLogger = {
|
|
9546
|
+
info: (message, data) => debug3(message, data),
|
|
9547
|
+
warn: (message, data) => debug3(message, data),
|
|
9548
|
+
debug: (message, data) => debug3(message, data),
|
|
9549
|
+
error: (message, data) => debug3(message, data)
|
|
9550
|
+
};
|
|
9551
|
+
return _contextLogger;
|
|
9552
|
+
};
|
|
9553
|
+
var contextLogger = {
|
|
9554
|
+
info: async (message, data) => {
|
|
9555
|
+
const logger2 = await createContextLogger();
|
|
9556
|
+
return logger2.info(message, data);
|
|
9557
|
+
},
|
|
9558
|
+
debug: async (message, data) => {
|
|
9559
|
+
const logger2 = await createContextLogger();
|
|
9560
|
+
return logger2.debug(message, data);
|
|
9561
|
+
},
|
|
9562
|
+
warn: async (message, data) => {
|
|
9563
|
+
const logger2 = await createContextLogger();
|
|
9564
|
+
return logger2.warn(message, data);
|
|
9565
|
+
},
|
|
9566
|
+
error: async (message, data) => {
|
|
9567
|
+
const logger2 = await createContextLogger();
|
|
9568
|
+
return logger2.error(message, data);
|
|
9569
|
+
}
|
|
9570
|
+
};
|
|
9571
|
+
|
|
9572
|
+
// src/features/analysis/scm/gitlab/gitlab.ts
|
|
10143
9573
|
init_env();
|
|
10144
9574
|
|
|
10145
9575
|
// src/features/analysis/scm/gitlab/types.ts
|
|
@@ -10156,6 +9586,14 @@ function removeTrailingSlash2(str) {
|
|
|
10156
9586
|
return str.trim().replace(/\/+$/, "");
|
|
10157
9587
|
}
|
|
10158
9588
|
var MAX_GITLAB_PR_BODY_LENGTH = 1048576;
|
|
9589
|
+
function buildUnifiedDiff(diffs) {
|
|
9590
|
+
return diffs.filter((d) => d.diff).map((d) => {
|
|
9591
|
+
const oldPath = d.old_path || d.new_path || "";
|
|
9592
|
+
const newPath = d.new_path || d.old_path || "";
|
|
9593
|
+
return `diff --git a/${oldPath} b/${newPath}
|
|
9594
|
+
${d.diff}`;
|
|
9595
|
+
}).join("\n");
|
|
9596
|
+
}
|
|
10159
9597
|
function getRandomGitlabCloudAnonToken() {
|
|
10160
9598
|
if (!GITLAB_API_TOKEN || typeof GITLAB_API_TOKEN !== "string") {
|
|
10161
9599
|
return void 0;
|
|
@@ -10209,7 +9647,10 @@ async function gitlabValidateParams({
|
|
|
10209
9647
|
if (code === 404 || description.includes("404") || description.includes("Not Found")) {
|
|
10210
9648
|
throw new InvalidRepoUrlError(`invalid gitlab repo URL: ${url}`);
|
|
10211
9649
|
}
|
|
10212
|
-
|
|
9650
|
+
contextLogger.warn("[gitlabValidateParams] Error validating params", {
|
|
9651
|
+
url,
|
|
9652
|
+
error: e
|
|
9653
|
+
});
|
|
10213
9654
|
throw new InvalidRepoUrlError(
|
|
10214
9655
|
`cannot access gitlab repo URL: ${url} with the provided access token`
|
|
10215
9656
|
);
|
|
@@ -10238,6 +9679,13 @@ async function getGitlabIsUserCollaborator({
|
|
|
10238
9679
|
];
|
|
10239
9680
|
return accessLevelWithWriteAccess.includes(groupAccess) || accessLevelWithWriteAccess.includes(projectAccess);
|
|
10240
9681
|
} catch (e) {
|
|
9682
|
+
contextLogger.warn(
|
|
9683
|
+
"[getGitlabIsUserCollaborator] Error checking collaborator status",
|
|
9684
|
+
{
|
|
9685
|
+
error: e instanceof Error ? e.message : String(e),
|
|
9686
|
+
repoUrl
|
|
9687
|
+
}
|
|
9688
|
+
);
|
|
10241
9689
|
return false;
|
|
10242
9690
|
}
|
|
10243
9691
|
}
|
|
@@ -10284,6 +9732,14 @@ async function getGitlabIsRemoteBranch({
|
|
|
10284
9732
|
const res = await api2.Branches.show(projectPath, branch);
|
|
10285
9733
|
return res.name === branch;
|
|
10286
9734
|
} catch (e) {
|
|
9735
|
+
contextLogger.warn(
|
|
9736
|
+
"[getGitlabIsRemoteBranch] Error checking remote branch",
|
|
9737
|
+
{
|
|
9738
|
+
error: e instanceof Error ? e.message : String(e),
|
|
9739
|
+
repoUrl,
|
|
9740
|
+
branch
|
|
9741
|
+
}
|
|
9742
|
+
);
|
|
10287
9743
|
return false;
|
|
10288
9744
|
}
|
|
10289
9745
|
}
|
|
@@ -10316,49 +9772,257 @@ async function getGitlabRepoList(url, accessToken) {
|
|
|
10316
9772
|
})
|
|
10317
9773
|
);
|
|
10318
9774
|
}
|
|
10319
|
-
async function getGitlabBranchList({
|
|
10320
|
-
accessToken,
|
|
10321
|
-
repoUrl
|
|
9775
|
+
async function getGitlabBranchList({
|
|
9776
|
+
accessToken,
|
|
9777
|
+
repoUrl
|
|
9778
|
+
}) {
|
|
9779
|
+
const { projectPath } = parseGitlabOwnerAndRepo(repoUrl);
|
|
9780
|
+
const api2 = getGitBeaker({ url: repoUrl, gitlabAuthToken: accessToken });
|
|
9781
|
+
try {
|
|
9782
|
+
const res = await api2.Branches.all(projectPath, {
|
|
9783
|
+
//keyset API pagination is not supported by GL for the branch list (at least not the on-prem version)
|
|
9784
|
+
//so for now we stick with the default pagination and just return the first page and limit the results to 1000 entries.
|
|
9785
|
+
//This is a temporary solution until we implement list branches with name search.
|
|
9786
|
+
perPage: MAX_BRANCHES_FETCH,
|
|
9787
|
+
page: 1
|
|
9788
|
+
});
|
|
9789
|
+
res.sort((a, b) => {
|
|
9790
|
+
if (!a.commit?.committed_date || !b.commit?.committed_date) {
|
|
9791
|
+
return 0;
|
|
9792
|
+
}
|
|
9793
|
+
return new Date(b.commit?.committed_date).getTime() - new Date(a.commit?.committed_date).getTime();
|
|
9794
|
+
});
|
|
9795
|
+
return res.map((branch) => branch.name).slice(0, MAX_BRANCHES_FETCH);
|
|
9796
|
+
} catch (e) {
|
|
9797
|
+
contextLogger.warn("[getGitlabBranchList] Error fetching branch list", {
|
|
9798
|
+
error: e instanceof Error ? e.message : String(e),
|
|
9799
|
+
repoUrl
|
|
9800
|
+
});
|
|
9801
|
+
return [];
|
|
9802
|
+
}
|
|
9803
|
+
}
|
|
9804
|
+
async function createMergeRequest(options) {
|
|
9805
|
+
const { projectPath } = parseGitlabOwnerAndRepo(options.repoUrl);
|
|
9806
|
+
const api2 = getGitBeaker({
|
|
9807
|
+
url: options.repoUrl,
|
|
9808
|
+
gitlabAuthToken: options.accessToken
|
|
9809
|
+
});
|
|
9810
|
+
const res = await api2.MergeRequests.create(
|
|
9811
|
+
projectPath,
|
|
9812
|
+
options.sourceBranchName,
|
|
9813
|
+
options.targetBranchName,
|
|
9814
|
+
options.title,
|
|
9815
|
+
{
|
|
9816
|
+
description: safeBody(options.body, MAX_GITLAB_PR_BODY_LENGTH)
|
|
9817
|
+
}
|
|
9818
|
+
);
|
|
9819
|
+
return res.iid;
|
|
9820
|
+
}
|
|
9821
|
+
async function getGitlabMergeRequest({
|
|
9822
|
+
url,
|
|
9823
|
+
prNumber,
|
|
9824
|
+
accessToken
|
|
9825
|
+
}) {
|
|
9826
|
+
const { projectPath } = parseGitlabOwnerAndRepo(url);
|
|
9827
|
+
const api2 = getGitBeaker({
|
|
9828
|
+
url,
|
|
9829
|
+
gitlabAuthToken: accessToken
|
|
9830
|
+
});
|
|
9831
|
+
return await api2.MergeRequests.show(projectPath, prNumber);
|
|
9832
|
+
}
|
|
9833
|
+
async function searchGitlabMergeRequests({
|
|
9834
|
+
repoUrl,
|
|
9835
|
+
accessToken,
|
|
9836
|
+
state,
|
|
9837
|
+
updatedAfter,
|
|
9838
|
+
orderBy = "updated_at",
|
|
9839
|
+
sort = "desc",
|
|
9840
|
+
perPage = GITLAB_PER_PAGE,
|
|
9841
|
+
page = 1
|
|
9842
|
+
}) {
|
|
9843
|
+
const { projectPath } = parseGitlabOwnerAndRepo(repoUrl);
|
|
9844
|
+
debug4(
|
|
9845
|
+
"[searchGitlabMergeRequests] Fetching MRs for %s (page=%d, perPage=%d)",
|
|
9846
|
+
projectPath,
|
|
9847
|
+
page,
|
|
9848
|
+
perPage
|
|
9849
|
+
);
|
|
9850
|
+
const api2 = getGitBeaker({
|
|
9851
|
+
url: repoUrl,
|
|
9852
|
+
gitlabAuthToken: accessToken
|
|
9853
|
+
});
|
|
9854
|
+
const mergeRequests = await api2.MergeRequests.all({
|
|
9855
|
+
projectId: projectPath,
|
|
9856
|
+
state: state === "all" ? void 0 : state,
|
|
9857
|
+
updatedAfter: updatedAfter?.toISOString(),
|
|
9858
|
+
orderBy,
|
|
9859
|
+
sort,
|
|
9860
|
+
perPage,
|
|
9861
|
+
page
|
|
9862
|
+
});
|
|
9863
|
+
const items = mergeRequests.map((mr) => ({
|
|
9864
|
+
iid: mr.iid,
|
|
9865
|
+
title: mr.title,
|
|
9866
|
+
state: mr.state,
|
|
9867
|
+
sourceBranch: mr.source_branch,
|
|
9868
|
+
targetBranch: mr.target_branch,
|
|
9869
|
+
authorUsername: mr.author?.username,
|
|
9870
|
+
createdAt: mr.created_at,
|
|
9871
|
+
updatedAt: mr.updated_at,
|
|
9872
|
+
description: mr.description
|
|
9873
|
+
}));
|
|
9874
|
+
debug4(
|
|
9875
|
+
"[searchGitlabMergeRequests] Found %d MRs on page %d",
|
|
9876
|
+
items.length,
|
|
9877
|
+
page
|
|
9878
|
+
);
|
|
9879
|
+
return {
|
|
9880
|
+
items,
|
|
9881
|
+
hasMore: mergeRequests.length === perPage
|
|
9882
|
+
};
|
|
9883
|
+
}
|
|
9884
|
+
var GITLAB_API_CONCURRENCY = 5;
|
|
9885
|
+
async function getGitlabMrCommitsBatch({
|
|
9886
|
+
repoUrl,
|
|
9887
|
+
accessToken,
|
|
9888
|
+
mrNumbers
|
|
9889
|
+
}) {
|
|
9890
|
+
if (mrNumbers.length === 0) {
|
|
9891
|
+
return /* @__PURE__ */ new Map();
|
|
9892
|
+
}
|
|
9893
|
+
const { projectPath } = parseGitlabOwnerAndRepo(repoUrl);
|
|
9894
|
+
const api2 = getGitBeaker({
|
|
9895
|
+
url: repoUrl,
|
|
9896
|
+
gitlabAuthToken: accessToken
|
|
9897
|
+
});
|
|
9898
|
+
const limit = pLimit2(GITLAB_API_CONCURRENCY);
|
|
9899
|
+
const results = await Promise.all(
|
|
9900
|
+
mrNumbers.map(
|
|
9901
|
+
(mrNumber) => limit(async () => {
|
|
9902
|
+
try {
|
|
9903
|
+
const commits = await api2.MergeRequests.allCommits(
|
|
9904
|
+
projectPath,
|
|
9905
|
+
mrNumber
|
|
9906
|
+
);
|
|
9907
|
+
return [mrNumber, commits.map((c) => c.id)];
|
|
9908
|
+
} catch (error) {
|
|
9909
|
+
contextLogger.warn(
|
|
9910
|
+
"[getGitlabMrCommitsBatch] Failed to fetch commits for MR",
|
|
9911
|
+
{
|
|
9912
|
+
error: error instanceof Error ? error.message : String(error),
|
|
9913
|
+
mrNumber,
|
|
9914
|
+
repoUrl
|
|
9915
|
+
}
|
|
9916
|
+
);
|
|
9917
|
+
return [mrNumber, []];
|
|
9918
|
+
}
|
|
9919
|
+
})
|
|
9920
|
+
)
|
|
9921
|
+
);
|
|
9922
|
+
return new Map(results);
|
|
9923
|
+
}
|
|
9924
|
+
async function getGitlabMrDataBatch({
|
|
9925
|
+
repoUrl,
|
|
9926
|
+
accessToken,
|
|
9927
|
+
mrNumbers
|
|
9928
|
+
}) {
|
|
9929
|
+
if (mrNumbers.length === 0) {
|
|
9930
|
+
return /* @__PURE__ */ new Map();
|
|
9931
|
+
}
|
|
9932
|
+
const { projectPath } = parseGitlabOwnerAndRepo(repoUrl);
|
|
9933
|
+
const api2 = getGitBeaker({
|
|
9934
|
+
url: repoUrl,
|
|
9935
|
+
gitlabAuthToken: accessToken
|
|
9936
|
+
});
|
|
9937
|
+
const limit = pLimit2(GITLAB_API_CONCURRENCY);
|
|
9938
|
+
const results = await Promise.all(
|
|
9939
|
+
mrNumbers.map(
|
|
9940
|
+
(mrNumber) => limit(async () => {
|
|
9941
|
+
try {
|
|
9942
|
+
const [diffs, notes] = await Promise.all([
|
|
9943
|
+
api2.MergeRequests.allDiffs(projectPath, mrNumber),
|
|
9944
|
+
api2.MergeRequestNotes.all(projectPath, mrNumber)
|
|
9945
|
+
]);
|
|
9946
|
+
let additions = 0;
|
|
9947
|
+
let deletions = 0;
|
|
9948
|
+
for (const diff of diffs) {
|
|
9949
|
+
if (diff.diff) {
|
|
9950
|
+
for (const line of diff.diff.split("\n")) {
|
|
9951
|
+
if (line.startsWith("+") && !line.startsWith("+++")) {
|
|
9952
|
+
additions++;
|
|
9953
|
+
} else if (line.startsWith("-") && !line.startsWith("---")) {
|
|
9954
|
+
deletions++;
|
|
9955
|
+
}
|
|
9956
|
+
}
|
|
9957
|
+
}
|
|
9958
|
+
}
|
|
9959
|
+
const comments = notes.map((note) => ({
|
|
9960
|
+
author: note.author ? {
|
|
9961
|
+
login: note.author.username,
|
|
9962
|
+
type: note.author.username.endsWith("[bot]") || note.author.username.toLowerCase() === "linear" ? "Bot" : "User"
|
|
9963
|
+
} : null,
|
|
9964
|
+
body: note.body
|
|
9965
|
+
}));
|
|
9966
|
+
return [
|
|
9967
|
+
mrNumber,
|
|
9968
|
+
{ changedLines: { additions, deletions }, comments }
|
|
9969
|
+
];
|
|
9970
|
+
} catch (error) {
|
|
9971
|
+
contextLogger.warn(
|
|
9972
|
+
"[getGitlabMrDataBatch] Failed to fetch data for MR",
|
|
9973
|
+
{
|
|
9974
|
+
error: error instanceof Error ? error.message : String(error),
|
|
9975
|
+
mrNumber,
|
|
9976
|
+
repoUrl
|
|
9977
|
+
}
|
|
9978
|
+
);
|
|
9979
|
+
return [
|
|
9980
|
+
mrNumber,
|
|
9981
|
+
{
|
|
9982
|
+
changedLines: { additions: 0, deletions: 0 },
|
|
9983
|
+
comments: []
|
|
9984
|
+
}
|
|
9985
|
+
];
|
|
9986
|
+
}
|
|
9987
|
+
})
|
|
9988
|
+
)
|
|
9989
|
+
);
|
|
9990
|
+
return new Map(results);
|
|
9991
|
+
}
|
|
9992
|
+
async function getGitlabMergeRequestLinesAdded({
|
|
9993
|
+
url,
|
|
9994
|
+
prNumber,
|
|
9995
|
+
accessToken
|
|
10322
9996
|
}) {
|
|
10323
|
-
const { projectPath } = parseGitlabOwnerAndRepo(repoUrl);
|
|
10324
|
-
const api2 = getGitBeaker({ url: repoUrl, gitlabAuthToken: accessToken });
|
|
10325
9997
|
try {
|
|
10326
|
-
const
|
|
10327
|
-
|
|
10328
|
-
|
|
10329
|
-
|
|
10330
|
-
perPage: MAX_BRANCHES_FETCH,
|
|
10331
|
-
page: 1
|
|
9998
|
+
const { projectPath } = parseGitlabOwnerAndRepo(url);
|
|
9999
|
+
const api2 = getGitBeaker({
|
|
10000
|
+
url,
|
|
10001
|
+
gitlabAuthToken: accessToken
|
|
10332
10002
|
});
|
|
10333
|
-
|
|
10334
|
-
|
|
10335
|
-
|
|
10003
|
+
const diffs = await api2.MergeRequests.allDiffs(projectPath, prNumber);
|
|
10004
|
+
let linesAdded = 0;
|
|
10005
|
+
for (const diff of diffs) {
|
|
10006
|
+
if (diff.diff) {
|
|
10007
|
+
const addedLines = diff.diff.split("\n").filter(
|
|
10008
|
+
(line) => line.startsWith("+") && !line.startsWith("+++")
|
|
10009
|
+
).length;
|
|
10010
|
+
linesAdded += addedLines;
|
|
10336
10011
|
}
|
|
10337
|
-
return new Date(b.commit?.committed_date).getTime() - new Date(a.commit?.committed_date).getTime();
|
|
10338
|
-
});
|
|
10339
|
-
return res.map((branch) => branch.name).slice(0, MAX_BRANCHES_FETCH);
|
|
10340
|
-
} catch (e) {
|
|
10341
|
-
return [];
|
|
10342
|
-
}
|
|
10343
|
-
}
|
|
10344
|
-
async function createMergeRequest(options) {
|
|
10345
|
-
const { projectPath } = parseGitlabOwnerAndRepo(options.repoUrl);
|
|
10346
|
-
const api2 = getGitBeaker({
|
|
10347
|
-
url: options.repoUrl,
|
|
10348
|
-
gitlabAuthToken: options.accessToken
|
|
10349
|
-
});
|
|
10350
|
-
const res = await api2.MergeRequests.create(
|
|
10351
|
-
projectPath,
|
|
10352
|
-
options.sourceBranchName,
|
|
10353
|
-
options.targetBranchName,
|
|
10354
|
-
options.title,
|
|
10355
|
-
{
|
|
10356
|
-
description: safeBody(options.body, MAX_GITLAB_PR_BODY_LENGTH)
|
|
10357
10012
|
}
|
|
10358
|
-
|
|
10359
|
-
|
|
10013
|
+
return linesAdded;
|
|
10014
|
+
} catch (error) {
|
|
10015
|
+
contextLogger.warn(
|
|
10016
|
+
"[getGitlabMergeRequestLinesAdded] Failed to fetch diffs for MR",
|
|
10017
|
+
{
|
|
10018
|
+
prNumber,
|
|
10019
|
+
error
|
|
10020
|
+
}
|
|
10021
|
+
);
|
|
10022
|
+
return 0;
|
|
10023
|
+
}
|
|
10360
10024
|
}
|
|
10361
|
-
async function
|
|
10025
|
+
async function getGitlabMergeRequestMetrics({
|
|
10362
10026
|
url,
|
|
10363
10027
|
prNumber,
|
|
10364
10028
|
accessToken
|
|
@@ -10368,7 +10032,28 @@ async function getGitlabMergeRequest({
|
|
|
10368
10032
|
url,
|
|
10369
10033
|
gitlabAuthToken: accessToken
|
|
10370
10034
|
});
|
|
10371
|
-
|
|
10035
|
+
const [mr, commits, linesAdded, notes] = await Promise.all([
|
|
10036
|
+
api2.MergeRequests.show(projectPath, prNumber),
|
|
10037
|
+
api2.MergeRequests.allCommits(projectPath, prNumber),
|
|
10038
|
+
getGitlabMergeRequestLinesAdded({ url, prNumber, accessToken }),
|
|
10039
|
+
api2.MergeRequestNotes.all(projectPath, prNumber)
|
|
10040
|
+
]);
|
|
10041
|
+
const sortedCommits = [...commits].sort(
|
|
10042
|
+
(a, b) => new Date(a.created_at).getTime() - new Date(b.created_at).getTime()
|
|
10043
|
+
);
|
|
10044
|
+
const firstCommitDate = sortedCommits[0]?.created_at ?? null;
|
|
10045
|
+
const commentIds = notes.filter((note) => !note.system).map((note) => String(note.id));
|
|
10046
|
+
return {
|
|
10047
|
+
state: mr.state,
|
|
10048
|
+
isDraft: mr.draft ?? false,
|
|
10049
|
+
createdAt: mr.created_at,
|
|
10050
|
+
mergedAt: mr.merged_at ?? null,
|
|
10051
|
+
linesAdded,
|
|
10052
|
+
commitsCount: commits.length,
|
|
10053
|
+
commitShas: commits.map((c) => c.id),
|
|
10054
|
+
firstCommitDate,
|
|
10055
|
+
commentIds
|
|
10056
|
+
};
|
|
10372
10057
|
}
|
|
10373
10058
|
async function getGitlabCommitUrl({
|
|
10374
10059
|
url,
|
|
@@ -10447,26 +10132,125 @@ function parseGitlabOwnerAndRepo(gitlabUrl) {
|
|
|
10447
10132
|
const { organization, repoName, projectPath } = parsingResult;
|
|
10448
10133
|
return { owner: organization, repo: repoName, projectPath };
|
|
10449
10134
|
}
|
|
10450
|
-
|
|
10451
|
-
|
|
10452
|
-
|
|
10453
|
-
|
|
10454
|
-
|
|
10455
|
-
|
|
10456
|
-
|
|
10457
|
-
|
|
10458
|
-
|
|
10459
|
-
|
|
10460
|
-
|
|
10461
|
-
|
|
10462
|
-
|
|
10463
|
-
|
|
10135
|
+
var GITLAB_MAX_RESULTS_LIMIT = 1024;
|
|
10136
|
+
var GITLAB_PER_PAGE = 128;
|
|
10137
|
+
async function getGitlabRecentCommits({
|
|
10138
|
+
repoUrl,
|
|
10139
|
+
accessToken,
|
|
10140
|
+
since
|
|
10141
|
+
}) {
|
|
10142
|
+
const { projectPath } = parseGitlabOwnerAndRepo(repoUrl);
|
|
10143
|
+
const api2 = getGitBeaker({ url: repoUrl, gitlabAuthToken: accessToken });
|
|
10144
|
+
const allCommits = [];
|
|
10145
|
+
let page = 1;
|
|
10146
|
+
let hasMore = true;
|
|
10147
|
+
while (hasMore && allCommits.length < GITLAB_MAX_RESULTS_LIMIT) {
|
|
10148
|
+
const commits = await api2.Commits.all(projectPath, {
|
|
10149
|
+
since,
|
|
10150
|
+
perPage: GITLAB_PER_PAGE,
|
|
10151
|
+
page
|
|
10152
|
+
});
|
|
10153
|
+
if (commits.length === 0) {
|
|
10154
|
+
hasMore = false;
|
|
10155
|
+
break;
|
|
10156
|
+
}
|
|
10157
|
+
for (const commit of commits) {
|
|
10158
|
+
if (allCommits.length >= GITLAB_MAX_RESULTS_LIMIT) {
|
|
10159
|
+
break;
|
|
10160
|
+
}
|
|
10161
|
+
allCommits.push({
|
|
10162
|
+
sha: commit.id,
|
|
10163
|
+
commit: {
|
|
10164
|
+
committer: commit.committed_date ? { date: commit.committed_date } : void 0,
|
|
10165
|
+
author: {
|
|
10166
|
+
email: commit.author_email,
|
|
10167
|
+
name: commit.author_name
|
|
10168
|
+
},
|
|
10169
|
+
message: commit.message
|
|
10170
|
+
},
|
|
10171
|
+
parents: commit.parent_ids?.map((sha) => ({ sha })) || []
|
|
10172
|
+
});
|
|
10173
|
+
}
|
|
10174
|
+
if (commits.length < GITLAB_PER_PAGE) {
|
|
10175
|
+
hasMore = false;
|
|
10176
|
+
} else {
|
|
10177
|
+
page++;
|
|
10178
|
+
}
|
|
10179
|
+
}
|
|
10180
|
+
if (allCommits.length >= GITLAB_MAX_RESULTS_LIMIT) {
|
|
10181
|
+
contextLogger.warn("[getGitlabRecentCommits] Hit commit pagination limit", {
|
|
10182
|
+
limit: GITLAB_MAX_RESULTS_LIMIT,
|
|
10183
|
+
count: allCommits.length,
|
|
10184
|
+
repoUrl,
|
|
10185
|
+
since
|
|
10186
|
+
});
|
|
10187
|
+
}
|
|
10188
|
+
return allCommits;
|
|
10189
|
+
}
|
|
10190
|
+
async function getGitlabCommitDiff({
|
|
10191
|
+
repoUrl,
|
|
10192
|
+
accessToken,
|
|
10193
|
+
commitSha
|
|
10194
|
+
}) {
|
|
10195
|
+
const { projectPath } = parseGitlabOwnerAndRepo(repoUrl);
|
|
10196
|
+
const api2 = getGitBeaker({ url: repoUrl, gitlabAuthToken: accessToken });
|
|
10197
|
+
const [commit, diffs] = await Promise.all([
|
|
10198
|
+
api2.Commits.show(projectPath, commitSha),
|
|
10199
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
10200
|
+
api2.Commits.showDiff(projectPath, commitSha, { unidiff: true })
|
|
10201
|
+
]);
|
|
10202
|
+
const diffString = buildUnifiedDiff(diffs);
|
|
10203
|
+
const commitTimestamp = commit.committed_date ? new Date(commit.committed_date) : /* @__PURE__ */ new Date();
|
|
10204
|
+
return {
|
|
10205
|
+
diff: diffString,
|
|
10206
|
+
commitTimestamp,
|
|
10207
|
+
commitSha: commit.id,
|
|
10208
|
+
authorName: commit.author_name,
|
|
10209
|
+
authorEmail: commit.author_email,
|
|
10210
|
+
message: commit.message
|
|
10211
|
+
};
|
|
10212
|
+
}
|
|
10213
|
+
async function getGitlabRateLimitStatus({
|
|
10214
|
+
repoUrl,
|
|
10215
|
+
accessToken
|
|
10216
|
+
}) {
|
|
10217
|
+
try {
|
|
10218
|
+
const api2 = getGitBeaker({ url: repoUrl, gitlabAuthToken: accessToken });
|
|
10219
|
+
const response = await api2.Users.showCurrentUser({ showExpanded: true });
|
|
10220
|
+
const headers = response.headers;
|
|
10221
|
+
if (!headers) {
|
|
10222
|
+
return null;
|
|
10223
|
+
}
|
|
10224
|
+
const remaining = headers["ratelimit-remaining"];
|
|
10225
|
+
const reset = headers["ratelimit-reset"];
|
|
10226
|
+
const limit = headers["ratelimit-limit"];
|
|
10227
|
+
if (!remaining || !reset) {
|
|
10228
|
+
return null;
|
|
10229
|
+
}
|
|
10230
|
+
const remainingNum = parseInt(remaining, 10);
|
|
10231
|
+
const resetNum = parseInt(reset, 10);
|
|
10232
|
+
if (isNaN(remainingNum) || isNaN(resetNum)) {
|
|
10233
|
+
contextLogger.warn(
|
|
10234
|
+
"[getGitlabRateLimitStatus] Malformed rate limit headers",
|
|
10235
|
+
{ remaining, reset, repoUrl }
|
|
10236
|
+
);
|
|
10237
|
+
return null;
|
|
10238
|
+
}
|
|
10464
10239
|
return {
|
|
10465
|
-
|
|
10466
|
-
|
|
10467
|
-
|
|
10240
|
+
remaining: remainingNum,
|
|
10241
|
+
reset: new Date(resetNum * 1e3),
|
|
10242
|
+
limit: limit ? parseInt(limit, 10) : void 0
|
|
10468
10243
|
};
|
|
10469
|
-
})
|
|
10244
|
+
} catch (error) {
|
|
10245
|
+
contextLogger.warn(
|
|
10246
|
+
"[getGitlabRateLimitStatus] Error fetching rate limit status",
|
|
10247
|
+
{
|
|
10248
|
+
error: error instanceof Error ? error.message : String(error),
|
|
10249
|
+
repoUrl
|
|
10250
|
+
}
|
|
10251
|
+
);
|
|
10252
|
+
return null;
|
|
10253
|
+
}
|
|
10470
10254
|
}
|
|
10471
10255
|
async function processBody(response) {
|
|
10472
10256
|
const headers = response.headers;
|
|
@@ -10507,8 +10291,90 @@ async function brokerRequestHandler(endpoint, options) {
|
|
|
10507
10291
|
};
|
|
10508
10292
|
throw new Error(`gitbeaker: ${response.statusText}`);
|
|
10509
10293
|
}
|
|
10294
|
+
async function getGitlabMergeRequestDiff({
|
|
10295
|
+
repoUrl,
|
|
10296
|
+
accessToken,
|
|
10297
|
+
mrNumber
|
|
10298
|
+
}) {
|
|
10299
|
+
debug4("[getGitlabMergeRequestDiff] Starting for MR #%d", mrNumber);
|
|
10300
|
+
const { projectPath } = parseGitlabOwnerAndRepo(repoUrl);
|
|
10301
|
+
const api2 = getGitBeaker({ url: repoUrl, gitlabAuthToken: accessToken });
|
|
10302
|
+
debug4(
|
|
10303
|
+
"[getGitlabMergeRequestDiff] Fetching MR details, diffs, and commits..."
|
|
10304
|
+
);
|
|
10305
|
+
const startMrFetch = Date.now();
|
|
10306
|
+
const [mr, mrDiffs, mrCommitsRaw] = await Promise.all([
|
|
10307
|
+
api2.MergeRequests.show(projectPath, mrNumber),
|
|
10308
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
10309
|
+
api2.MergeRequests.allDiffs(projectPath, mrNumber, { unidiff: true }),
|
|
10310
|
+
api2.MergeRequests.allCommits(projectPath, mrNumber)
|
|
10311
|
+
]);
|
|
10312
|
+
debug4(
|
|
10313
|
+
"[getGitlabMergeRequestDiff] MR fetch took %dms. Diffs: %d, Commits: %d",
|
|
10314
|
+
Date.now() - startMrFetch,
|
|
10315
|
+
mrDiffs.length,
|
|
10316
|
+
mrCommitsRaw.length
|
|
10317
|
+
);
|
|
10318
|
+
const diffString = buildUnifiedDiff(mrDiffs);
|
|
10319
|
+
debug4(
|
|
10320
|
+
"[getGitlabMergeRequestDiff] Fetching commit diffs for %d commits...",
|
|
10321
|
+
mrCommitsRaw.length
|
|
10322
|
+
);
|
|
10323
|
+
const startCommitFetch = Date.now();
|
|
10324
|
+
const commitDiffLimit = pLimit2(GITLAB_API_CONCURRENCY);
|
|
10325
|
+
const commits = await Promise.all(
|
|
10326
|
+
mrCommitsRaw.map(
|
|
10327
|
+
(commit) => commitDiffLimit(async () => {
|
|
10328
|
+
const commitDiff = await getGitlabCommitDiff({
|
|
10329
|
+
repoUrl,
|
|
10330
|
+
accessToken,
|
|
10331
|
+
commitSha: commit.id
|
|
10332
|
+
});
|
|
10333
|
+
return {
|
|
10334
|
+
diff: commitDiff.diff,
|
|
10335
|
+
commitTimestamp: commitDiff.commitTimestamp,
|
|
10336
|
+
commitSha: commitDiff.commitSha,
|
|
10337
|
+
authorName: commitDiff.authorName,
|
|
10338
|
+
authorEmail: commitDiff.authorEmail,
|
|
10339
|
+
message: commitDiff.message
|
|
10340
|
+
};
|
|
10341
|
+
})
|
|
10342
|
+
)
|
|
10343
|
+
);
|
|
10344
|
+
commits.sort(
|
|
10345
|
+
(a, b) => new Date(a.commitTimestamp).getTime() - new Date(b.commitTimestamp).getTime()
|
|
10346
|
+
);
|
|
10347
|
+
debug4(
|
|
10348
|
+
"[getGitlabMergeRequestDiff] Commit diffs fetch took %dms",
|
|
10349
|
+
Date.now() - startCommitFetch
|
|
10350
|
+
);
|
|
10351
|
+
const addedLinesByFile = parseAddedLinesByFile(diffString);
|
|
10352
|
+
const diffLines = [];
|
|
10353
|
+
for (const [file, lines] of addedLinesByFile) {
|
|
10354
|
+
for (const line of lines) {
|
|
10355
|
+
diffLines.push({ file, line });
|
|
10356
|
+
}
|
|
10357
|
+
}
|
|
10358
|
+
return {
|
|
10359
|
+
diff: diffString,
|
|
10360
|
+
createdAt: new Date(mr.created_at),
|
|
10361
|
+
updatedAt: new Date(mr.updated_at),
|
|
10362
|
+
submitRequestId: String(mrNumber),
|
|
10363
|
+
submitRequestNumber: mrNumber,
|
|
10364
|
+
sourceBranch: mr.source_branch,
|
|
10365
|
+
targetBranch: mr.target_branch,
|
|
10366
|
+
authorName: mr.author?.name || mr.author?.username,
|
|
10367
|
+
authorEmail: void 0,
|
|
10368
|
+
// GitLab MR API doesn't expose author email directly
|
|
10369
|
+
title: mr.title,
|
|
10370
|
+
description: mr.description || void 0,
|
|
10371
|
+
commits,
|
|
10372
|
+
diffLines
|
|
10373
|
+
};
|
|
10374
|
+
}
|
|
10510
10375
|
|
|
10511
10376
|
// src/features/analysis/scm/gitlab/GitlabSCMLib.ts
|
|
10377
|
+
init_client_generates();
|
|
10512
10378
|
var GitlabSCMLib = class extends SCMLib {
|
|
10513
10379
|
constructor(url, accessToken, scmOrg) {
|
|
10514
10380
|
super(url, accessToken, scmOrg);
|
|
@@ -10535,7 +10401,7 @@ var GitlabSCMLib = class extends SCMLib {
|
|
|
10535
10401
|
}
|
|
10536
10402
|
async getRepoList(_scmOrg) {
|
|
10537
10403
|
if (!this.accessToken) {
|
|
10538
|
-
|
|
10404
|
+
contextLogger.warn("[GitlabSCMLib.getRepoList] No access token provided");
|
|
10539
10405
|
throw new Error("no access token");
|
|
10540
10406
|
}
|
|
10541
10407
|
return getGitlabRepoList(this.url, this.accessToken);
|
|
@@ -10627,16 +10493,6 @@ var GitlabSCMLib = class extends SCMLib {
|
|
|
10627
10493
|
markdownComment: comment
|
|
10628
10494
|
});
|
|
10629
10495
|
}
|
|
10630
|
-
async getRepoBlameRanges(ref, path25) {
|
|
10631
|
-
this._validateUrl();
|
|
10632
|
-
return await getGitlabBlameRanges(
|
|
10633
|
-
{ ref, path: path25, gitlabUrl: this.url },
|
|
10634
|
-
{
|
|
10635
|
-
url: this.url,
|
|
10636
|
-
gitlabAuthToken: this.accessToken
|
|
10637
|
-
}
|
|
10638
|
-
);
|
|
10639
|
-
}
|
|
10640
10496
|
async getReferenceData(ref) {
|
|
10641
10497
|
this._validateUrl();
|
|
10642
10498
|
return await getGitlabReferenceData(
|
|
@@ -10680,25 +10536,180 @@ var GitlabSCMLib = class extends SCMLib {
|
|
|
10680
10536
|
this._validateAccessTokenAndUrl();
|
|
10681
10537
|
return `${this.url}/-/commits/${branchName}`;
|
|
10682
10538
|
}
|
|
10683
|
-
async getCommitDiff(
|
|
10684
|
-
|
|
10539
|
+
async getCommitDiff(commitSha) {
|
|
10540
|
+
this._validateAccessTokenAndUrl();
|
|
10541
|
+
const result = await getGitlabCommitDiff({
|
|
10542
|
+
repoUrl: this.url,
|
|
10543
|
+
accessToken: this.accessToken,
|
|
10544
|
+
commitSha
|
|
10545
|
+
});
|
|
10546
|
+
return {
|
|
10547
|
+
diff: result.diff,
|
|
10548
|
+
commitTimestamp: result.commitTimestamp,
|
|
10549
|
+
commitSha: result.commitSha,
|
|
10550
|
+
authorName: result.authorName,
|
|
10551
|
+
authorEmail: result.authorEmail,
|
|
10552
|
+
message: result.message
|
|
10553
|
+
};
|
|
10685
10554
|
}
|
|
10686
|
-
async getSubmitRequestDiff(
|
|
10687
|
-
|
|
10555
|
+
async getSubmitRequestDiff(submitRequestId) {
|
|
10556
|
+
this._validateAccessTokenAndUrl();
|
|
10557
|
+
const mrNumber = parseInt(submitRequestId, 10);
|
|
10558
|
+
if (isNaN(mrNumber) || mrNumber <= 0) {
|
|
10559
|
+
throw new Error(`Invalid merge request ID: ${submitRequestId}`);
|
|
10560
|
+
}
|
|
10561
|
+
return getGitlabMergeRequestDiff({
|
|
10562
|
+
repoUrl: this.url,
|
|
10563
|
+
accessToken: this.accessToken,
|
|
10564
|
+
mrNumber
|
|
10565
|
+
});
|
|
10566
|
+
}
|
|
10567
|
+
async searchSubmitRequests(params) {
|
|
10568
|
+
this._validateAccessTokenAndUrl();
|
|
10569
|
+
const page = parseCursorSafe(params.cursor, 1);
|
|
10570
|
+
const perPage = params.limit || 10;
|
|
10571
|
+
const sort = params.sort || { field: "updated", order: "desc" };
|
|
10572
|
+
const orderBy = sort.field === "created" ? "created_at" : "updated_at";
|
|
10573
|
+
let gitlabState;
|
|
10574
|
+
if (params.filters?.state === "open") {
|
|
10575
|
+
gitlabState = "opened";
|
|
10576
|
+
} else if (params.filters?.state === "closed") {
|
|
10577
|
+
gitlabState = "closed";
|
|
10578
|
+
} else {
|
|
10579
|
+
gitlabState = "all";
|
|
10580
|
+
}
|
|
10581
|
+
const searchResult = await searchGitlabMergeRequests({
|
|
10582
|
+
repoUrl: this.url,
|
|
10583
|
+
accessToken: this.accessToken,
|
|
10584
|
+
state: gitlabState,
|
|
10585
|
+
updatedAfter: params.filters?.updatedAfter,
|
|
10586
|
+
orderBy,
|
|
10587
|
+
sort: sort.order,
|
|
10588
|
+
perPage,
|
|
10589
|
+
page
|
|
10590
|
+
});
|
|
10591
|
+
const results = searchResult.items.map((mr) => {
|
|
10592
|
+
let status = "open";
|
|
10593
|
+
if (mr.state === "merged") {
|
|
10594
|
+
status = "merged";
|
|
10595
|
+
} else if (mr.state === "closed") {
|
|
10596
|
+
status = "closed";
|
|
10597
|
+
}
|
|
10598
|
+
return {
|
|
10599
|
+
submitRequestId: String(mr.iid),
|
|
10600
|
+
submitRequestNumber: mr.iid,
|
|
10601
|
+
title: mr.title,
|
|
10602
|
+
status,
|
|
10603
|
+
sourceBranch: mr.sourceBranch,
|
|
10604
|
+
targetBranch: mr.targetBranch,
|
|
10605
|
+
authorName: mr.authorUsername,
|
|
10606
|
+
authorEmail: void 0,
|
|
10607
|
+
createdAt: new Date(mr.createdAt),
|
|
10608
|
+
updatedAt: new Date(mr.updatedAt),
|
|
10609
|
+
description: mr.description || void 0,
|
|
10610
|
+
tickets: [],
|
|
10611
|
+
changedLines: { added: 0, removed: 0 }
|
|
10612
|
+
};
|
|
10613
|
+
});
|
|
10614
|
+
const MAX_TOTAL_RESULTS = 1024;
|
|
10615
|
+
const totalFetchedSoFar = page * perPage;
|
|
10616
|
+
const reachedLimit = totalFetchedSoFar >= MAX_TOTAL_RESULTS;
|
|
10617
|
+
if (reachedLimit && searchResult.hasMore) {
|
|
10618
|
+
contextLogger.warn(
|
|
10619
|
+
"[searchSubmitRequests] Hit limit of merge requests for GitLab repo",
|
|
10620
|
+
{
|
|
10621
|
+
limit: MAX_TOTAL_RESULTS
|
|
10622
|
+
}
|
|
10623
|
+
);
|
|
10624
|
+
}
|
|
10625
|
+
return {
|
|
10626
|
+
results,
|
|
10627
|
+
nextCursor: searchResult.hasMore && !reachedLimit ? String(page + 1) : void 0,
|
|
10628
|
+
hasMore: searchResult.hasMore && !reachedLimit
|
|
10629
|
+
};
|
|
10688
10630
|
}
|
|
10689
|
-
async
|
|
10690
|
-
|
|
10631
|
+
async getPrCommitsBatch(_repoUrl, prNumbers) {
|
|
10632
|
+
this._validateAccessTokenAndUrl();
|
|
10633
|
+
return getGitlabMrCommitsBatch({
|
|
10634
|
+
repoUrl: this.url,
|
|
10635
|
+
accessToken: this.accessToken,
|
|
10636
|
+
mrNumbers: prNumbers
|
|
10637
|
+
});
|
|
10691
10638
|
}
|
|
10692
|
-
async
|
|
10693
|
-
|
|
10639
|
+
async getPrDataBatch(_repoUrl, prNumbers) {
|
|
10640
|
+
this._validateAccessTokenAndUrl();
|
|
10641
|
+
return getGitlabMrDataBatch({
|
|
10642
|
+
repoUrl: this.url,
|
|
10643
|
+
accessToken: this.accessToken,
|
|
10644
|
+
mrNumbers: prNumbers
|
|
10645
|
+
});
|
|
10694
10646
|
}
|
|
10695
10647
|
async searchRepos(_params) {
|
|
10696
10648
|
throw new Error("searchRepos not implemented for GitLab");
|
|
10697
10649
|
}
|
|
10698
|
-
|
|
10699
|
-
|
|
10700
|
-
|
|
10701
|
-
|
|
10650
|
+
async getPullRequestMetrics(prNumber) {
|
|
10651
|
+
this._validateAccessTokenAndUrl();
|
|
10652
|
+
const metrics = await getGitlabMergeRequestMetrics({
|
|
10653
|
+
url: this.url,
|
|
10654
|
+
prNumber,
|
|
10655
|
+
accessToken: this.accessToken
|
|
10656
|
+
});
|
|
10657
|
+
let prStatus;
|
|
10658
|
+
switch (metrics.state) {
|
|
10659
|
+
case "merged":
|
|
10660
|
+
prStatus = "MERGED" /* Merged */;
|
|
10661
|
+
break;
|
|
10662
|
+
case "closed":
|
|
10663
|
+
prStatus = "CLOSED" /* Closed */;
|
|
10664
|
+
break;
|
|
10665
|
+
default:
|
|
10666
|
+
prStatus = metrics.isDraft ? "DRAFT" /* Draft */ : "ACTIVE" /* Active */;
|
|
10667
|
+
}
|
|
10668
|
+
return {
|
|
10669
|
+
prId: String(prNumber),
|
|
10670
|
+
repositoryUrl: this.url,
|
|
10671
|
+
prCreatedAt: new Date(metrics.createdAt),
|
|
10672
|
+
prMergedAt: metrics.mergedAt ? new Date(metrics.mergedAt) : null,
|
|
10673
|
+
firstCommitDate: metrics.firstCommitDate ? new Date(metrics.firstCommitDate) : null,
|
|
10674
|
+
linesAdded: metrics.linesAdded,
|
|
10675
|
+
commitsCount: metrics.commitsCount,
|
|
10676
|
+
commitShas: metrics.commitShas,
|
|
10677
|
+
prStatus,
|
|
10678
|
+
commentIds: metrics.commentIds
|
|
10679
|
+
};
|
|
10680
|
+
}
|
|
10681
|
+
async getRecentCommits(since) {
|
|
10682
|
+
this._validateAccessTokenAndUrl();
|
|
10683
|
+
const commits = await getGitlabRecentCommits({
|
|
10684
|
+
repoUrl: this.url,
|
|
10685
|
+
accessToken: this.accessToken,
|
|
10686
|
+
since
|
|
10687
|
+
});
|
|
10688
|
+
return { data: commits };
|
|
10689
|
+
}
|
|
10690
|
+
async getRateLimitStatus() {
|
|
10691
|
+
this._validateAccessTokenAndUrl();
|
|
10692
|
+
return getGitlabRateLimitStatus({
|
|
10693
|
+
repoUrl: this.url,
|
|
10694
|
+
accessToken: this.accessToken
|
|
10695
|
+
});
|
|
10696
|
+
}
|
|
10697
|
+
/**
|
|
10698
|
+
* Extract Linear ticket links from pre-fetched comments (pure function, no API calls).
|
|
10699
|
+
* Linear bot uses the same comment format on GitLab as on GitHub.
|
|
10700
|
+
* Bot username may be 'linear' or 'linear[bot]' on GitLab.
|
|
10701
|
+
*/
|
|
10702
|
+
extractLinearTicketsFromComments(comments) {
|
|
10703
|
+
const tickets = [];
|
|
10704
|
+
const seen = /* @__PURE__ */ new Set();
|
|
10705
|
+
for (const comment of comments) {
|
|
10706
|
+
const authorLogin = comment.author?.login?.toLowerCase() || "";
|
|
10707
|
+
const isLinearBot = authorLogin === "linear" || authorLogin === "linear[bot]" || comment.author?.type === "Bot" && authorLogin.includes("linear");
|
|
10708
|
+
if (isLinearBot) {
|
|
10709
|
+
tickets.push(...extractLinearTicketsFromBody(comment.body || "", seen));
|
|
10710
|
+
}
|
|
10711
|
+
}
|
|
10712
|
+
return tickets;
|
|
10702
10713
|
}
|
|
10703
10714
|
};
|
|
10704
10715
|
|
|
@@ -10757,10 +10768,6 @@ var StubSCMLib = class extends SCMLib {
|
|
|
10757
10768
|
console.warn("getUserHasAccessToRepo() returning false");
|
|
10758
10769
|
return false;
|
|
10759
10770
|
}
|
|
10760
|
-
async getRepoBlameRanges(_ref, _path) {
|
|
10761
|
-
console.warn("getRepoBlameRanges() returning empty array");
|
|
10762
|
-
return [];
|
|
10763
|
-
}
|
|
10764
10771
|
async getReferenceData(_ref) {
|
|
10765
10772
|
console.warn("getReferenceData() returning null/empty defaults");
|
|
10766
10773
|
return {
|
|
@@ -10825,14 +10832,18 @@ var StubSCMLib = class extends SCMLib {
|
|
|
10825
10832
|
diffLines: []
|
|
10826
10833
|
};
|
|
10827
10834
|
}
|
|
10828
|
-
async getSubmitRequests(_repoUrl) {
|
|
10829
|
-
console.warn("getSubmitRequests() returning empty array");
|
|
10830
|
-
return [];
|
|
10831
|
-
}
|
|
10832
10835
|
async getPullRequestMetrics(_prNumber) {
|
|
10833
10836
|
console.warn("getPullRequestMetrics() returning empty object");
|
|
10834
10837
|
throw new Error("getPullRequestMetrics() not implemented");
|
|
10835
10838
|
}
|
|
10839
|
+
async getRecentCommits(_since) {
|
|
10840
|
+
console.warn("getRecentCommits() returning empty array");
|
|
10841
|
+
return { data: [] };
|
|
10842
|
+
}
|
|
10843
|
+
async getRateLimitStatus() {
|
|
10844
|
+
console.warn("getRateLimitStatus() returning null");
|
|
10845
|
+
return null;
|
|
10846
|
+
}
|
|
10836
10847
|
};
|
|
10837
10848
|
|
|
10838
10849
|
// src/features/analysis/scm/scmFactory.ts
|
|
@@ -12853,7 +12864,7 @@ import Debug10 from "debug";
|
|
|
12853
12864
|
|
|
12854
12865
|
// src/features/analysis/add_fix_comments_for_pr/utils/utils.ts
|
|
12855
12866
|
import Debug9 from "debug";
|
|
12856
|
-
import
|
|
12867
|
+
import parseDiff2 from "parse-diff";
|
|
12857
12868
|
import { z as z27 } from "zod";
|
|
12858
12869
|
|
|
12859
12870
|
// src/features/analysis/utils/by_key.ts
|
|
@@ -13226,7 +13237,7 @@ Refresh the page in order to see the changes.`,
|
|
|
13226
13237
|
}
|
|
13227
13238
|
async function getRelevantVulenrabilitiesFromDiff(params) {
|
|
13228
13239
|
const { gqlClient, diff, vulnerabilityReportId } = params;
|
|
13229
|
-
const parsedDiff =
|
|
13240
|
+
const parsedDiff = parseDiff2(diff);
|
|
13230
13241
|
const fileHunks = parsedDiff.map((file) => {
|
|
13231
13242
|
const fileNumbers = file.chunks.flatMap((chunk) => chunk.changes).filter((change) => change.type === "add").map((_change) => {
|
|
13232
13243
|
const change = _change;
|
|
@@ -15327,7 +15338,7 @@ async function getRepositoryUrl() {
|
|
|
15327
15338
|
}
|
|
15328
15339
|
const remoteUrl = await gitService.getRemoteUrl();
|
|
15329
15340
|
const parsed = parseScmURL(remoteUrl);
|
|
15330
|
-
return parsed?.scmType === "GitHub" /* GitHub */ ? remoteUrl : null;
|
|
15341
|
+
return parsed?.scmType === "GitHub" /* GitHub */ || parsed?.scmType === "GitLab" /* GitLab */ ? remoteUrl : null;
|
|
15331
15342
|
} catch {
|
|
15332
15343
|
return null;
|
|
15333
15344
|
}
|
|
@@ -21493,7 +21504,7 @@ import {
|
|
|
21493
21504
|
writeFileSync
|
|
21494
21505
|
} from "fs";
|
|
21495
21506
|
import fs21 from "fs/promises";
|
|
21496
|
-
import
|
|
21507
|
+
import parseDiff3 from "parse-diff";
|
|
21497
21508
|
import path20 from "path";
|
|
21498
21509
|
var PatchApplicationService = class {
|
|
21499
21510
|
/**
|
|
@@ -22177,7 +22188,7 @@ var PatchApplicationService = class {
|
|
|
22177
22188
|
fixId,
|
|
22178
22189
|
scanContext
|
|
22179
22190
|
}) {
|
|
22180
|
-
const parsedPatch =
|
|
22191
|
+
const parsedPatch = parseDiff3(patch);
|
|
22181
22192
|
if (!parsedPatch || parsedPatch.length === 0) {
|
|
22182
22193
|
throw new Error("Failed to parse patch - no changes found");
|
|
22183
22194
|
}
|