@@ -1091,6 +1091,86 @@ pub fn indexPack(allocator: Allocator, pack: std.fs.File, index_writer: anytype)
1091
1091
try index_writer .writeAll (& index_checksum );
1092
1092
}
1093
1093
1094
+ /// A reader that stores read data in a growable internal buffer. The read
1095
+ /// position can be rewound to allow previously read data to be read again.
1096
+ fn AccumulatingReader (comptime ReaderType : type ) type {
1097
+ return struct {
1098
+ child_reader : ReaderType ,
1099
+ buffer : std .ArrayListUnmanaged (u8 ) = .{},
1100
+ /// The position in `buffer` from which reads should start, returning
1101
+ /// buffered data. If this is `buffer.items.len`, data will be read from
1102
+ /// `child_reader` instead.
1103
+ read_start : usize = 0 ,
1104
+ allocator : Allocator ,
1105
+
1106
+ const Self = @This ();
1107
+
1108
+ fn deinit (self : * Self ) void {
1109
+ self .buffer .deinit (self .allocator );
1110
+ self .* = undefined ;
1111
+ }
1112
+
1113
+ const ReadError = ReaderType .Error || Allocator .Error ;
1114
+ const Reader = std .io .Reader (* Self , ReadError , read );
1115
+
1116
+ fn read (self : * Self , buf : []u8 ) ReadError ! usize {
1117
+ if (self .read_start < self .buffer .items .len ) {
1118
+ // Previously buffered data is available and should be used
1119
+ // before reading more data from the underlying reader.
1120
+ const available = self .buffer .items .len - self .read_start ;
1121
+ const count = @min (available , buf .len );
1122
+ @memcpy (buf [0.. count ], self .buffer .items [self .read_start .. ][0.. count ]);
1123
+ self .read_start += count ;
1124
+ return count ;
1125
+ }
1126
+
1127
+ try self .buffer .ensureUnusedCapacity (self .allocator , buf .len );
1128
+ const read_buffer = self .buffer .unusedCapacitySlice ();
1129
+ const count = try self .child_reader .read (read_buffer [0.. buf .len ]);
1130
+ @memcpy (buf [0.. count ], read_buffer [0.. count ]);
1131
+ self .buffer .items .len += count ;
1132
+ self .read_start += count ;
1133
+ return count ;
1134
+ }
1135
+
1136
+ fn reader (self : * Self ) Reader {
1137
+ return .{ .context = self };
1138
+ }
1139
+
1140
+ /// Returns a slice of the buffered data that has already been read,
1141
+ /// except the last `count_before_end` bytes.
1142
+ fn readDataExcept (self : Self , count_before_end : usize ) []const u8 {
1143
+ assert (count_before_end <= self .read_start );
1144
+ return self .buffer .items [0 .. self .read_start - count_before_end ];
1145
+ }
1146
+
1147
+ /// Discards the first `count` bytes of buffered data.
1148
+ fn discard (self : * Self , count : usize ) void {
1149
+ assert (count <= self .buffer .items .len );
1150
+ const retain = self .buffer .items .len - count ;
1151
+ mem .copyForwards (
1152
+ u8 ,
1153
+ self .buffer .items [0.. retain ],
1154
+ self .buffer .items [count .. ][0.. retain ],
1155
+ );
1156
+ self .buffer .items .len = retain ;
1157
+ self .read_start -= @min (self .read_start , count );
1158
+ }
1159
+
1160
+ /// Rewinds the read position to the beginning of buffered data.
1161
+ fn rewind (self : * Self ) void {
1162
+ self .read_start = 0 ;
1163
+ }
1164
+ };
1165
+ }
1166
+
1167
+ fn accumulatingReader (
1168
+ allocator : Allocator ,
1169
+ reader : anytype ,
1170
+ ) AccumulatingReader (@TypeOf (reader )) {
1171
+ return .{ .child_reader = reader , .allocator = allocator };
1172
+ }
1173
+
1094
1174
/// Performs the first pass over the packfile data for index construction.
1095
1175
/// This will index all non-delta objects, queue delta objects for further
1096
1176
/// processing, and return the pack checksum (which is part of the index
@@ -1102,59 +1182,106 @@ fn indexPackFirstPass(
1102
1182
pending_deltas : * std .ArrayListUnmanaged (IndexEntry ),
1103
1183
) ! [Sha1 .digest_length ]u8 {
1104
1184
var pack_buffered_reader = std .io .bufferedReader (pack .reader ());
1105
- var pack_counting_reader = std .io .countingReader (pack_buffered_reader .reader ());
1106
- var pack_hashed_reader = std .compress .hashedReader (pack_counting_reader .reader (), Sha1 .init (.{}));
1107
- const pack_reader = pack_hashed_reader .reader ();
1185
+ var pack_accumulating_reader = accumulatingReader (allocator , pack_buffered_reader .reader ());
1186
+ defer pack_accumulating_reader .deinit ();
1187
+ var pack_position : usize = 0 ;
1188
+ var pack_hash = Sha1 .init (.{});
1189
+ const pack_reader = pack_accumulating_reader .reader ();
1108
1190
1109
1191
const pack_header = try PackHeader .read (pack_reader );
1192
+ const pack_header_bytes = pack_accumulating_reader .readDataExcept (0 );
1193
+ pack_position += pack_header_bytes .len ;
1194
+ pack_hash .update (pack_header_bytes );
1195
+ pack_accumulating_reader .discard (pack_header_bytes .len );
1110
1196
1111
1197
var current_entry : u32 = 0 ;
1112
1198
while (current_entry < pack_header .total_objects ) : (current_entry += 1 ) {
1113
- const entry_offset = pack_counting_reader .bytes_read ;
1114
- var entry_crc32_reader = std .compress .hashedReader (pack_reader , std .hash .Crc32 .init ());
1115
- const entry_header = try EntryHeader .read (entry_crc32_reader .reader ());
1199
+ const entry_offset = pack_position ;
1200
+ var entry_crc32 = std .hash .Crc32 .init ();
1201
+
1202
+ const entry_header = try EntryHeader .read (pack_reader );
1203
+ const entry_header_bytes = pack_accumulating_reader .readDataExcept (0 );
1204
+ pack_position += entry_header_bytes .len ;
1205
+ pack_hash .update (entry_header_bytes );
1206
+ entry_crc32 .update (entry_header_bytes );
1207
+ pack_accumulating_reader .discard (entry_header_bytes .len );
1208
+
1116
1209
switch (entry_header ) {
1117
1210
inline .commit , .tree , .blob , .tag = > | object , tag | {
1118
- var entry_decompress_stream = std .compress .zlib .decompressor (entry_crc32_reader . reader () );
1119
- var entry_counting_reader = std . io . countingReader ( entry_decompress_stream . reader ()) ;
1211
+ var entry_decompress_stream = std .compress .zlib .decompressor (pack_reader );
1212
+ var entry_data_size : usize = 0 ;
1120
1213
var entry_hashed_writer = hashedWriter (std .io .null_writer , Sha1 .init (.{}));
1121
1214
const entry_writer = entry_hashed_writer .writer ();
1215
+
1122
1216
// The object header is not included in the pack data but is
1123
1217
// part of the object's ID
1124
1218
try entry_writer .print ("{s} {}\x00 " , .{ @tagName (tag ), object .uncompressed_length });
1125
- var fifo = std .fifo .LinearFifo (u8 , .{ .Static = 4096 }).init ();
1126
- try fifo .pump (entry_counting_reader .reader (), entry_writer );
1127
- if (entry_counting_reader .bytes_read != object .uncompressed_length ) {
1219
+
1220
+ while (try entry_decompress_stream .next ()) | decompressed_data | {
1221
+ entry_data_size += decompressed_data .len ;
1222
+ try entry_writer .writeAll (decompressed_data );
1223
+
1224
+ const compressed_bytes = pack_accumulating_reader .readDataExcept (entry_decompress_stream .unreadBytes ());
1225
+ pack_position += compressed_bytes .len ;
1226
+ pack_hash .update (compressed_bytes );
1227
+ entry_crc32 .update (compressed_bytes );
1228
+ pack_accumulating_reader .discard (compressed_bytes .len );
1229
+ }
1230
+ const footer_bytes = pack_accumulating_reader .readDataExcept (entry_decompress_stream .unreadBytes ());
1231
+ pack_position += footer_bytes .len ;
1232
+ pack_hash .update (footer_bytes );
1233
+ entry_crc32 .update (footer_bytes );
1234
+ pack_accumulating_reader .discard (footer_bytes .len );
1235
+ pack_accumulating_reader .rewind ();
1236
+
1237
+ if (entry_data_size != object .uncompressed_length ) {
1128
1238
return error .InvalidObject ;
1129
1239
}
1240
+
1130
1241
const oid = entry_hashed_writer .hasher .finalResult ();
1131
1242
try index_entries .put (allocator , oid , .{
1132
1243
.offset = entry_offset ,
1133
- .crc32 = entry_crc32_reader . hasher .final (),
1244
+ .crc32 = entry_crc32 .final (),
1134
1245
});
1135
1246
},
1136
1247
inline .ofs_delta , .ref_delta = > | delta | {
1137
- var entry_decompress_stream = std .compress .zlib .decompressor (entry_crc32_reader .reader ());
1138
- var entry_counting_reader = std .io .countingReader (entry_decompress_stream .reader ());
1139
- var fifo = std .fifo .LinearFifo (u8 , .{ .Static = 4096 }).init ();
1140
- try fifo .pump (entry_counting_reader .reader (), std .io .null_writer );
1141
- if (entry_counting_reader .bytes_read != delta .uncompressed_length ) {
1248
+ var entry_decompress_stream = std .compress .zlib .decompressor (pack_reader );
1249
+ var entry_data_size : usize = 0 ;
1250
+
1251
+ while (try entry_decompress_stream .next ()) | decompressed_data | {
1252
+ entry_data_size += decompressed_data .len ;
1253
+
1254
+ const compressed_bytes = pack_accumulating_reader .readDataExcept (entry_decompress_stream .unreadBytes ());
1255
+ pack_position += compressed_bytes .len ;
1256
+ pack_hash .update (compressed_bytes );
1257
+ entry_crc32 .update (compressed_bytes );
1258
+ pack_accumulating_reader .discard (compressed_bytes .len );
1259
+ }
1260
+ const footer_bytes = pack_accumulating_reader .readDataExcept (entry_decompress_stream .unreadBytes ());
1261
+ pack_position += footer_bytes .len ;
1262
+ pack_hash .update (footer_bytes );
1263
+ entry_crc32 .update (footer_bytes );
1264
+ pack_accumulating_reader .discard (footer_bytes .len );
1265
+ pack_accumulating_reader .rewind ();
1266
+
1267
+ if (entry_data_size != delta .uncompressed_length ) {
1142
1268
return error .InvalidObject ;
1143
1269
}
1270
+
1144
1271
try pending_deltas .append (allocator , .{
1145
1272
.offset = entry_offset ,
1146
- .crc32 = entry_crc32_reader . hasher .final (),
1273
+ .crc32 = entry_crc32 .final (),
1147
1274
});
1148
1275
},
1149
1276
}
1150
1277
}
1151
1278
1152
- const pack_checksum = pack_hashed_reader . hasher .finalResult ();
1153
- const recorded_checksum = try pack_buffered_reader . reader () .readBytesNoEof (Sha1 .digest_length );
1279
+ const pack_checksum = pack_hash .finalResult ();
1280
+ const recorded_checksum = try pack_reader .readBytesNoEof (Sha1 .digest_length );
1154
1281
if (! mem .eql (u8 , & pack_checksum , & recorded_checksum )) {
1155
1282
return error .CorruptedPack ;
1156
1283
}
1157
- _ = pack_buffered_reader . reader () .readByte () catch | e | switch (e ) {
1284
+ _ = pack_reader .readByte () catch | e | switch (e ) {
1158
1285
error .EndOfStream = > return pack_checksum ,
1159
1286
else = > | other | return other ,
1160
1287
};
@@ -1385,7 +1512,11 @@ test "packfile indexing and checkout" {
1385
1512
defer worktree .cleanup ();
1386
1513
1387
1514
const commit_id = try parseOid ("dd582c0720819ab7130b103635bd7271b9fd4feb" );
1388
- try repository .checkout (worktree .dir , commit_id );
1515
+
1516
+ var diagnostics : Diagnostics = .{ .allocator = testing .allocator };
1517
+ defer diagnostics .deinit ();
1518
+ try repository .checkout (worktree .dir , commit_id , & diagnostics );
1519
+ try testing .expect (diagnostics .errors .items .len == 0 );
1389
1520
1390
1521
const expected_files : []const []const u8 = &.{
1391
1522
"dir/file" ,
0 commit comments