odb/source-loose: wire up `read_object_stream()` callback

Move `odb_source_loose_read_object_stream()` and its associated helpers from "object-file.c" into "odb/source-loose.c" and wire it up as the `read_object_stream()` callback of the loose source. As part of the move we are also forced to expose a couple of functions from "object-file.h" that parse object headers in a somewhat-generic way, as those functions are now used by both subsystems. Signed-off-by: Patrick Steinhardt <ps@pks.im> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Patrick Steinhardt committed Jun 1, 2026 at 10:20 UTC 727a935a71c29524c936520d8aba4de7098f7566
4 files changed +222 -200
object-file.c
+6 -194
@@ -164,28 +164,6 @@ int stream_object_signature(struct repository *r,
164 return !oideq(oid, &real_oid) ? -1 : 0;
165 }
166
167 -/*
168 - * Find "oid" as a loose object in given source, open the object and return its
169 - * file descriptor. Returns the file descriptor on success, negative on failure.
170 - *
171 - * The "path" out-parameter will give the path of the object we found (if any).
172 - * Note that it may point to static storage and is only valid until another
173 - * call to stat_loose_object().
174 - */
175 -static int open_loose_object(struct odb_source_loose *loose,
176 - const struct object_id *oid, const char **path)
177 -{
178 - static struct strbuf buf = STRBUF_INIT;
179 - int fd;
180 -
181 - *path = odb_loose_path(&loose->files->base, &buf, oid);
182 - fd = git_open(*path);
183 - if (fd >= 0)
184 - return fd;
185 -
186 - return -1;
187 -}
188 -
167 static int quick_has_loose(struct odb_source_loose *loose,
168 const struct object_id *oid)
169 {
@@ -215,42 +193,11 @@ static void *map_fd(int fd, const char *path, unsigned long *size)
193 return map;
194 }
195
218 -static void *odb_source_loose_map_object(struct odb_source *source,
219 - const struct object_id *oid,
220 - unsigned long *size)
221 -{
222 - struct odb_source_files *files = odb_source_files_downcast(source);
223 - const char *p;
224 - int fd = open_loose_object(files->loose, oid, &p);
225 -
226 - if (fd < 0)
227 - return NULL;
228 - return map_fd(fd, p, size);
229 -}
230 -
231 -enum unpack_loose_header_result {
232 - ULHR_OK,
233 - ULHR_BAD,
234 - ULHR_TOO_LONG,
235 -};
236 -
237 -/**
238 - * unpack_loose_header() initializes the data stream needed to unpack
239 - * a loose object header.
240 - *
241 - * Returns:
242 - *
243 - * - ULHR_OK on success
244 - * - ULHR_BAD on error
245 - * - ULHR_TOO_LONG if the header was too long
246 - *
247 - * It will only parse up to MAX_HEADER_LEN bytes.
248 - */
249 -static enum unpack_loose_header_result unpack_loose_header(git_zstream *stream,
250 - unsigned char *map,
251 - unsigned long mapsize,
252 - void *buffer,
253 - unsigned long bufsiz)
196 +enum unpack_loose_header_result unpack_loose_header(git_zstream *stream,
197 + unsigned char *map,
198 + unsigned long mapsize,
199 + void *buffer,
200 + unsigned long bufsiz)
201 {
202 int status;
203
@@ -340,7 +287,7 @@ static void *unpack_loose_rest(git_zstream *stream,
287 * too permissive for what we want to check. So do an anal
288 * object header parse by hand.
289 */
343 -static int parse_loose_header(const char *hdr, struct object_info *oi)
290 +int parse_loose_header(const char *hdr, struct object_info *oi)
291 {
292 const char *type_buf = hdr;
293 size_t size;
@@ -2170,138 +2117,3 @@ struct odb_transaction *odb_transaction_files_begin(struct odb_source *source)
2117
2118 return &transaction->base;
2119 }
2173 -
2174 -struct odb_loose_read_stream {
2175 - struct odb_read_stream base;
2176 - git_zstream z;
2177 - enum {
2178 - ODB_LOOSE_READ_STREAM_INUSE,
2179 - ODB_LOOSE_READ_STREAM_DONE,
2180 - ODB_LOOSE_READ_STREAM_ERROR,
2181 - } z_state;
2182 - void *mapped;
2183 - unsigned long mapsize;
2184 - char hdr[32];
2185 - int hdr_avail;
2186 - int hdr_used;
2187 -};
2188 -
2189 -static ssize_t read_istream_loose(struct odb_read_stream *_st, char *buf, size_t sz)
2190 -{
2191 - struct odb_loose_read_stream *st =
2192 - container_of(_st, struct odb_loose_read_stream, base);
2193 - size_t total_read = 0;
2194 -
2195 - switch (st->z_state) {
2196 - case ODB_LOOSE_READ_STREAM_DONE:
2197 - return 0;
2198 - case ODB_LOOSE_READ_STREAM_ERROR:
2199 - return -1;
2200 - default:
2201 - break;
2202 - }
2203 -
2204 - if (st->hdr_used < st->hdr_avail) {
2205 - size_t to_copy = st->hdr_avail - st->hdr_used;
2206 - if (sz < to_copy)
2207 - to_copy = sz;
2208 - memcpy(buf, st->hdr + st->hdr_used, to_copy);
2209 - st->hdr_used += to_copy;
2210 - total_read += to_copy;
2211 - }
2212 -
2213 - while (total_read < sz) {
2214 - int status;
2215 -
2216 - st->z.next_out = (unsigned char *)buf + total_read;
2217 - st->z.avail_out = sz - total_read;
2218 - status = git_inflate(&st->z, Z_FINISH);
2219 -
2220 - total_read = st->z.next_out - (unsigned char *)buf;
2221 -
2222 - if (status == Z_STREAM_END) {
2223 - git_inflate_end(&st->z);
2224 - st->z_state = ODB_LOOSE_READ_STREAM_DONE;
2225 - break;
2226 - }
2227 - if (status != Z_OK && (status != Z_BUF_ERROR || total_read < sz)) {
2228 - git_inflate_end(&st->z);
2229 - st->z_state = ODB_LOOSE_READ_STREAM_ERROR;
2230 - return -1;
2231 - }
2232 - }
2233 - return total_read;
2234 -}
2235 -
2236 -static int close_istream_loose(struct odb_read_stream *_st)
2237 -{
2238 - struct odb_loose_read_stream *st =
2239 - container_of(_st, struct odb_loose_read_stream, base);
2240 -
2241 - if (st->z_state == ODB_LOOSE_READ_STREAM_INUSE)
2242 - git_inflate_end(&st->z);
2243 - munmap(st->mapped, st->mapsize);
2244 - return 0;
2245 -}
2246 -
2247 -int odb_source_loose_read_object_stream(struct odb_read_stream **out,
2248 - struct odb_source *source,
2249 - const struct object_id *oid)
2250 -{
2251 - struct object_info oi = OBJECT_INFO_INIT;
2252 - struct odb_loose_read_stream *st;
2253 - unsigned long mapsize;
2254 - unsigned long size_ul;
2255 - void *mapped;
2256 -
2257 - mapped = odb_source_loose_map_object(source, oid, &mapsize);
2258 - if (!mapped)
2259 - return -1;
2260 -
2261 - /*
2262 - * Note: we must allocate this structure early even though we may still
2263 - * fail. This is because we need to initialize the zlib stream, and it
2264 - * is not possible to copy the stream around after the fact because it
2265 - * has self-referencing pointers.
2266 - */
2267 - CALLOC_ARRAY(st, 1);
2268 -
2269 - switch (unpack_loose_header(&st->z, mapped, mapsize, st->hdr,
2270 - sizeof(st->hdr))) {
2271 - case ULHR_OK:
2272 - break;
2273 - case ULHR_BAD:
2274 - case ULHR_TOO_LONG:
2275 - goto error;
2276 - }
2277 -
2278 - /*
2279 - * object_info.sizep is unsigned long* (32-bit on Windows), but
2280 - * st->base.size is size_t (64-bit). Use temporary variable.
2281 - * Note: loose objects >4GB would still truncate here, but such
2282 - * large loose objects are uncommon (they'd normally be packed).
2283 - */
2284 - oi.sizep = &size_ul;
2285 - oi.typep = &st->base.type;
2286 -
2287 - if (parse_loose_header(st->hdr, &oi) < 0 || st->base.type < 0)
2288 - goto error;
2289 - st->base.size = size_ul;
2290 -
2291 - st->mapped = mapped;
2292 - st->mapsize = mapsize;
2293 - st->hdr_used = strlen(st->hdr) + 1;
2294 - st->hdr_avail = st->z.total_out;
2295 - st->z_state = ODB_LOOSE_READ_STREAM_INUSE;
2296 - st->base.close = close_istream_loose;
2297 - st->base.read = read_istream_loose;
2298 -
2299 - *out = &st->base;
2300 -
2301 - return 0;
2302 -error:
2303 - git_inflate_end(&st->z);
2304 - munmap(mapped, mapsize);
2305 - free(st);
2306 - return -1;
2307 -}
object-file.h
+26 -5
@@ -18,13 +18,8 @@ int index_fd(struct index_state *istate, struct object_id *oid, int fd, struct s
18 int index_path(struct index_state *istate, struct object_id *oid, const char *path, struct stat *st, unsigned flags);
19
20 struct object_info;
21 -struct odb_read_stream;
21 struct odb_source;
22
24 -int odb_source_loose_read_object_stream(struct odb_read_stream **out,
25 - struct odb_source *source,
26 - const struct object_id *oid);
27 -
23 /*
24 * Return true iff an object database source has a loose object
25 * with the specified name. This function does not respect replace
@@ -199,6 +194,32 @@ int read_object_info_from_path(struct odb_source_loose *loose,
194 struct object_info *oi,
195 enum object_info_flags flags);
196
197 +enum unpack_loose_header_result {
198 + ULHR_OK,
199 + ULHR_BAD,
200 + ULHR_TOO_LONG,
201 +};
202 +
203 +/**
204 + * unpack_loose_header() initializes the data stream needed to unpack
205 + * a loose object header.
206 + *
207 + * Returns:
208 + *
209 + * - ULHR_OK on success
210 + * - ULHR_BAD on error
211 + * - ULHR_TOO_LONG if the header was too long
212 + *
213 + * It will only parse up to MAX_HEADER_LEN bytes.
214 + */
215 +enum unpack_loose_header_result unpack_loose_header(git_zstream *stream,
216 + unsigned char *map,
217 + unsigned long mapsize,
218 + void *buffer,
219 + unsigned long bufsiz);
220 +
221 +int parse_loose_header(const char *hdr, struct object_info *oi);
222 +
223 struct odb_transaction;
224
225 /*
odb/source-files.c
+1 -1
@@ -67,7 +67,7 @@ static int odb_source_files_read_object_stream(struct odb_read_stream **out,
67 {
68 struct odb_source_files *files = odb_source_files_downcast(source);
69 if (!packfile_store_read_object_stream(out, files->packed, oid) ||
70 - !odb_source_loose_read_object_stream(out, source, oid))
70 + !odb_source_read_object_stream(out, &files->loose->base, oid))
71 return 0;
72 return -1;
73 }
odb/source-loose.c
+189
@@ -1,11 +1,13 @@
1 #include "git-compat-util.h"
2 #include "abspath.h"
3 #include "chdir-notify.h"
4 +#include "gettext.h"
5 #include "loose.h"
6 #include "object-file.h"
7 #include "odb.h"
8 #include "odb/source-files.h"
9 #include "odb/source-loose.h"
10 +#include "odb/streaming.h"
11 #include "oidtree.h"
12 #include "strbuf.h"
13
@@ -30,6 +32,192 @@ static int odb_source_loose_read_object_info(struct odb_source *source,
32 return read_object_info_from_path(loose, buf.buf, oid, oi, flags);
33 }
34
35 +/*
36 + * Find "oid" as a loose object in given source, open the object and return its
37 + * file descriptor. Returns the file descriptor on success, negative on failure.
38 + *
39 + * The "path" out-parameter will give the path of the object we found (if any).
40 + * Note that it may point to static storage and is only valid until another
41 + * call to open_loose_object().
42 + */
43 +static int open_loose_object(struct odb_source_loose *loose,
44 + const struct object_id *oid, const char **path)
45 +{
46 + static struct strbuf buf = STRBUF_INIT;
47 + int fd;
48 +
49 + *path = odb_loose_path(&loose->base, &buf, oid);
50 + fd = git_open(*path);
51 + if (fd >= 0)
52 + return fd;
53 +
54 + return -1;
55 +}
56 +
57 +static void *odb_source_loose_map_object(struct odb_source_loose *loose,
58 + const struct object_id *oid,
59 + unsigned long *size)
60 +{
61 + const char *p;
62 + int fd = open_loose_object(loose, oid, &p);
63 + void *map = NULL;
64 + struct stat st;
65 +
66 + if (fd < 0)
67 + return NULL;
68 +
69 + if (!fstat(fd, &st)) {
70 + *size = xsize_t(st.st_size);
71 + if (!*size) {
72 + /* mmap() is forbidden on empty files */
73 + error(_("object file %s is empty"), p);
74 + goto out;
75 + }
76 +
77 + map = xmmap(NULL, *size, PROT_READ, MAP_PRIVATE, fd, 0);
78 + }
79 +
80 +out:
81 + close(fd);
82 + return map;
83 +}
84 +
85 +struct odb_loose_read_stream {
86 + struct odb_read_stream base;
87 + git_zstream z;
88 + enum {
89 + ODB_LOOSE_READ_STREAM_INUSE,
90 + ODB_LOOSE_READ_STREAM_DONE,
91 + ODB_LOOSE_READ_STREAM_ERROR,
92 + } z_state;
93 + void *mapped;
94 + unsigned long mapsize;
95 + char hdr[32];
96 + int hdr_avail;
97 + int hdr_used;
98 +};
99 +
100 +static ssize_t read_istream_loose(struct odb_read_stream *_st, char *buf, size_t sz)
101 +{
102 + struct odb_loose_read_stream *st =
103 + container_of(_st, struct odb_loose_read_stream, base);
104 + size_t total_read = 0;
105 +
106 + switch (st->z_state) {
107 + case ODB_LOOSE_READ_STREAM_DONE:
108 + return 0;
109 + case ODB_LOOSE_READ_STREAM_ERROR:
110 + return -1;
111 + default:
112 + break;
113 + }
114 +
115 + if (st->hdr_used < st->hdr_avail) {
116 + size_t to_copy = st->hdr_avail - st->hdr_used;
117 + if (sz < to_copy)
118 + to_copy = sz;
119 + memcpy(buf, st->hdr + st->hdr_used, to_copy);
120 + st->hdr_used += to_copy;
121 + total_read += to_copy;
122 + }
123 +
124 + while (total_read < sz) {
125 + int status;
126 +
127 + st->z.next_out = (unsigned char *)buf + total_read;
128 + st->z.avail_out = sz - total_read;
129 + status = git_inflate(&st->z, Z_FINISH);
130 +
131 + total_read = st->z.next_out - (unsigned char *)buf;
132 +
133 + if (status == Z_STREAM_END) {
134 + git_inflate_end(&st->z);
135 + st->z_state = ODB_LOOSE_READ_STREAM_DONE;
136 + break;
137 + }
138 + if (status != Z_OK && (status != Z_BUF_ERROR || total_read < sz)) {
139 + git_inflate_end(&st->z);
140 + st->z_state = ODB_LOOSE_READ_STREAM_ERROR;
141 + return -1;
142 + }
143 + }
144 + return total_read;
145 +}
146 +
147 +static int close_istream_loose(struct odb_read_stream *_st)
148 +{
149 + struct odb_loose_read_stream *st =
150 + container_of(_st, struct odb_loose_read_stream, base);
151 +
152 + if (st->z_state == ODB_LOOSE_READ_STREAM_INUSE)
153 + git_inflate_end(&st->z);
154 + munmap(st->mapped, st->mapsize);
155 + return 0;
156 +}
157 +
158 +static int odb_source_loose_read_object_stream(struct odb_read_stream **out,
159 + struct odb_source *source,
160 + const struct object_id *oid)
161 +{
162 + struct odb_source_loose *loose = odb_source_loose_downcast(source);
163 + struct object_info oi = OBJECT_INFO_INIT;
164 + struct odb_loose_read_stream *st;
165 + unsigned long mapsize;
166 + unsigned long size_ul;
167 + void *mapped;
168 +
169 + mapped = odb_source_loose_map_object(loose, oid, &mapsize);
170 + if (!mapped)
171 + return -1;
172 +
173 + /*
174 + * Note: we must allocate this structure early even though we may still
175 + * fail. This is because we need to initialize the zlib stream, and it
176 + * is not possible to copy the stream around after the fact because it
177 + * has self-referencing pointers.
178 + */
179 + CALLOC_ARRAY(st, 1);
180 +
181 + switch (unpack_loose_header(&st->z, mapped, mapsize, st->hdr,
182 + sizeof(st->hdr))) {
183 + case ULHR_OK:
184 + break;
185 + case ULHR_BAD:
186 + case ULHR_TOO_LONG:
187 + goto error;
188 + }
189 +
190 + /*
191 + * object_info.sizep is unsigned long* (32-bit on Windows), but
192 + * st->base.size is size_t (64-bit). Use temporary variable.
193 + * Note: loose objects >4GB would still truncate here, but such
194 + * large loose objects are uncommon (they'd normally be packed).
195 + */
196 + oi.sizep = &size_ul;
197 + oi.typep = &st->base.type;
198 +
199 + if (parse_loose_header(st->hdr, &oi) < 0 || st->base.type < 0)
200 + goto error;
201 + st->base.size = size_ul;
202 +
203 + st->mapped = mapped;
204 + st->mapsize = mapsize;
205 + st->hdr_used = strlen(st->hdr) + 1;
206 + st->hdr_avail = st->z.total_out;
207 + st->z_state = ODB_LOOSE_READ_STREAM_INUSE;
208 + st->base.close = close_istream_loose;
209 + st->base.read = read_istream_loose;
210 +
211 + *out = &st->base;
212 +
213 + return 0;
214 +error:
215 + git_inflate_end(&st->z);
216 + munmap(mapped, mapsize);
217 + free(st);
218 + return -1;
219 +}
220 +
221 static void odb_source_loose_clear_cache(struct odb_source_loose *loose)
222 {
223 oidtree_clear(loose->cache);
@@ -84,6 +272,7 @@ struct odb_source_loose *odb_source_loose_new(struct odb_source_files *files)
272 loose->base.close = odb_source_loose_close;
273 loose->base.reprepare = odb_source_loose_reprepare;
274 loose->base.read_object_info = odb_source_loose_read_object_info;
275 + loose->base.read_object_stream = odb_source_loose_read_object_stream;
276
277 if (!is_absolute_path(loose->base.path))
278 chdir_notify_register(NULL, odb_source_loose_reparent, loose);