Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ node_modules
protobuf
versions
acmeair-nodejs
packages/dd-trace/src/plugins/util/git-repo-info.js
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we move this to a vendor folder instead and ignore the entire folder?

10 changes: 8 additions & 2 deletions packages/dd-trace/src/plugins/util/ci-app-spec.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
"test.framework",
"test.suite",
"test.name",
"resource.name",
"ci.pipeline.id",
"ci.pipeline.name",
"ci.pipeline.number",
Expand All @@ -17,5 +16,12 @@
"ci.stage.name",
"git.commit.sha",
"git.branch",
"git.tag"
"git.tag",
"git.commit.message",
"git.commit.committer.date",
"git.commit.committer.email",
"git.commit.committer.name",
"git.commit.author.date",
"git.commit.author.email",
"git.commit.author.name"
]
356 changes: 356 additions & 0 deletions packages/dd-trace/src/plugins/util/git-repo-info.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,356 @@
// From https://github.com/rwjblue/git-repo-info/blob/d3ab418ef8b392eabbe911a37871708b15201b70/index.js
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The file should also be added to LICENSE-3rdparty.csv similar to the TDigest entry.

// License MIT, Copyright Robert Jackson and Contributors
'use strict';

var fs = require('fs');
var path = require('path');
var zlib = require('zlib');

var GIT_DIR = '.git';

function changeGitDir(newDirName) {
GIT_DIR = newDirName;
}

function findRepoHandleLinkedWorktree(gitPath) {
var stat = fs.statSync(gitPath);
var root = path.dirname(path.resolve(gitPath));
if (stat.isDirectory()) {
return {
// for the base (non-linked) dir, there is no distinction between where we
// find the HEAD file and where we find the rest of .git
worktreeGitDir: gitPath,
commonGitDir: gitPath,
root: root,
};
} else {
// We have a file that tells us where to find the worktree git dir. Once we
// look there we'll know how to find the common git dir, depending on
// whether it's a linked worktree git dir, or a submodule dir

var linkedGitDir = fs.readFileSync(gitPath).toString();
var absolutePath=path.resolve(path.dirname(gitPath));
var worktreeGitDirUnresolved = /gitdir: (.*)/.exec(linkedGitDir)[1];
var worktreeGitDir = path.resolve(absolutePath,worktreeGitDirUnresolved);
var commonDirPath = path.join(worktreeGitDir, 'commondir');
if (fs.existsSync(commonDirPath)) {
// this directory contains a `commondir` file; we're in a linked worktree

var commonDirRelative = fs.readFileSync(commonDirPath).toString().replace(/\r?\n$/, '');
var commonDir = path.resolve(path.join(worktreeGitDir, commonDirRelative));

return {
worktreeGitDir: worktreeGitDir,
commonGitDir: commonDir,
root: path.dirname(commonDir),
};
} else {
// there is no `commondir` file; we're in a submodule
return {
worktreeGitDir: worktreeGitDir,
commonGitDir: worktreeGitDir,
root: root,
};
}
}
}

function findRepo(startingPath) {
var gitPath, lastPath;
var currentPath = startingPath;

if (!currentPath) { currentPath = process.cwd(); }

do {
gitPath = path.join(currentPath, GIT_DIR);

if (fs.existsSync(gitPath)) {
return findRepoHandleLinkedWorktree(gitPath);
}

lastPath = currentPath;
currentPath = path.resolve(currentPath, '..');
} while (lastPath !== currentPath);

return null;
}

function findPackedTags(gitPath, refPath) {
return getPackedRefsForType(gitPath, refPath, 'tag');
}

function findPackedCommit(gitPath, refPath) {
return getPackedRefsForType(gitPath, refPath, 'commit')[0];
}

function getPackedRefsForType(gitPath, refPath, type) {
var packedRefsFile = getPackedRefsFile(gitPath);
if (packedRefsFile) {
return getLinesForRefPath(packedRefsFile, type, refPath).map(function(shaLine) {
return getShaBasedOnType(type, shaLine);
});
}
return [];
}

function getPackedRefsFile(gitPath) {
var packedRefsFilePath = path.join(gitPath, 'packed-refs');
return fs.existsSync(packedRefsFilePath) ? fs.readFileSync(packedRefsFilePath, { encoding: 'utf8' }) : false;
}

function getLinesForRefPath(packedRefsFile, type, refPath) {
return packedRefsFile.split(/\r?\n/).reduce(function(acc, line, idx, arr) {
var targetLine = line.indexOf('^') > -1 ? arr[idx-1] : line;
return doesLineMatchRefPath(type, line, refPath) ? acc.concat(targetLine) : acc;
}, []);
}

function doesLineMatchRefPath(type, line, refPath) {
var refPrefix = type === 'tag' ? 'refs/tags' : 'refs/heads';
return (line.indexOf(refPrefix) > -1 || line.indexOf('^') > -1) && line.indexOf(refPath) > -1;
}

function getShaBasedOnType(type, shaLine) {
var shaResult = '';
if (type === 'tag') {
shaResult = shaLine.split('tags/')[1];
} else if (type === 'commit') {
shaResult = shaLine.split(' ')[0];
}

return shaResult;
}

function commitForTag(gitPath, tag) {
var tagPath = path.join(gitPath, 'refs', 'tags', tag);
var taggedObject = fs.readFileSync(tagPath, { encoding: 'utf8' }).trim();
var objectPath = path.join(gitPath, 'objects', taggedObject.slice(0, 2), taggedObject.slice(2));

if (!zlib.inflateSync || !fs.existsSync(objectPath)) {
// we cannot support annotated tags on node v0.10 because
// zlib does not allow sync access
return taggedObject;
}

var objectContents = zlib.inflateSync(fs.readFileSync(objectPath)).toString();

// 'tag 172\u0000object c1ee41c325d54f410b133e0018c7a6b1316f6cda\ntype commit\ntag awesome-tag\ntagger Robert Jackson
// <[email protected]> 1429100021 -0400\n\nI am making an annotated tag.\n'
if (objectContents.slice(0,3) === 'tag') {
var sections = objectContents.split(/\0|\r?\n/);
var sha = sections[1].slice(7);

return sha;
} else {
// this will return the tag for lightweight tags
return taggedObject;
}
}

function findTag(gitPath, sha) {
var tags = findPackedTags(gitPath, sha)
.concat(findUnpackedTags(gitPath, sha));
tags.sort();
return tags.length ? tags[0] : false;
}

var LAST_TAG_CACHE = {};

function findLastTagCached(gitPath, sha) {
if(!LAST_TAG_CACHE[gitPath]) {
LAST_TAG_CACHE[gitPath] = {};
}

if(!LAST_TAG_CACHE[gitPath][sha]) {
LAST_TAG_CACHE[gitPath][sha] = findLastTag(gitPath, sha);
}

return LAST_TAG_CACHE[gitPath][sha];
}

function findLastTag(gitPath, sha) {
var queue = [{ sha: sha, depth: 0 }];
var seenCommits = new Set();
while (queue.length) {
var element = queue.shift();
if (seenCommits.has(element.sha)) {
continue;
}
seenCommits.add(element.sha);
var tag = findTag(gitPath, element.sha);
if (tag) {
return {
tag: tag,
commitsSinceLastTag: element.depth
};
}
var commitData = getCommitData(gitPath, sha);
if (commitData && commitData.parents) {
for (var i = 0; i < commitData.parents.length; i++) {
queue.push({ sha: commitData.parents[i], depth: element.depth + 1 });
}
}
}
return { tag: null, commitsSinceLastTag: Infinity };
}

function findUnpackedTags(gitPath, sha) {
var unpackedTags = [];
var tags = findLooseRefsForType(gitPath, 'tags');
for (var i = 0, l = tags.length; i < l; i++) {
var commitAtTag = commitForTag(gitPath, tags[i]);
if (commitAtTag === sha) {
unpackedTags.push(tags[i]);
}
}
return unpackedTags;
}

function findLooseRefsForType(gitPath, type) {
var refsPath = path.join(gitPath, 'refs', type);
return fs.existsSync(refsPath) ? fs.readdirSync(refsPath) : [];
}

module.exports = function(gitPath) {
var gitPathInfo = findRepo(gitPath);

var result = {
sha: null,
abbreviatedSha: null,
branch: null,
tag: null,
committer: null,
committerDate: null,
author: null,
authorDate: null,
commitMessage: null,
root: null,
commonGitDir: null,
worktreeGitDir: null,
lastTag: null,
commitsSinceLastTag: 0,
};

if (!gitPathInfo) { return result; }

try {
result.root = gitPathInfo.root;
result.commonGitDir = gitPathInfo.commonGitDir;
result.worktreeGitDir = gitPathInfo.worktreeGitDir;

var headFilePath = path.join(gitPathInfo.worktreeGitDir, 'HEAD');

if (fs.existsSync(headFilePath)) {
var headFile = fs.readFileSync(headFilePath, {encoding: 'utf8'});
var branchName = headFile.split('/').slice(2).join('/').trim();
if (!branchName) {
branchName = headFile.split('/').slice(-1)[0].trim();
}
var refPath = headFile.split(' ')[1];

// Find branch and SHA
if (refPath) {
refPath = refPath.trim();
var branchPath = path.join(gitPathInfo.commonGitDir, refPath);

result.branch = branchName;
if (fs.existsSync(branchPath)) {
result.sha = fs.readFileSync(branchPath, { encoding: 'utf8' }).trim();
} else {
result.sha = findPackedCommit(gitPathInfo.commonGitDir, refPath);
}
} else {
result.sha = branchName;
}

result.abbreviatedSha = result.sha.slice(0,10);

// Find commit data
var commitData = getCommitData(gitPathInfo.commonGitDir, result.sha);
if (commitData) {
result = Object.keys(commitData).reduce(function(r, key) {
result[key] = commitData[key];
return result;
}, result);
}

// Find tag
var tag = findTag(gitPathInfo.commonGitDir, result.sha);
if (tag) {
result.tag = tag;
}

var lastTagInfo = findLastTagCached(gitPathInfo.commonGitDir, result.sha);
result.lastTag = lastTagInfo.tag;
result.commitsSinceLastTag = lastTagInfo.commitsSinceLastTag;
}
} catch (e) {
if (!module.exports._suppressErrors) {
throw e; // helps with testing and scenarios where we do not expect errors
} else {
// eat the error
}
}

return result;
};

module.exports._suppressErrors = true;
module.exports._findRepo = findRepo;
module.exports._changeGitDir = changeGitDir;

function getCommitData(gitPath, sha) {
var objectPath = path.join(gitPath, 'objects', sha.slice(0, 2), sha.slice(2));

if (zlib.inflateSync && fs.existsSync(objectPath)) {
var objectContents = zlib.inflateSync(fs.readFileSync(objectPath)).toString();

return objectContents.split(/\0|\r?\n/)
.filter(function(item) {
return !!item;
})
.reduce(function(data, section) {
var part = section.slice(0, section.indexOf(' ')).trim();

switch(part) {
case 'commit':
case 'tag':
case 'object':
case 'type':
case 'tree':
//ignore these for now
break;
case 'author':
case 'committer':
var parts = section.match(/^(?:author|committer)\s(.+)\s(\d+\s(?:\+|\-)\d{4})$/);

if (parts) {
data[part] = parts[1];
data[part + 'Date'] = parseDate(parts[2]);
}
break;
case 'parent':
if (!data.parents) {
data.parents = [];
}
data.parents.push(section.split(' ')[1]);
break;
default:
// Added compatibility with multiline commit message
// https://github.com/rwjblue/git-repo-info/pull/60
if (!data.commitMessage) {
data.commitMessage = section;
} else {
data.commitMessage = `${data.commitMessage}\n${section}`;
}
}

return data;
}, {});
}
}

function parseDate(d) {
var epoch = d.split(' ')[0];
return new Date(epoch * 1000).toISOString();
}
Loading