@@ -46,6 +46,7 @@ template<> struct hash<git_oid>
46
46
47
47
}
48
48
49
+
49
50
std::ostream & operator << (std::ostream & str, const git_oid & oid)
50
51
{
51
52
str << git_oid_tostr_s (&oid);
@@ -57,6 +58,78 @@ bool operator == (const git_oid & oid1, const git_oid & oid2)
57
58
return git_oid_equal (&oid1, &oid2);
58
59
}
59
60
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
+ return filename;
96
+ }
97
+
98
+ std::string result (dotCount * 2 , ' .' ); // String with 2*dotCount dots
99
+ result += " git" ;
100
+
101
+ return result;
102
+ }
103
+
104
+ std::string unescapeDotGit (const std::string filename) {
105
+ // Check if this filename matches the pattern of dots followed by "git"
106
+ int dotCount = matchesDotPlusGit (filename);
107
+ // Ensure dots are even for unescaping (must be divisible by 2)
108
+ if (dotCount == 0 || dotCount % 2 != 0 ) {
109
+ // Can't unescape an odd number of dots, return as is
110
+ return filename;
111
+ }
112
+
113
+ // Create a new string with half the dots plus "git"
114
+ std::string result (dotCount / 2 , ' .' ); // String with dotCount/2 dots
115
+ result += " git" ;
116
+
117
+ return result;
118
+ }
119
+
120
+ const git_tree_entry* gitTreebuilderGet (git_treebuilder *bld, std::string name)
121
+ {
122
+ auto escapedName = escapeDotGit (name);
123
+ return git_treebuilder_get (bld, escapedName.c_str ());
124
+ }
125
+
126
+ const std::string gitTreeEntryName (const git_tree_entry *entry)
127
+ {
128
+ auto escapedName = git_tree_entry_name (entry);
129
+ return unescapeDotGit (escapedName);
130
+ }
131
+ }
132
+
60
133
namespace nix {
61
134
62
135
struct GitSourceAccessor ;
@@ -740,7 +813,7 @@ struct GitSourceAccessor : SourceAccessor
740
813
for (size_t n = 0 ; n < count; ++n) {
741
814
auto entry = git_tree_entry_byindex (tree.get (), n);
742
815
// FIXME: add to cache
743
- res.emplace (std::string (git_tree_entry_name (entry)), DirEntry{});
816
+ res.emplace (std::string (gitTreeEntryName (entry)), DirEntry{});
744
817
}
745
818
746
819
return res;
@@ -799,7 +872,7 @@ struct GitSourceAccessor : SourceAccessor
799
872
if (git_tree_entry_dup (Setter (copy), entry))
800
873
throw Error (" dupping tree entry: %s" , git_error_last ()->message );
801
874
802
- auto entryName = std::string_view ( git_tree_entry_name ( entry) );
875
+ auto entryName = gitTreeEntryName ( entry);
803
876
804
877
if (entryName == name)
805
878
res = copy.get ();
@@ -970,6 +1043,7 @@ struct GitExportIgnoreSourceAccessor : CachingFilteringSourceAccessor {
970
1043
return !isExportIgnored (path);
971
1044
}
972
1045
1046
+
973
1047
};
974
1048
975
1049
struct GitFileSystemObjectSinkImpl : GitFileSystemObjectSink
@@ -990,7 +1064,7 @@ struct GitFileSystemObjectSinkImpl : GitFileSystemObjectSink
990
1064
Tree prevTree = nullptr ;
991
1065
992
1066
if (!pendingDirs.empty () &&
993
- (entry = git_treebuilder_get (pendingDirs.back ().builder .get (), name. c_str () )))
1067
+ (entry = gitTreebuilderGet (pendingDirs.back ().builder .get (), name)))
994
1068
{
995
1069
/* Clone a tree that we've already finished. This happens
996
1070
if a tarball has directory entries that are not
@@ -1028,7 +1102,8 @@ struct GitFileSystemObjectSinkImpl : GitFileSystemObjectSink
1028
1102
{
1029
1103
assert (!pendingDirs.empty ());
1030
1104
auto & pending = pendingDirs.back ();
1031
- if (git_treebuilder_insert (nullptr , pending.builder .get (), name.c_str (), &oid, mode))
1105
+ auto escapedName = escapeDotGit (name);
1106
+ if (git_treebuilder_insert (nullptr , pending.builder .get (), escapedName.c_str (), &oid, mode))
1032
1107
throw Error (" adding a file to a tree builder: %s" , git_error_last ()->message );
1033
1108
};
1034
1109
@@ -1159,7 +1234,7 @@ struct GitFileSystemObjectSinkImpl : GitFileSystemObjectSink
1159
1234
for (auto & c : CanonPath (relTargetLeft)) {
1160
1235
if (auto builder = std::get_if<git_treebuilder *>(&curDir)) {
1161
1236
assert (*builder);
1162
- if (!(entry = git_treebuilder_get (*builder, std::string (c). c_str ( ))))
1237
+ if (!(entry = gitTreebuilderGet (*builder, std::string (c))))
1163
1238
throw Error (" cannot find hard link target '%s' for path '%s'" , target, path);
1164
1239
curDir = *git_tree_entry_id (entry);
1165
1240
} else if (auto oid = std::get_if<git_oid>(&curDir)) {
0 commit comments