import test from "ava"; import { temporaryDirectory } from "tempy"; import { addNote, fetch, fetchNotes, getBranches, getGitHead, getNote, getTagHead, getTags, isBranchUpToDate, isGitRepo, isRefExists, push, repoUrl, tag, verifyTagName, } from "../lib/git.js"; import { gitAddConfig, gitAddNote, gitCheckout, gitCommits, gitCommitTag, gitDetachedHead, gitDetachedHeadFromBranch, gitFetch, gitGetCommits, gitGetNote, gitPush, gitRemoteTagHead, gitRepo, gitShallowClone, gitTagVersion, initGit, } from "./helpers/git-utils.js"; test("Get the last commit sha", async (t) => { // Create a git repository, set the current working directory at the root of the repo const { cwd } = await gitRepo(); // Add commits to the master branch const commits = await gitCommits(["First"], { cwd }); const result = await getGitHead({ cwd }); t.is(result, commits[0].hash); }); test("Throw error if the last commit sha cannot be found", async (t) => { // Create a git repository, set the current working directory at the root of the repo const { cwd } = await gitRepo(); await t.throwsAsync(getGitHead({ cwd })); }); test("Unshallow and fetch repository", async (t) => { // Create a git repository, set the current working directory at the root of the repo let { cwd, repositoryUrl } = await gitRepo(); // Add commits to the master branch await gitCommits(["First", "Second"], { cwd }); // Create a shallow clone with only 1 commit cwd = await gitShallowClone(repositoryUrl); // Verify the shallow clone contains only one commit t.is((await gitGetCommits(undefined, { cwd })).length, 1); await fetch(repositoryUrl, "master", "master", { cwd }); // Verify the shallow clone contains all the commits t.is((await gitGetCommits(undefined, { cwd })).length, 2); }); test("Do not throw error when unshallow a complete repository", async (t) => { // Create a git repository, set the current working directory at the root of the repo const { cwd, repositoryUrl } = await gitRepo(true); await gitCommits(["First"], { cwd }); await gitPush(repositoryUrl, "master", { cwd }); await gitCheckout("second-branch", true, { cwd }); await gitCommits(["Second"], { cwd }); await gitPush(repositoryUrl, "second-branch", { cwd }); await t.notThrowsAsync(fetch(repositoryUrl, "master", "master", { cwd })); await t.notThrowsAsync(fetch(repositoryUrl, "second-branch", "master", { cwd })); }); test("Fetch all tags on a detached head repository", async (t) => { let { cwd, repositoryUrl } = await gitRepo(); await gitCommits(["First"], { cwd }); await gitTagVersion("v1.0.0", undefined, { cwd }); await gitCommits(["Second"], { cwd }); await gitTagVersion("v1.0.1", undefined, { cwd }); const [commit] = await gitCommits(["Third"], { cwd }); await gitTagVersion("v1.1.0", undefined, { cwd }); await gitPush(repositoryUrl, "master", { cwd }); cwd = await gitDetachedHead(repositoryUrl, commit.hash); await fetch(repositoryUrl, "master", "master", { cwd }); t.deepEqual((await getTags("master", { cwd })).sort(), ["v1.0.0", "v1.0.1", "v1.1.0"].sort()); }); test("Fetch all tags on a repository with a detached head from branch (CircleCI)", async (t) => { let { cwd, repositoryUrl } = await gitRepo(); await gitCommits(["First"], { cwd }); await gitTagVersion("v1.0.0", undefined, { cwd }); await gitCommits(["Second"], { cwd }); await gitTagVersion("v1.0.1", undefined, { cwd }); const [commit] = await gitCommits(["Third"], { cwd }); await gitTagVersion("v1.1.0", undefined, { cwd }); await gitPush(repositoryUrl, "master", { cwd }); await gitCheckout("other-branch", true, { cwd }); await gitPush(repositoryUrl, "other-branch", { cwd }); await gitCheckout("master", false, { cwd }); await gitCommits(["Fourth"], { cwd }); await gitTagVersion("v2.0.0", undefined, { cwd }); await gitPush(repositoryUrl, "master", { cwd }); cwd = await gitDetachedHeadFromBranch(repositoryUrl, "other-branch", commit.hash); await fetch(repositoryUrl, "master", "other-branch", { cwd }); await fetch(repositoryUrl, "other-branch", "other-branch", { cwd }); t.deepEqual((await getTags("other-branch", { cwd })).sort(), ["v1.0.0", "v1.0.1", "v1.1.0"].sort()); t.deepEqual((await getTags("master", { cwd })).sort(), ["v1.0.0", "v1.0.1", "v1.1.0", "v2.0.0"].sort()); }); test("Fetch all tags on a detached head repository with outdated cached repo (GitLab CI)", async (t) => { const { cwd, repositoryUrl } = await gitRepo(); await gitCommits(["First"], { cwd }); await gitTagVersion("v1.0.0", undefined, { cwd }); await gitCommits(["Second"], { cwd }); await gitTagVersion("v1.0.1", undefined, { cwd }); let [commit] = await gitCommits(["Third"], { cwd }); await gitTagVersion("v1.1.0", undefined, { cwd }); await gitPush(repositoryUrl, "master", { cwd }); // Create a clone (as first CI run would) const cloneCwd = await gitShallowClone(repositoryUrl); await gitFetch(repositoryUrl, { cwd: cloneCwd }); await gitCheckout(commit.hash, false, { cwd: cloneCwd }); // Push tag to remote [commit] = await gitCommits(["Fourth"], { cwd }); await gitTagVersion("v1.2.0", undefined, { cwd }); await gitPush(repositoryUrl, "master", { cwd }); // Fetch on the cached repo and make detached head, leaving master outdated await fetch(repositoryUrl, "master", "master", { cwd: cloneCwd }); await gitCheckout(commit.hash, false, { cwd: cloneCwd }); t.deepEqual((await getTags("master", { cwd: cloneCwd })).sort(), ["v1.0.0", "v1.0.1", "v1.1.0", "v1.2.0"].sort()); }); test("Verify if a branch exists", async (t) => { // Create a git repository, set the current working directory at the root of the repo const { cwd } = await gitRepo(); // Add commits to the master branch await gitCommits(["First"], { cwd }); // Create the new branch 'other-branch' from master await gitCheckout("other-branch", true, { cwd }); // Add commits to the 'other-branch' branch await gitCommits(["Second"], { cwd }); t.true(await isRefExists("master", { cwd })); t.true(await isRefExists("other-branch", { cwd })); t.falsy(await isRefExists("next", { cwd })); }); test("Get all branches", async (t) => { const { cwd, repositoryUrl } = await gitRepo(true); await gitCommits(["First"], { cwd }); await gitPush(repositoryUrl, "master", { cwd }); await gitCheckout("second-branch", true, { cwd }); await gitCommits(["Second"], { cwd }); await gitPush(repositoryUrl, "second-branch", { cwd }); await gitCheckout("third-branch", true, { cwd }); await gitCommits(["Third"], { cwd }); await gitPush(repositoryUrl, "third-branch", { cwd }); t.deepEqual((await getBranches(repositoryUrl, { cwd })).sort(), ["master", "second-branch", "third-branch"].sort()); }); test("Return empty array if there are no branches", async (t) => { const { cwd, repositoryUrl } = await initGit(true); t.deepEqual(await getBranches(repositoryUrl, { cwd }), []); }); test("Get the commit sha for a given tag", async (t) => { // Create a git repository, set the current working directory at the root of the repo const { cwd } = await gitRepo(); // Add commits to the master branch const commits = await gitCommits(["First"], { cwd }); // Create the tag corresponding to version 1.0.0 await gitTagVersion("v1.0.0", undefined, { cwd }); t.is(await getTagHead("v1.0.0", { cwd }), commits[0].hash); }); test("Return git remote repository url from config", async (t) => { // Create a git repository, set the current working directory at the root of the repo const { cwd } = await gitRepo(); // Add remote.origin.url config await gitAddConfig("remote.origin.url", "git@hostname.com:owner/package.git", { cwd }); t.is(await repoUrl({ cwd }), "git@hostname.com:owner/package.git"); }); test("Return git remote repository url set while cloning", async (t) => { // Create a git repository, set the current working directory at the root of the repo let { cwd, repositoryUrl } = await gitRepo(); await gitCommits(["First"], { cwd }); // Create a clone cwd = await gitShallowClone(repositoryUrl); t.is(await repoUrl({ cwd }), repositoryUrl); }); test("Return falsy if git repository url is not set", async (t) => { // Create a git repository, set the current working directory at the root of the repo const { cwd } = await gitRepo(); t.falsy(await repoUrl({ cwd })); }); test("Add tag on head commit", async (t) => { // Create a git repository, set the current working directory at the root of the repo const { cwd } = await gitRepo(); const commits = await gitCommits(["Test commit"], { cwd }); await tag("tag_name", "HEAD", { cwd }); await t.is(await gitCommitTag(commits[0].hash, { cwd }), "tag_name"); }); test("Push tag to remote repository", async (t) => { // Create a git repository with a remote, set the current working directory at the root of the repo const { cwd, repositoryUrl } = await gitRepo(true); const commits = await gitCommits(["Test commit"], { cwd }); await tag("tag_name", "HEAD", { cwd }); await push(repositoryUrl, { cwd }); t.is(await gitRemoteTagHead(repositoryUrl, "tag_name", { cwd }), commits[0].hash); }); test("Push tag to remote repository with remote branch ahead", async (t) => { const { cwd, repositoryUrl } = await gitRepo(true); const commits = await gitCommits(["First"], { cwd }); await gitPush(repositoryUrl, "master", { cwd }); const temporaryRepo = await gitShallowClone(repositoryUrl); await gitCommits(["Second"], { cwd: temporaryRepo }); await gitPush("origin", "master", { cwd: temporaryRepo }); await tag("tag_name", "HEAD", { cwd }); await push(repositoryUrl, { cwd }); t.is(await gitRemoteTagHead(repositoryUrl, "tag_name", { cwd }), commits[0].hash); }); test('Return "true" if in a Git repository', async (t) => { // Create a git repository with a remote, set the current working directory at the root of the repo const { cwd } = await gitRepo(true); t.true(await isGitRepo({ cwd })); }); test("Return falsy if not in a Git repository", async (t) => { const cwd = temporaryDirectory(); t.falsy(await isGitRepo({ cwd })); }); test('Return "true" for valid tag names', async (t) => { t.true(await verifyTagName("1.0.0")); t.true(await verifyTagName("v1.0.0")); t.true(await verifyTagName("tag_name")); t.true(await verifyTagName("tag/name")); }); test("Return falsy for invalid tag names", async (t) => { t.falsy(await verifyTagName("?1.0.0")); t.falsy(await verifyTagName("*1.0.0")); t.falsy(await verifyTagName("[1.0.0]")); t.falsy(await verifyTagName("1.0.0..")); }); test("Throws error if obtaining the tags fails", async (t) => { const cwd = temporaryDirectory(); await t.throwsAsync(getTags("master", { cwd })); }); test('Return "true" if repository is up to date', async (t) => { const { cwd, repositoryUrl } = await gitRepo(true); await gitCommits(["First"], { cwd }); await gitPush(repositoryUrl, "master", { cwd }); t.true(await isBranchUpToDate(repositoryUrl, "master", { cwd })); }); test("Return falsy if repository is not up to date", async (t) => { const { cwd, repositoryUrl } = await gitRepo(true); await gitCommits(["First"], { cwd }); await gitCommits(["Second"], { cwd }); await gitPush(repositoryUrl, "master", { cwd }); t.true(await isBranchUpToDate(repositoryUrl, "master", { cwd })); const temporaryRepo = await gitShallowClone(repositoryUrl); await gitCommits(["Third"], { cwd: temporaryRepo }); await gitPush("origin", "master", { cwd: temporaryRepo }); t.falsy(await isBranchUpToDate(repositoryUrl, "master", { cwd })); }); test("Return falsy if detached head repository is not up to date", async (t) => { let { cwd, repositoryUrl } = await gitRepo(); const [commit] = await gitCommits(["First"], { cwd }); await gitCommits(["Second"], { cwd }); await gitPush(repositoryUrl, "master", { cwd }); cwd = await gitDetachedHead(repositoryUrl, commit.hash); await fetch(repositoryUrl, "master", "master", { cwd }); t.falsy(await isBranchUpToDate(repositoryUrl, "master", { cwd })); }); test("Get a commit note", async (t) => { // Create a git repository, set the current working directory at the root of the repo const { cwd } = await gitRepo(); // Add commits to the master branch const commits = await gitCommits(["First"], { cwd }); await gitAddNote(JSON.stringify({ note: "note" }), commits[0].hash, { cwd }); t.deepEqual(await getNote(commits[0].hash, { cwd }), { note: "note" }); }); test("Return empty object if there is no commit note", async (t) => { // Create a git repository, set the current working directory at the root of the repo const { cwd } = await gitRepo(); // Add commits to the master branch const commits = await gitCommits(["First"], { cwd }); t.deepEqual(await getNote(commits[0].hash, { cwd }), {}); }); test("Throw error if a commit note in invalid", async (t) => { // Create a git repository, set the current working directory at the root of the repo const { cwd } = await gitRepo(); // Add commits to the master branch const commits = await gitCommits(["First"], { cwd }); await gitAddNote("non-json note", commits[0].hash, { cwd }); await t.throwsAsync(getNote(commits[0].hash, { cwd })); }); test("Add a commit note", async (t) => { // Create a git repository, set the current working directory at the root of the repo const { cwd } = await gitRepo(); // Add commits to the master branch const commits = await gitCommits(["First"], { cwd }); await addNote({ note: "note" }, commits[0].hash, { cwd }); t.is(await gitGetNote(commits[0].hash, { cwd }), '{"note":"note"}'); }); test("Overwrite a commit note", async (t) => { // Create a git repository, set the current working directory at the root of the repo const { cwd } = await gitRepo(); // Add commits to the master branch const commits = await gitCommits(["First"], { cwd }); await addNote({ note: "note" }, commits[0].hash, { cwd }); await addNote({ note: "note2" }, commits[0].hash, { cwd }); t.is(await gitGetNote(commits[0].hash, { cwd }), '{"note":"note2"}'); }); test("Unshallow and fetch repository with notes", async (t) => { // Create a git repository, set the current working directory at the root of the repo let { cwd, repositoryUrl } = await gitRepo(); // Add commits to the master branch const commits = await gitCommits(["First", "Second"], { cwd }); await gitAddNote(JSON.stringify({ note: "note" }), commits[0].hash, { cwd }); // Create a shallow clone with only 1 commit cwd = await gitShallowClone(repositoryUrl); // Verify the shallow clone doesn't contains the note await t.throwsAsync(gitGetNote(commits[0].hash, { cwd })); await fetch(repositoryUrl, "master", "master", { cwd }); await fetchNotes(repositoryUrl, { cwd }); // Verify the shallow clone contains the note t.is(await gitGetNote(commits[0].hash, { cwd }), '{"note":"note"}'); }); test("Fetch all notes on a detached head repository", async (t) => { let { cwd, repositoryUrl } = await gitRepo(); await gitCommits(["First"], { cwd }); const [commit] = await gitCommits(["Second"], { cwd }); await gitPush(repositoryUrl, "master", { cwd }); await gitAddNote(JSON.stringify({ note: "note" }), commit.hash, { cwd }); cwd = await gitDetachedHead(repositoryUrl, commit.hash); await fetch(repositoryUrl, "master", "master", { cwd }); await fetchNotes(repositoryUrl, { cwd }); t.is(await gitGetNote(commit.hash, { cwd }), '{"note":"note"}'); });