Skip to content

Commit f66edb4

Browse files
committed
escape .git for tarball cache
Fix for #10575
1 parent 3f13cc0 commit f66edb4

File tree

1 file changed

+83
-5
lines changed

1 file changed

+83
-5
lines changed

Diff for: src/libfetchers/git-utils.cc

+83-5
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ template<> struct hash<git_oid>
4646

4747
}
4848

49+
4950
std::ostream & operator << (std::ostream & str, const git_oid & oid)
5051
{
5152
str << git_oid_tostr_s(&oid);
@@ -57,6 +58,81 @@ bool operator == (const git_oid & oid1, const git_oid & oid2)
5758
return git_oid_equal(&oid1, &oid2);
5859
}
5960

61+
namespace {
62+
63+
int matchesDotPlusGit(const std::string& str) {
64+
// String must have at least 4 characters (at least one '.' plus "git")
65+
if (str.size() < 4) {
66+
return 0;
67+
}
68+
69+
// Count consecutive dots at the beginning
70+
size_t dotCount = 0;
71+
while (dotCount < str.size() && str[dotCount] == '.') {
72+
dotCount++;
73+
}
74+
75+
// Must have at least one dot
76+
if (dotCount == 0) {
77+
return 0;
78+
}
79+
80+
// After the dots, check if the remaining string is exactly "git"
81+
if ((str.size() == dotCount + 3) &&
82+
(str[dotCount] == 'g') &&
83+
(str[dotCount + 1] == 'i') &&
84+
(str[dotCount + 2] == 't')) {
85+
return dotCount;
86+
}
87+
return 0;
88+
}
89+
90+
std::string escapeDotGit(const std::string& filename) {
91+
// Check if this filename matches the pattern of dots followed by "git"
92+
int dotCount = matchesDotPlusGit(filename);
93+
if (dotCount == 0) {
94+
// Not a dot+git pattern, return as is
95+
fprintf(stderr, "Not a dot+git pattern: %s\n", filename.c_str());
96+
return filename;
97+
}
98+
99+
std::string result(dotCount * 2, '.'); // String with 2*dotCount dots
100+
result += "git";
101+
fprintf(stderr, "Escaped: %s -> %s\n", filename.c_str(), result.c_str());
102+
103+
return result;
104+
}
105+
106+
std::string unescapeDotGit(const std::string filename) {
107+
// Check if this filename matches the pattern of dots followed by "git"
108+
int dotCount = matchesDotPlusGit(filename);
109+
// Ensure dots are even for unescaping (must be divisible by 2)
110+
if (dotCount == 0 || dotCount % 2 != 0) {
111+
// Can't unescape an odd number of dots, return as is
112+
return filename;
113+
}
114+
115+
// Create a new string with half the dots plus "git"
116+
std::string result(dotCount / 2, '.'); // String with dotCount/2 dots
117+
result += "git";
118+
fprintf(stderr, "Unescaped: %s -> %s\n", filename.c_str(), result.c_str());
119+
120+
return result;
121+
}
122+
123+
const git_tree_entry* gitTreebuilderGet(git_treebuilder *bld, std::string name)
124+
{
125+
auto escapedName = escapeDotGit(name);
126+
return git_treebuilder_get(bld, escapedName.c_str());
127+
}
128+
129+
const std::string gitTreeEntryName(const git_tree_entry *entry)
130+
{
131+
auto escapedName = git_tree_entry_name(entry);
132+
return unescapeDotGit(escapedName);
133+
}
134+
}
135+
60136
namespace nix {
61137

62138
struct GitSourceAccessor;
@@ -740,7 +816,7 @@ struct GitSourceAccessor : SourceAccessor
740816
for (size_t n = 0; n < count; ++n) {
741817
auto entry = git_tree_entry_byindex(tree.get(), n);
742818
// FIXME: add to cache
743-
res.emplace(std::string(git_tree_entry_name(entry)), DirEntry{});
819+
res.emplace(std::string(gitTreeEntryName(entry)), DirEntry{});
744820
}
745821

746822
return res;
@@ -799,7 +875,7 @@ struct GitSourceAccessor : SourceAccessor
799875
if (git_tree_entry_dup(Setter(copy), entry))
800876
throw Error("dupping tree entry: %s", git_error_last()->message);
801877

802-
auto entryName = std::string_view(git_tree_entry_name(entry));
878+
auto entryName = gitTreeEntryName(entry);
803879

804880
if (entryName == name)
805881
res = copy.get();
@@ -970,6 +1046,7 @@ struct GitExportIgnoreSourceAccessor : CachingFilteringSourceAccessor {
9701046
return !isExportIgnored(path);
9711047
}
9721048

1049+
9731050
};
9741051

9751052
struct GitFileSystemObjectSinkImpl : GitFileSystemObjectSink
@@ -990,7 +1067,7 @@ struct GitFileSystemObjectSinkImpl : GitFileSystemObjectSink
9901067
Tree prevTree = nullptr;
9911068

9921069
if (!pendingDirs.empty() &&
993-
(entry = git_treebuilder_get(pendingDirs.back().builder.get(), name.c_str())))
1070+
(entry = gitTreebuilderGet(pendingDirs.back().builder.get(), name)))
9941071
{
9951072
/* Clone a tree that we've already finished. This happens
9961073
if a tarball has directory entries that are not
@@ -1028,7 +1105,8 @@ struct GitFileSystemObjectSinkImpl : GitFileSystemObjectSink
10281105
{
10291106
assert(!pendingDirs.empty());
10301107
auto & pending = pendingDirs.back();
1031-
if (git_treebuilder_insert(nullptr, pending.builder.get(), name.c_str(), &oid, mode))
1108+
auto escapedName = escapeDotGit(name);
1109+
if (git_treebuilder_insert(nullptr, pending.builder.get(), escapedName.c_str(), &oid, mode))
10321110
throw Error("adding a file to a tree builder: %s", git_error_last()->message);
10331111
};
10341112

@@ -1159,7 +1237,7 @@ struct GitFileSystemObjectSinkImpl : GitFileSystemObjectSink
11591237
for (auto & c : CanonPath(relTargetLeft)) {
11601238
if (auto builder = std::get_if<git_treebuilder *>(&curDir)) {
11611239
assert(*builder);
1162-
if (!(entry = git_treebuilder_get(*builder, std::string(c).c_str())))
1240+
if (!(entry = gitTreebuilderGet(*builder, std::string(c))))
11631241
throw Error("cannot find hard link target '%s' for path '%s'", target, path);
11641242
curDir = *git_tree_entry_id(entry);
11651243
} else if (auto oid = std::get_if<git_oid>(&curDir)) {

0 commit comments

Comments
 (0)