Originally, when designing pluggable object databases the goal was that
the object database can have multiple sources, and every source attached
to it could use a different backend. This would have allowed for quite a
lot of flexibility, as you could trivially mix and match different kinds
of object storages in whatever way you like.
But while well-intentioned, this design led to a bunch of conceptual
problems:
- We're now trying to read objects in source order, whereas we
previously tried to read objects via packfiles before trying to read
them via loose objects. This led to a performance regression when
using alternates or when using a quarantine directory.
- Some data structures are supposed to only ever exist once, like for
example bitmaps and commit graphs. At the same time, those data
structures also span across the union of all objects, so they may
cross sources.
- It is unclear how we can extend GIT_OBJECT_DIRECTORY or
GIT_ALTERNATE_OBJECT_DIRECTORIES to become backend-agnostic in a
backwards-compatible way. In general, introducing an object storage
extension into the current status quo where alternates may have to
be extended to become generic was proving to be painful.
- Some mechanisms of alternates assume way too much about how exactly
their backends work. Alternate refs for example assume that the
alternate is backed by a filesystem path, and that this filesystem
path may also allow us to read references. This is not a given
though, as backends may not even have local data at all.
In short, there are a bunch of conceptual mismatches when we have
alternates and pluggable object databases coexist. So while the original
idea was nice, it does not result in a system that is easy to reason
about.
Correct course by moving alternates into the "files" source itself so
that it becomes an implementation detail thereof so that we can avoid
all of these shortcomings. While it's unfortunate that we cannot easily
mix and match sources now, that ability doesn't go away. It's still very
much feasible to introduce a new backend that allows for exactly that
use case, and such a backend may also be a lot more flexible as we can
now add new logic to determine which objects should be stored where. So
the original motivation for having per-source backends can still be
realized with the new architecture.
Note that as part of this move, we also handle the GIT_OBJECT_DIRECTORY
and GIT_ALTERNATE_OBJECT_DIRECTORIES environment variables in the
"files" backend. This may be surprising at first, but object directories
are very much a concept of that backend, too. So these variables would
have bad interactions with other backends, and they create a bit of a
mismatch with the eventual object storage extension that we plan to
introduce.
Note that this commit is way larger than I'd like it to be. I'm sorry, I
couldn't find a way to split it up further. That being said, most of the
changes are straight-forward conversions that go from iterating over
sources to iterating over object directories. The more involved changes
are moving the infrastructure to track individual sources from "odb.c"
and moving them into "odb/source-files.c".
This also serves to show a bit of a who's-who of commands that don't
work properly with pluggable object databases. Almost all of these
commands are related to housekeeping though -- some of them will be
converted eventually, like for example commit graphs or MIDXs. But many
of them will stay incompatible going forward as they are simply too
specific to the "files" backend.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
362 lines
11 KiB
C
362 lines
11 KiB
C
#ifndef PACKFILE_H
|
|
#define PACKFILE_H
|
|
|
|
#include "list.h"
|
|
#include "object.h"
|
|
#include "odb.h"
|
|
#include "odb/source-files.h"
|
|
#include "odb/source-packed.h"
|
|
#include "oidset.h"
|
|
#include "packfile-list.h"
|
|
#include "repository.h"
|
|
|
|
/* in odb.h */
|
|
struct object_info;
|
|
struct odb_stream;
|
|
|
|
struct packed_git {
|
|
struct pack_window *windows;
|
|
off_t pack_size;
|
|
const void *index_data;
|
|
size_t index_size;
|
|
uint32_t num_objects;
|
|
size_t crc_offset;
|
|
struct oidset bad_objects;
|
|
int index_version;
|
|
time_t mtime;
|
|
int pack_fd;
|
|
int index; /* for builtin/pack-objects.c */
|
|
unsigned pack_local:1,
|
|
pack_keep:1,
|
|
pack_keep_in_core:1,
|
|
pack_keep_in_core_open:1,
|
|
freshened:1,
|
|
do_not_close:1,
|
|
pack_promisor:1,
|
|
multi_pack_index:1,
|
|
is_cruft:1;
|
|
unsigned char hash[GIT_MAX_RAWSZ];
|
|
struct revindex_entry *revindex;
|
|
const uint32_t *revindex_data;
|
|
const uint32_t *revindex_map;
|
|
size_t revindex_size;
|
|
/*
|
|
* mtimes_map points at the beginning of the memory mapped region of
|
|
* this pack's corresponding .mtimes file, and mtimes_size is the size
|
|
* of that .mtimes file
|
|
*/
|
|
const uint32_t *mtimes_map;
|
|
size_t mtimes_size;
|
|
|
|
/* repo denotes the repository this packfile belongs to */
|
|
struct repository *repo;
|
|
|
|
/* something like ".git/objects/pack/xxxxx.pack" */
|
|
char pack_name[FLEX_ARRAY]; /* more */
|
|
};
|
|
|
|
/*
|
|
* Add the pack to the store so that contained objects become accessible via
|
|
* the store. This moves ownership into the store.
|
|
*/
|
|
void packfile_store_add_pack(struct odb_source_packed *store,
|
|
struct packed_git *pack);
|
|
|
|
/*
|
|
* Get all packs managed by the given store, including packfiles that are
|
|
* referenced by multi-pack indices.
|
|
*/
|
|
struct packfile_list_entry *packfile_store_get_packs(struct odb_source_packed *store);
|
|
|
|
struct repo_for_each_pack_data {
|
|
struct odb_files_dir *dir;
|
|
struct packfile_list_entry *entry;
|
|
};
|
|
|
|
static inline struct repo_for_each_pack_data repo_for_eack_pack_data_init(struct repository *repo)
|
|
{
|
|
struct repo_for_each_pack_data data = { 0 };
|
|
struct odb_source_files *files = odb_source_files_downcast(repo->objects->source);
|
|
|
|
for (struct odb_files_dir *dir = files->dirs; dir; dir = dir->next) {
|
|
struct packfile_list_entry *entry = packfile_store_get_packs(dir->packed);
|
|
if (!entry)
|
|
continue;
|
|
data.dir = dir;
|
|
data.entry = entry;
|
|
break;
|
|
}
|
|
|
|
return data;
|
|
}
|
|
|
|
static inline void repo_for_each_pack_data_next(struct repo_for_each_pack_data *data)
|
|
{
|
|
struct odb_files_dir *dir;
|
|
|
|
data->entry = data->entry->next;
|
|
if (data->entry)
|
|
return;
|
|
|
|
for (dir = data->dir->next; dir; dir = dir->next) {
|
|
struct packfile_list_entry *entry = packfile_store_get_packs(dir->packed);
|
|
if (!entry)
|
|
continue;
|
|
data->dir = dir;
|
|
data->entry = entry;
|
|
return;
|
|
}
|
|
|
|
data->dir = NULL;
|
|
data->entry = NULL;
|
|
}
|
|
|
|
/*
|
|
* Load and iterate through all packs of the given repository. This helper
|
|
* function will yield packfiles from all object sources connected to the
|
|
* repository.
|
|
*/
|
|
#define repo_for_each_pack(repo, p) \
|
|
for (struct repo_for_each_pack_data eack_pack_data = repo_for_eack_pack_data_init(repo); \
|
|
((p) = (eack_pack_data.entry ? eack_pack_data.entry->pack : NULL)); \
|
|
repo_for_each_pack_data_next(&eack_pack_data))
|
|
|
|
/*
|
|
* Open the packfile and add it to the store if it isn't yet known. Returns
|
|
* either the newly opened packfile or the preexisting packfile. Returns a
|
|
* `NULL` pointer in case the packfile could not be opened.
|
|
*/
|
|
struct packed_git *packfile_store_load_pack(struct odb_source_packed *store,
|
|
const char *idx_path, int local);
|
|
|
|
enum kept_pack_type {
|
|
KEPT_PACK_ON_DISK = (1 << 0),
|
|
KEPT_PACK_IN_CORE = (1 << 1),
|
|
KEPT_PACK_IN_CORE_OPEN = (1 << 2),
|
|
};
|
|
|
|
/*
|
|
* Retrieve the cache of kept packs from the given packfile store. Accepts a
|
|
* combination of `kept_pack_type` flags. The cache is computed on demand and
|
|
* will be recomputed whenever the flags change.
|
|
*/
|
|
struct packed_git **packfile_store_get_kept_pack_cache(struct odb_source_packed *store,
|
|
unsigned flags);
|
|
|
|
struct pack_window {
|
|
struct pack_window *next;
|
|
unsigned char *base;
|
|
off_t offset;
|
|
size_t len;
|
|
unsigned int last_used;
|
|
unsigned int inuse_cnt;
|
|
};
|
|
|
|
struct pack_entry {
|
|
off_t offset;
|
|
struct packed_git *p;
|
|
};
|
|
|
|
/*
|
|
* Generate the filename to be used for a pack file with checksum "sha1" and
|
|
* extension "ext". The result is written into the strbuf "buf", overwriting
|
|
* any existing contents. A pointer to buf->buf is returned as a convenience.
|
|
*
|
|
* Example: odb_pack_name(out, sha1, "idx") => ".git/objects/pack/pack-1234..idx"
|
|
*/
|
|
char *odb_pack_name(struct repository *r, struct strbuf *buf,
|
|
const unsigned char *hash, const char *ext);
|
|
|
|
/*
|
|
* Return the basename of the packfile, omitting any containing directory
|
|
* (e.g., "pack-1234abcd[...].pack").
|
|
*/
|
|
const char *pack_basename(struct packed_git *p);
|
|
|
|
/*
|
|
* Parse the pack idx file found at idx_path and create a packed_git struct
|
|
* which can be used with find_pack_entry_one().
|
|
*
|
|
* You probably don't want to use this function! It skips most of the normal
|
|
* sanity checks (including whether we even have the matching .pack file),
|
|
* and does not add the resulting packed_git struct to the internal list of
|
|
* packs. You probably want add_packed_git() instead.
|
|
*/
|
|
struct packed_git *parse_pack_index(struct repository *r, unsigned char *sha1,
|
|
const char *idx_path);
|
|
|
|
typedef void each_file_in_pack_dir_fn(const char *full_path, size_t full_path_len,
|
|
const char *file_name, void *data);
|
|
void for_each_file_in_pack_subdir(const char *objdir,
|
|
const char *subdir,
|
|
each_file_in_pack_dir_fn fn,
|
|
void *data);
|
|
void for_each_file_in_pack_dir(const char *objdir,
|
|
each_file_in_pack_dir_fn fn,
|
|
void *data);
|
|
|
|
/*
|
|
* Iterate over all accessible packed objects without respect to reachability.
|
|
* By default, this includes both local and alternate packs.
|
|
*
|
|
* Note that some objects may appear twice if they are found in multiple packs.
|
|
* Each pack is visited in an unspecified order. By default, objects within a
|
|
* pack are visited in pack-idx order (i.e., sorted by oid).
|
|
*/
|
|
typedef int each_packed_object_fn(const struct object_id *oid,
|
|
struct packed_git *pack,
|
|
uint32_t pos,
|
|
void *data);
|
|
int for_each_object_in_pack(struct packed_git *p,
|
|
each_packed_object_fn, void *data,
|
|
enum odb_for_each_object_flags flags);
|
|
|
|
/* A hook to report invalid files in pack directory */
|
|
#define PACKDIR_FILE_PACK 1
|
|
#define PACKDIR_FILE_IDX 2
|
|
#define PACKDIR_FILE_GARBAGE 4
|
|
extern void (*report_garbage)(unsigned seen_bits, const char *path);
|
|
|
|
void pack_report(struct repository *repo);
|
|
|
|
/*
|
|
* mmap the index file for the specified packfile (if it is not
|
|
* already mmapped). Return 0 on success.
|
|
*/
|
|
int open_pack_index(struct packed_git *);
|
|
|
|
/*
|
|
* munmap the index file for the specified packfile (if it is
|
|
* currently mmapped).
|
|
*/
|
|
void close_pack_index(struct packed_git *);
|
|
|
|
int close_pack_fd(struct packed_git *p);
|
|
|
|
uint32_t get_pack_fanout(struct packed_git *p, uint32_t value);
|
|
|
|
struct object_database;
|
|
|
|
unsigned char *use_pack(struct packed_git *, struct pack_window **, off_t,
|
|
size_t *);
|
|
void close_pack_windows(struct packed_git *);
|
|
void close_pack(struct packed_git *);
|
|
void unuse_pack(struct pack_window **);
|
|
void clear_delta_base_cache(void);
|
|
struct packed_git *add_packed_git(struct repository *r, const char *path,
|
|
size_t path_len, int local);
|
|
|
|
/*
|
|
* Unlink the .pack and associated extension files.
|
|
* Does not unlink if 'force_delete' is false and the pack-file is
|
|
* marked as ".keep".
|
|
*/
|
|
void unlink_pack_path(const char *pack_name, int force_delete);
|
|
|
|
/*
|
|
* Make sure that a pointer access into an mmap'd index file is within bounds,
|
|
* and can provide at least 8 bytes of data.
|
|
*
|
|
* Note that this is only necessary for variable-length segments of the file
|
|
* (like the 64-bit extended offset table), as we compare the size to the
|
|
* fixed-length parts when we open the file.
|
|
*/
|
|
void check_pack_index_ptr(const struct packed_git *p, const void *ptr);
|
|
|
|
/*
|
|
* Perform binary search on a pack-index for a given oid. Packfile is expected to
|
|
* have a valid pack-index.
|
|
*
|
|
* See 'bsearch_hash' for more information.
|
|
*/
|
|
int bsearch_pack(const struct object_id *oid, const struct packed_git *p, uint32_t *result);
|
|
|
|
/*
|
|
* Write the oid of the nth object within the specified packfile into the first
|
|
* parameter. Open the index if it is not already open. Returns 0 on success,
|
|
* negative otherwise.
|
|
*/
|
|
int nth_packed_object_id(struct object_id *, struct packed_git *, uint32_t n);
|
|
|
|
/*
|
|
* Return the offset of the nth object within the specified packfile.
|
|
* The index must already be opened.
|
|
*/
|
|
off_t nth_packed_object_offset(const struct packed_git *, uint32_t n);
|
|
|
|
/*
|
|
* If the object named by oid is present in the specified packfile,
|
|
* return its offset within the packfile; otherwise, return 0.
|
|
*/
|
|
off_t find_pack_entry_one(const struct object_id *oid, struct packed_git *);
|
|
|
|
int packfile_fill_entry(struct packed_git *p,
|
|
const struct object_id *oid,
|
|
struct pack_entry *e,
|
|
struct packed_git **bad_pack);
|
|
|
|
int is_pack_valid(struct packed_git *);
|
|
void *unpack_entry(struct repository *r, struct packed_git *, off_t,
|
|
enum object_type *, size_t *);
|
|
size_t unpack_object_header_buffer(const unsigned char *buf, size_t len,
|
|
enum object_type *type, size_t *sizep);
|
|
size_t get_size_from_delta(struct packed_git *, struct pack_window **, off_t);
|
|
int unpack_object_header(struct packed_git *, struct pack_window **, off_t *, size_t *);
|
|
off_t get_delta_base(struct packed_git *p, struct pack_window **w_curs,
|
|
off_t *curpos, enum object_type type,
|
|
off_t delta_obj_offset);
|
|
|
|
int packfile_read_object_stream(struct odb_stream **out,
|
|
const struct object_id *oid,
|
|
struct packed_git *pack,
|
|
off_t offset);
|
|
|
|
void release_pack_memory(size_t);
|
|
|
|
/* global flag to enable extra checks when accessing packed objects */
|
|
extern int do_check_packed_object_crc;
|
|
|
|
/*
|
|
* Look up the object info for a specific offset in the packfile.
|
|
* Returns zero on success, a negative error code otherwise.
|
|
*/
|
|
int packed_object_info(struct odb_source_packed *source,
|
|
struct packed_git *pack,
|
|
off_t offset, struct object_info *);
|
|
int packed_object_info_with_index_pos(struct odb_source_packed *source,
|
|
struct packed_git *p, off_t obj_offset,
|
|
uint32_t *maybe_index_pos, struct object_info *oi);
|
|
|
|
void mark_bad_packed_object(struct packed_git *, const struct object_id *);
|
|
|
|
int has_object_pack(struct repository *r, const struct object_id *oid);
|
|
int has_object_kept_pack(struct repository *r, const struct object_id *oid,
|
|
unsigned flags);
|
|
|
|
/*
|
|
* Return 1 if an object in a promisor packfile is or refers to the given
|
|
* object, 0 otherwise.
|
|
*/
|
|
int is_promisor_object(struct repository *r, const struct object_id *oid);
|
|
|
|
/*
|
|
* Expose a function for fuzz testing.
|
|
*
|
|
* load_idx() parses a block of memory as a packfile index and puts the results
|
|
* into a struct packed_git.
|
|
*
|
|
* This function should not be used directly. It is exposed here only so that we
|
|
* have a convenient entry-point for fuzz testing. For real uses, you should
|
|
* probably use open_pack_index() instead.
|
|
*/
|
|
int load_idx(const char *path, const unsigned int hashsz, void *idx_map,
|
|
size_t idx_size, struct packed_git *p);
|
|
|
|
/*
|
|
* Parse a --pack_header option as accepted by index-pack and unpack-objects,
|
|
* turning it into the matching bytes we'd find in a pack.
|
|
*/
|
|
int parse_pack_header_option(const char *in, unsigned char *out, unsigned int *len);
|
|
|
|
#endif
|