[OCaml] Mobile-friendly clone of cgit.
fix read packed-refs for branch resolution and listing
Store.Ref.list only enumerates loose reference files; repositories that have been garbage-collected or received as bare clones keep all refs exclusively in packed-refs, making them invisible to it. Add read_packed_refs, a shared parser for the packed-refs file, and use it in two places: - resolve_head_hash: when Store.Ref.list returns no branches, also consult packed-refs so that the last-resort fallback can find a resolvable branch. - Reference.refs_by_prefix: always merge loose refs with packed-refs entries (loose wins on duplicates) so that branches and tags appear in the UI regardless of storage format. Fixes HTTP 500 on repositories served only from pack files.
lib/resolvers.ml
@@ -208,6 +208,46 @@
208
208
let fallback_branch_candidates config =
209
209
fallback_branch_candidates_for config.Config.default_branch
210
210
211
Added:
(** Parse the [packed-refs] file and return all entries as [(hex, refname)]
212
Added:
pairs. This is the shared primitive used both by [resolve_head_hash] (which
213
Added:
needs branch references for its fallback) and by [Reference.refs_by_prefix]
214
Added:
(which enumerates branches and tags for the UI).
215
Added:
216
Added:
[Store.Ref.list] only walks the filesystem for loose reference files; when a
217
Added:
repository has been garbage-collected or was received as a pack, all refs
218
Added:
live exclusively in [packed-refs] and are invisible to it. *)
219
Added:
let read_packed_refs store =
220
Added:
let dotgit = Fpath.to_string (Store.dotgit store) in
221
Added:
let path = Filename.concat dotgit "packed-refs" in
222
Added:
try
223
Added:
In_channel.with_open_text path @@ fun ic ->
224
Added:
let rec collect acc =
225
Added:
match In_channel.input_line ic with
226
Added:
| None -> List.rev acc
227
Added:
| Some line when String.length line = 0 -> collect acc
228
Added:
| Some line when line.[0] = '#' || line.[0] = '^' -> collect acc
229
Added:
| Some line -> (
230
Added:
match String.index_opt line ' ' with
231
Added:
| None -> collect acc
232
Added:
| Some i ->
233
Added:
let hex = String.sub line 0 i in
234
Added:
let refname =
235
Added:
String.sub line (i + 1) (String.length line - i - 1)
236
Added:
in
237
Added:
collect ((hex, refname) :: acc))
238
Added:
in
239
Added:
collect []
240
Added:
with Sys_error _ -> []
241
Added:
242
Added:
(** Branch entries from [packed-refs], returned as [(full_refname, reference)]
243
Added:
pairs suitable for [try_references]. *)
244
Added:
let packed_refs_branches store =
245
Added:
read_packed_refs store
246
Added:
|> List.filter_map (fun (_hex, refname) ->
247
Added:
if String.starts_with ~prefix:"refs/heads/" refname then
248
Added:
Some (refname, Git.Reference.v refname)
249
Added:
else None)
250
Added:
211
251
let resolve_head_hash repository =
212
252
let resolve reference =
213
253
map_store (Store.Ref.resolve repository.store reference)
@@ -223,8 +263,18 @@
223
263
if String.starts_with ~prefix:"refs/heads/" name then
224
264
Some (name, reference)
225
265
else None)
226
Removed:
|> List.sort (fun (left, _) (right, _) -> String.compare left right)
227
266
in
267
Added:
(* When the store lists no loose branches, also consult packed-refs
268
Added:
so that repositories with only packed references are handled. *)
269
Added:
let branches =
270
Added:
if branches = [] then packed_refs_branches repository.store
271
Added:
else branches
272
Added:
in
273
Added:
let branches =
274
Added:
List.sort
275
Added:
(fun (left, _) (right, _) -> String.compare left right)
276
Added:
branches
277
Added:
in
228
278
let rec try_branches = function
229
279
| [] ->
230
280
Lwt_result.fail
@@ -376,14 +426,33 @@
376
426
let tag_name name = drop_prefix ~prefix:"refs/tags/" name
377
427
let to_t_with_name name (_, hash) = { name; hash = Store.Hash.to_hex hash }
378
428
429
Added:
(** Enumerate references matching a given prefix, combining both loose
430
Added:
references (from [Store.Ref.list]) and packed references read directly
431
Added:
from the [packed-refs] file. Duplicates are removed by preferring the
432
Added:
loose entry (which takes precedence in git's resolution order). *)
379
433
let refs_by_prefix repository name_of_reference =
380
434
let open Lwt.Syntax in
381
435
let* references = Store.Ref.list repository.store in
382
Removed:
references
383
Removed:
|> List.filter_map (fun ((reference, _) as raw) ->
384
Removed:
Git.Reference.to_string reference
385
Removed:
|> name_of_reference
386
Removed:
|> Option.map (fun name -> to_t_with_name name raw))
436
Added:
let from_loose =
437
Added:
references
438
Added:
|> List.filter_map (fun ((reference, _) as raw) ->
439
Added:
Git.Reference.to_string reference
440
Added:
|> name_of_reference
441
Added:
|> Option.map (fun name -> to_t_with_name name raw))
442
Added:
in
443
Added:
let from_packed =
444
Added:
read_packed_refs repository.store
445
Added:
|> List.filter_map (fun (hex, refname) ->
446
Added:
name_of_reference refname
447
Added:
|> Option.map (fun name -> { name; hash = hex }))
448
Added:
in
449
Added:
(* Merge: loose refs win over packed refs for the same name *)
450
Added:
let loose_names = List.map (fun r -> r.name) from_loose in
451
Added:
let merged =
452
Added:
from_loose
453
Added:
@ List.filter (fun r -> not (List.mem r.name loose_names)) from_packed
454
Added:
in
455
Added:
merged
387
456
|> List.sort (fun left right -> String.compare left.name right.name)
388
457
|> Lwt_result.return
389
458