@@ -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,81 @@ 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
+ 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
+
60
136
namespace nix {
61
137
62
138
struct GitSourceAccessor ;
@@ -740,7 +816,7 @@ struct GitSourceAccessor : SourceAccessor
740
816
for (size_t n = 0 ; n < count; ++n) {
741
817
auto entry = git_tree_entry_byindex (tree.get (), n);
742
818
// FIXME: add to cache
743
- res.emplace (std::string (git_tree_entry_name (entry)), DirEntry{});
819
+ res.emplace (std::string (gitTreeEntryName (entry)), DirEntry{});
744
820
}
745
821
746
822
return res;
@@ -799,7 +875,7 @@ struct GitSourceAccessor : SourceAccessor
799
875
if (git_tree_entry_dup (Setter (copy), entry))
800
876
throw Error (" dupping tree entry: %s" , git_error_last ()->message );
801
877
802
- auto entryName = std::string_view ( git_tree_entry_name ( entry) );
878
+ auto entryName = gitTreeEntryName ( entry);
803
879
804
880
if (entryName == name)
805
881
res = copy.get ();
@@ -970,6 +1046,7 @@ struct GitExportIgnoreSourceAccessor : CachingFilteringSourceAccessor {
970
1046
return !isExportIgnored (path);
971
1047
}
972
1048
1049
+
973
1050
};
974
1051
975
1052
struct GitFileSystemObjectSinkImpl : GitFileSystemObjectSink
@@ -990,7 +1067,7 @@ struct GitFileSystemObjectSinkImpl : GitFileSystemObjectSink
990
1067
Tree prevTree = nullptr ;
991
1068
992
1069
if (!pendingDirs.empty () &&
993
- (entry = git_treebuilder_get (pendingDirs.back ().builder .get (), name. c_str () )))
1070
+ (entry = gitTreebuilderGet (pendingDirs.back ().builder .get (), name)))
994
1071
{
995
1072
/* Clone a tree that we've already finished. This happens
996
1073
if a tarball has directory entries that are not
@@ -1028,7 +1105,8 @@ struct GitFileSystemObjectSinkImpl : GitFileSystemObjectSink
1028
1105
{
1029
1106
assert (!pendingDirs.empty ());
1030
1107
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))
1032
1110
throw Error (" adding a file to a tree builder: %s" , git_error_last ()->message );
1033
1111
};
1034
1112
@@ -1159,7 +1237,7 @@ struct GitFileSystemObjectSinkImpl : GitFileSystemObjectSink
1159
1237
for (auto & c : CanonPath (relTargetLeft)) {
1160
1238
if (auto builder = std::get_if<git_treebuilder *>(&curDir)) {
1161
1239
assert (*builder);
1162
- if (!(entry = git_treebuilder_get (*builder, std::string (c). c_str ( ))))
1240
+ if (!(entry = gitTreebuilderGet (*builder, std::string (c))))
1163
1241
throw Error (" cannot find hard link target '%s' for path '%s'" , target, path);
1164
1242
curDir = *git_tree_entry_id (entry);
1165
1243
} else if (auto oid = std::get_if<git_oid>(&curDir)) {
0 commit comments