From 405d54b38c555a52abbac512b494f62c10d5ad8a Mon Sep 17 00:00:00 2001 From: Wout Mertens Date: Wed, 10 Feb 2021 09:45:41 +0100 Subject: [PATCH] nodegit: gitlog missing file line diffs --- components/commitdiff/commitdiff.js | 2 +- source/config.js | 2 +- source/git-api.js | 38 +++++++++++++++-------------- source/git-parser.js | 4 +-- source/git-promise.js | 1 + source/nodegit.js | 31 ++++++++++++++++++++--- 6 files changed, 52 insertions(+), 26 deletions(-) diff --git a/components/commitdiff/commitdiff.js b/components/commitdiff/commitdiff.js index 9a3b49eaa..3d1e7542b 100644 --- a/components/commitdiff/commitdiff.js +++ b/components/commitdiff/commitdiff.js @@ -24,7 +24,7 @@ class CommitDiff { loadFileLineDiffs(args) { const tempCommitLineDiffs = []; const lineDiffLength = this.commitLineDiffs().length; - + if (!args.fileLineDiffs) return; args.fileLineDiffs .slice(lineDiffLength === 0 ? 0 : lineDiffLength + 1, this.maxNumberOfFilesShown) .forEach((fileLineDiff) => { diff --git a/source/config.js b/source/config.js index 7c763599e..52f6d00d7 100644 --- a/source/config.js +++ b/source/config.js @@ -129,7 +129,7 @@ const defaultConfig = { maxActiveBranchSearchIteration: -1, // number of nodes to load for each git.log call - numberOfNodesPerLoad: 25, + numberOfNodesPerLoad: 100, // Specifies a custom git merge tool to use when resolving conflicts. Your git configuration must be set up to use this! // A true value will use the default tool while a string value will use the tool of that specified name. diff --git a/source/git-api.js b/source/git-api.js index 07c458882..4bf5e6338 100644 --- a/source/git-api.js +++ b/source/git-api.js @@ -180,13 +180,15 @@ exports.registerApi = (env) => { ); }; - const w = (fn) => (req, res) => + /** @typedef {{ repo?: import('./nodegit').NGWrap; body: any; params: any; query: any }} Req */ + + const w = (/** @type {(req:Req, res:any) => any} */ fn) => (req, res) => new Promise((resolve) => resolve(fn(req, res))).catch((err) => { winston.warn('Responding with ERROR: ', err); res.status(500).json(err); }); - const jw = (fn) => (req, res) => + const jw = (/** @type {(req:Req, res:any) => any} */ fn) => (req, res) => jsonResultOrFailProm(res, new Promise((resolve) => resolve(fn(req)))); const credentialsOption = (socketId, remote) => { @@ -421,26 +423,26 @@ exports.registerApi = (env) => { `${exports.pathPrefix}/gitlog`, ensureAuthenticated, ensurePathExists, - jw((req) => { + jw(async (req) => { const limit = getNumber(req.query.limit, config.numberOfNodesPerLoad || 25); // TODO if skip is 0, return all references (max 20 most recent) and 100 commits of current + 10 of each reference // TODO ask for more not via skip but via oid const skip = getNumber(req.query.skip, 0); - return gitPromise - .log(req.query.path, limit, skip, config.maxActiveBranchSearchIteration) - .catch((err) => { - if (err.stderr && err.stderr.indexOf("fatal: bad default revision 'HEAD'") == 0) { - return { limit: limit, skip: skip, nodes: [] }; - } else if ( - /fatal: your current branch '.+' does not have any commits yet.*/.test(err.stderr) - ) { - return { limit: limit, skip: skip, nodes: [] }; - } else if (err.stderr && err.stderr.indexOf('fatal: Not a git repository') == 0) { - return { limit: limit, skip: skip, nodes: [] }; - } else { - throw err; - } - }); + const nodes = await req.repo.log(limit, skip, config.maxActiveBranchSearchIteration); + // .catch((err) => { + // if (err.stderr && err.stderr.indexOf("fatal: bad default revision 'HEAD'") == 0) { + // return { limit: limit, skip: skip, nodes: [] }; + // } else if ( + // /fatal: your current branch '.+' does not have any commits yet.*/.test(err.stderr) + // ) { + // return { limit: limit, skip: skip, nodes: [] }; + // } else if (err.stderr && err.stderr.indexOf('fatal: Not a git repository') == 0) { + // return { limit: limit, skip: skip, nodes: [] }; + // } else { + // throw err; + // } + // }); + return { limit, skip, nodes, isHeadExist: true }; }) ); diff --git a/source/git-parser.js b/source/git-parser.js index 259ec5057..d60bd2925 100644 --- a/source/git-parser.js +++ b/source/git-parser.js @@ -18,7 +18,7 @@ const _ = require('lodash'); * sha1: Hash; * parents: Hash[]; * refs: Ref[]; - * isHead: boolean; + * isHead?: boolean; * message: string; * authorName?: string; * authorEmail?: string; @@ -49,7 +49,7 @@ const _ = require('lodash'); * additions?: number; * deletions?: number; * }} FileStatus - * @typedef {{ name: string; current?: boolean; sha1?: Hash }} Branch + * @typedef {{ name: string; current?: boolean; sha1?: Hash; remote?: string }} Branch * @typedef {{ * gitRootPath: string; * type: 'inited' | 'uninited' | 'bare' | 'no-such-path'; diff --git a/source/git-promise.js b/source/git-promise.js index 7539d2eba..dec27ebf2 100644 --- a/source/git-promise.js +++ b/source/git-promise.js @@ -50,6 +50,7 @@ const gitExecutorProm = (args, retryCount) => { return rateLimiter() .then(() => { return new Promise((resolve, reject) => { + console.log(`git executing: ${args.repoPath} ${args.commands.join(' ')}`); if (config.logGitCommands) winston.info(`git executing: ${args.repoPath} ${args.commands.join(' ')}`); let rejectedError = null; diff --git a/source/nodegit.js b/source/nodegit.js index b7202e61f..1a41e87c8 100644 --- a/source/nodegit.js +++ b/source/nodegit.js @@ -50,20 +50,25 @@ const normalizeError = (err) => { const splitMail = (signature) => { if (!signature) return []; - const match = /^([^<])*]*)>?$/.exec(signature); + const match = /^([^<]*)]*)>?$/.exec(signature); return match ? [match[1].trim(), match[2].trim()] : []; }; -const formatCommit = /** @type {nodegit.Commit} */ (c) => { +/** + * @param {nodegit.Commit} c + * @param {nodegit.Oid} hId + */ +const formatCommit = (c, hId) => { const [authorName, authorEmail] = splitMail(c.author().toString()); const [committerName, committerEmail] = splitMail(c.author().toString()); /** @type {gitParser.Commit} */ const out = { sha1: c.sha(), parents: c.parents().map(String), - refs: [], // TODO cached refs on client, don't include here - isHead: false, // TODO cached refs on client, don't include here + refs: hId && hId.equal(c.id()) ? ['HEAD'] : [], // TODO cached refs on client, don't include here message: c.message(), + // TODO find out how to extract from rawHeader() + authorDate: c.date().toJSON(), commitDate: c.date().toJSON(), authorName, authorEmail, @@ -212,6 +217,23 @@ class NGWrap { }; return out; } + + async log(limit = 500, skip, maxIter) { + const walker = this.r.createRevWalk(); + walker.sorting(nodegit.Revwalk.SORT.TIME); + const head = await this.r.getHeadCommit(); + if (skip) await walker.fastWalk(skip).catch(normalizeError); + else { + if (head) walker.push(head.id()); + walker.pushGlob('*'); + } + const commits = await walker.getCommits(limit).catch(normalizeError); + // TODO detect head client-side + const headId = head && head.id(); + const result = commits.map((c) => formatCommit(c, headId)); + // TODO keep the walker as a cursor, time out and recreate if needed + return result; + } } const repoPs = {}; @@ -253,6 +275,7 @@ const initGit = (path, isBare) => nodegit.Repository.init(path, isBare ? 1 : 0).catch(normalizeError); module.exports = { + NGWrap, getRepo, initGit, quickStatus,