mirror of
https://github.com/NixOS/nix
synced 2025-06-28 13:41:15 +02:00
Git object hashing in libutil
This is the core functionality but just unit-tested and not yet made part of the store layer. This is because there is some tech debt around (a) repeated boilerplate hashing objects (b) better integration of the new `SourceAccessor` type that needs to be cleaned up first. Part of RFC 133 Co-Authored-By: Matthew Bauer <mjbauer95@gmail.com> Co-Authored-By: Carlo Nucera <carlo.nucera@protonmail.com> Co-authored-by: Robert Hensing <roberth@users.noreply.github.com> Co-authored-by: Florian Klink <flokli@flokli.de>
This commit is contained in:
parent
9afa697ab6
commit
20b95d6223
13 changed files with 667 additions and 38 deletions
|
@ -1,33 +1,236 @@
|
|||
#include "git.hh"
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "git.hh"
|
||||
#include "memory-source-accessor.hh"
|
||||
|
||||
#include "tests/characterization.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
TEST(GitLsRemote, parseSymrefLineWithReference) {
|
||||
auto line = "ref: refs/head/main HEAD";
|
||||
auto res = git::parseLsRemoteLine(line);
|
||||
ASSERT_TRUE(res.has_value());
|
||||
ASSERT_EQ(res->kind, git::LsRemoteRefLine::Kind::Symbolic);
|
||||
ASSERT_EQ(res->target, "refs/head/main");
|
||||
ASSERT_EQ(res->reference, "HEAD");
|
||||
using namespace git;
|
||||
|
||||
class GitTest : public CharacterizationTest
|
||||
{
|
||||
Path unitTestData = getUnitTestData() + "/libutil/git";
|
||||
|
||||
public:
|
||||
|
||||
Path goldenMaster(std::string_view testStem) const override {
|
||||
return unitTestData + "/" + testStem;
|
||||
}
|
||||
|
||||
TEST(GitLsRemote, parseSymrefLineWithNoReference) {
|
||||
auto line = "ref: refs/head/main";
|
||||
auto res = git::parseLsRemoteLine(line);
|
||||
ASSERT_TRUE(res.has_value());
|
||||
ASSERT_EQ(res->kind, git::LsRemoteRefLine::Kind::Symbolic);
|
||||
ASSERT_EQ(res->target, "refs/head/main");
|
||||
ASSERT_EQ(res->reference, std::nullopt);
|
||||
}
|
||||
/**
|
||||
* We set these in tests rather than the regular globals so we don't have
|
||||
* to worry about race conditions if the tests run concurrently.
|
||||
*/
|
||||
ExperimentalFeatureSettings mockXpSettings;
|
||||
|
||||
TEST(GitLsRemote, parseObjectRefLine) {
|
||||
auto line = "abc123 refs/head/main";
|
||||
auto res = git::parseLsRemoteLine(line);
|
||||
ASSERT_TRUE(res.has_value());
|
||||
ASSERT_EQ(res->kind, git::LsRemoteRefLine::Kind::Object);
|
||||
ASSERT_EQ(res->target, "abc123");
|
||||
ASSERT_EQ(res->reference, "refs/head/main");
|
||||
private:
|
||||
|
||||
void SetUp() override
|
||||
{
|
||||
mockXpSettings.set("experimental-features", "git-hashing");
|
||||
}
|
||||
};
|
||||
|
||||
TEST(GitMode, gitMode_directory) {
|
||||
Mode m = Mode::Directory;
|
||||
RawMode r = 0040000;
|
||||
ASSERT_EQ(static_cast<RawMode>(m), r);
|
||||
ASSERT_EQ(decodeMode(r), std::optional { m });
|
||||
};
|
||||
|
||||
TEST(GitMode, gitMode_executable) {
|
||||
Mode m = Mode::Executable;
|
||||
RawMode r = 0100755;
|
||||
ASSERT_EQ(static_cast<RawMode>(m), r);
|
||||
ASSERT_EQ(decodeMode(r), std::optional { m });
|
||||
};
|
||||
|
||||
TEST(GitMode, gitMode_regular) {
|
||||
Mode m = Mode::Regular;
|
||||
RawMode r = 0100644;
|
||||
ASSERT_EQ(static_cast<RawMode>(m), r);
|
||||
ASSERT_EQ(decodeMode(r), std::optional { m });
|
||||
};
|
||||
|
||||
TEST(GitMode, gitMode_symlink) {
|
||||
Mode m = Mode::Symlink;
|
||||
RawMode r = 0120000;
|
||||
ASSERT_EQ(static_cast<RawMode>(m), r);
|
||||
ASSERT_EQ(decodeMode(r), std::optional { m });
|
||||
};
|
||||
|
||||
TEST_F(GitTest, blob_read) {
|
||||
readTest("hello-world-blob.bin", [&](const auto & encoded) {
|
||||
StringSource in { encoded };
|
||||
StringSink out;
|
||||
RegularFileSink out2 { out };
|
||||
parse(out2, "", in, [](auto &, auto) {}, mockXpSettings);
|
||||
|
||||
auto expected = readFile(goldenMaster("hello-world.bin"));
|
||||
|
||||
ASSERT_EQ(out.s, expected);
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(GitTest, blob_write) {
|
||||
writeTest("hello-world-blob.bin", [&]() {
|
||||
auto decoded = readFile(goldenMaster("hello-world.bin"));
|
||||
StringSink s;
|
||||
dumpBlobPrefix(decoded.size(), s, mockXpSettings);
|
||||
s(decoded);
|
||||
return s.s;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* This data is for "shallow" tree tests. However, we use "real" hashes
|
||||
* so that we can check our test data in the corresponding functional
|
||||
* test (`git-hashing/unit-test-data`).
|
||||
*/
|
||||
const static Tree tree = {
|
||||
{
|
||||
"Foo",
|
||||
{
|
||||
.mode = Mode::Regular,
|
||||
// hello world with special chars from above
|
||||
.hash = Hash::parseAny("63ddb340119baf8492d2da53af47e8c7cfcd5eb2", htSHA1),
|
||||
},
|
||||
},
|
||||
{
|
||||
"bAr",
|
||||
{
|
||||
.mode = Mode::Executable,
|
||||
// ditto
|
||||
.hash = Hash::parseAny("63ddb340119baf8492d2da53af47e8c7cfcd5eb2", htSHA1),
|
||||
},
|
||||
},
|
||||
{
|
||||
"baZ/",
|
||||
{
|
||||
.mode = Mode::Directory,
|
||||
// Empty directory hash
|
||||
.hash = Hash::parseAny("4b825dc642cb6eb9a060e54bf8d69288fbee4904", htSHA1),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
TEST_F(GitTest, tree_read) {
|
||||
readTest("tree.bin", [&](const auto & encoded) {
|
||||
StringSource in { encoded };
|
||||
NullParseSink out;
|
||||
Tree got;
|
||||
parse(out, "", in, [&](auto & name, auto entry) {
|
||||
auto name2 = name;
|
||||
if (entry.mode == Mode::Directory)
|
||||
name2 += '/';
|
||||
got.insert_or_assign(name2, std::move(entry));
|
||||
}, mockXpSettings);
|
||||
|
||||
ASSERT_EQ(got, tree);
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(GitTest, tree_write) {
|
||||
writeTest("tree.bin", [&]() {
|
||||
StringSink s;
|
||||
dumpTree(tree, s, mockXpSettings);
|
||||
return s.s;
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(GitTest, both_roundrip) {
|
||||
using File = MemorySourceAccessor::File;
|
||||
|
||||
MemorySourceAccessor files;
|
||||
files.root = File::Directory {
|
||||
.contents {
|
||||
{
|
||||
"foo",
|
||||
File::Regular {
|
||||
.contents = "hello\n\0\n\tworld!",
|
||||
},
|
||||
},
|
||||
{
|
||||
"bar",
|
||||
File::Directory {
|
||||
.contents = {
|
||||
{
|
||||
"baz",
|
||||
File::Regular {
|
||||
.executable = true,
|
||||
.contents = "good day,\n\0\n\tworld!",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
std::map<Hash, std::string> cas;
|
||||
|
||||
std::function<DumpHook> dumpHook;
|
||||
dumpHook = [&](const CanonPath & path) {
|
||||
StringSink s;
|
||||
HashSink hashSink { htSHA1 };
|
||||
TeeSink s2 { s, hashSink };
|
||||
auto mode = dump(
|
||||
files, path, s2, dumpHook,
|
||||
defaultPathFilter, mockXpSettings);
|
||||
auto hash = hashSink.finish().first;
|
||||
cas.insert_or_assign(hash, std::move(s.s));
|
||||
return TreeEntry {
|
||||
.mode = mode,
|
||||
.hash = hash,
|
||||
};
|
||||
};
|
||||
|
||||
auto root = dumpHook(CanonPath::root);
|
||||
|
||||
MemorySourceAccessor files2;
|
||||
|
||||
MemorySink sinkFiles2 { files2 };
|
||||
|
||||
std::function<void(const Path, const Hash &)> mkSinkHook;
|
||||
mkSinkHook = [&](const Path prefix, const Hash & hash) {
|
||||
StringSource in { cas[hash] };
|
||||
parse(sinkFiles2, prefix, in, [&](const Path & name, const auto & entry) {
|
||||
mkSinkHook(prefix + "/" + name, entry.hash);
|
||||
}, mockXpSettings);
|
||||
};
|
||||
|
||||
mkSinkHook("", root.hash);
|
||||
|
||||
ASSERT_EQ(files, files2);
|
||||
}
|
||||
|
||||
TEST(GitLsRemote, parseSymrefLineWithReference) {
|
||||
auto line = "ref: refs/head/main HEAD";
|
||||
auto res = parseLsRemoteLine(line);
|
||||
ASSERT_TRUE(res.has_value());
|
||||
ASSERT_EQ(res->kind, LsRemoteRefLine::Kind::Symbolic);
|
||||
ASSERT_EQ(res->target, "refs/head/main");
|
||||
ASSERT_EQ(res->reference, "HEAD");
|
||||
}
|
||||
|
||||
TEST(GitLsRemote, parseSymrefLineWithNoReference) {
|
||||
auto line = "ref: refs/head/main";
|
||||
auto res = parseLsRemoteLine(line);
|
||||
ASSERT_TRUE(res.has_value());
|
||||
ASSERT_EQ(res->kind, LsRemoteRefLine::Kind::Symbolic);
|
||||
ASSERT_EQ(res->target, "refs/head/main");
|
||||
ASSERT_EQ(res->reference, std::nullopt);
|
||||
}
|
||||
|
||||
TEST(GitLsRemote, parseObjectRefLine) {
|
||||
auto line = "abc123 refs/head/main";
|
||||
auto res = parseLsRemoteLine(line);
|
||||
ASSERT_TRUE(res.has_value());
|
||||
ASSERT_EQ(res->kind, LsRemoteRefLine::Kind::Object);
|
||||
ASSERT_EQ(res->target, "abc123");
|
||||
ASSERT_EQ(res->reference, "refs/head/main");
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue