module dub.internal.git;
import dub.internal.vibecompat.core.file;
import dub.internal.vibecompat.core.log;
import std.file;
import std.string;
version (Windows)
{
import dub.internal.vibecompat.data.json;
string determineVersionWithGit(NativePath path)
{
// On Windows, which is slow at running external processes,
// cache the version numbers that are determined using
// git to speed up the initialization phase.
import dub.internal.utils : jsonFromFile;
// quickly determine head commit without invoking git
string head_commit;
auto hpath = (path ~ ".git/HEAD").toNativeString();
if (exists(hpath)) {
auto head_ref = readText(hpath).strip();
if (head_ref.startsWith("ref: ")) {
auto rpath = (path ~ (".git/"~head_ref[5 .. $])).toNativeString();
if (exists(rpath))
head_commit = readText(rpath).strip();
}
}
// return the last determined version for that commit
// not that this is not always correct, most notably when
// a tag gets added/removed/changed and changes the outcome
// of the full version detection computation
auto vcachepath = path ~ ".dub/version.json";
if (existsFile(vcachepath)) {
auto ver = jsonFromFile(vcachepath);
if (head_commit == ver["commit"].opt!string)
return ver["version"].get!string;
}
// if no cache file or the HEAD commit changed, perform full detection
auto ret = determineVersionWithGitTool(path);
// update version cache file
if (head_commit.length) {
import dub.internal.utils : atomicWriteJsonFile;
if (!existsFile(path ~".dub")) createDirectory(path ~ ".dub");
atomicWriteJsonFile(vcachepath, Json(["commit": Json(head_commit), "version": Json(ret)]));
}
return ret;
}
}
else
{
string determineVersionWithGit(NativePath path)
{
return determineVersionWithGitTool(path);
}
}
// determines the version of a package that is stored in a Git working copy
// by invoking the "git" executable
private string determineVersionWithGitTool(NativePath path)
{
import dub.semver;
import std.algorithm : canFind;
import std.conv : to;
import std.process;
auto git_dir = path ~ ".git";
if (!existsFile(git_dir) || !isDir(git_dir.toNativeString)) return null;
auto git_dir_param = "--git-dir=" ~ git_dir.toNativeString();
static string exec(scope string[] params...) {
auto ret = executeShell(escapeShellCommand(params));
if (ret.status == 0) return ret.output.strip;
logDebug("'%s' failed with exit code %s: %s", params.join(" "), ret.status, ret.output.strip);
return null;
}
auto tag = exec("git", git_dir_param, "describe", "--long", "--tags");
if (tag !is null) {
auto parts = tag.split("-");
auto commit = parts[$-1];
auto num = parts[$-2].to!int;
tag = parts[0 .. $-2].join("-");
if (tag.startsWith("v") && isValidVersion(tag[1 .. $])) {
if (num == 0) return tag[1 .. $];
else if (tag.canFind("+")) return format("%s.commit.%s.%s", tag[1 .. $], num, commit);
else return format("%s+commit.%s.%s", tag[1 .. $], num, commit);
}
}
auto branch = exec("git", git_dir_param, "rev-parse", "--abbrev-ref", "HEAD");
if (branch !is null) {
if (branch != "HEAD") return "~" ~ branch;
}
return null;
}