mirror of
https://github.com/git/git.git
synced 2024-09-28 05:20:00 +02:00
Merge branch 'mh/packed-refs-various'
Update reading and updating packed-refs file, correcting corner case bugs. * mh/packed-refs-various: (33 commits) refs: handle the main ref_cache specially refs: change do_for_each_*() functions to take ref_cache arguments pack_one_ref(): do some cheap tests before a more expensive one pack_one_ref(): use write_packed_entry() to do the writing pack_one_ref(): use function peel_entry() refs: inline function do_not_prune() pack_refs(): change to use do_for_each_entry() refs: use same lock_file object for both ref-packing functions pack_one_ref(): rename "path" parameter to "refname" pack-refs: merge code from pack-refs.{c,h} into refs.{c,h} pack-refs: rename handle_one_ref() to pack_one_ref() refs: extract a function write_packed_entry() repack_without_ref(): write peeled refs in the rewritten file t3211: demonstrate loss of peeled refs if a packed ref is deleted refs: change how packed refs are deleted search_ref_dir(): return an index rather than a pointer repack_without_ref(): silence errors for dangling packed refs t3210: test for spurious error messages for dangling packed refs refs: change the internal reference-iteration API refs: extract a function peel_entry() ...
This commit is contained in:
commit
2f1ef15070
2
Makefile
2
Makefile
@ -685,7 +685,6 @@ LIB_H += notes-cache.h
|
||||
LIB_H += notes-merge.h
|
||||
LIB_H += notes.h
|
||||
LIB_H += object.h
|
||||
LIB_H += pack-refs.h
|
||||
LIB_H += pack-revindex.h
|
||||
LIB_H += pack.h
|
||||
LIB_H += parse-options.h
|
||||
@ -818,7 +817,6 @@ LIB_OBJS += notes-cache.o
|
||||
LIB_OBJS += notes-merge.o
|
||||
LIB_OBJS += object.o
|
||||
LIB_OBJS += pack-check.o
|
||||
LIB_OBJS += pack-refs.o
|
||||
LIB_OBJS += pack-revindex.o
|
||||
LIB_OBJS += pack-write.o
|
||||
LIB_OBJS += pager.o
|
||||
|
@ -18,7 +18,6 @@
|
||||
#include "transport.h"
|
||||
#include "strbuf.h"
|
||||
#include "dir.h"
|
||||
#include "pack-refs.h"
|
||||
#include "sigchain.h"
|
||||
#include "branch.h"
|
||||
#include "remote.h"
|
||||
|
@ -1,6 +1,6 @@
|
||||
#include "builtin.h"
|
||||
#include "parse-options.h"
|
||||
#include "pack-refs.h"
|
||||
#include "refs.h"
|
||||
|
||||
static char const * const pack_refs_usage[] = {
|
||||
N_("git pack-refs [options]"),
|
||||
|
148
pack-refs.c
148
pack-refs.c
@ -1,148 +0,0 @@
|
||||
#include "cache.h"
|
||||
#include "refs.h"
|
||||
#include "tag.h"
|
||||
#include "pack-refs.h"
|
||||
|
||||
struct ref_to_prune {
|
||||
struct ref_to_prune *next;
|
||||
unsigned char sha1[20];
|
||||
char name[FLEX_ARRAY];
|
||||
};
|
||||
|
||||
struct pack_refs_cb_data {
|
||||
unsigned int flags;
|
||||
struct ref_to_prune *ref_to_prune;
|
||||
FILE *refs_file;
|
||||
};
|
||||
|
||||
static int do_not_prune(int flags)
|
||||
{
|
||||
/* If it is already packed or if it is a symref,
|
||||
* do not prune it.
|
||||
*/
|
||||
return (flags & (REF_ISSYMREF|REF_ISPACKED));
|
||||
}
|
||||
|
||||
static int handle_one_ref(const char *path, const unsigned char *sha1,
|
||||
int flags, void *cb_data)
|
||||
{
|
||||
struct pack_refs_cb_data *cb = cb_data;
|
||||
struct object *o;
|
||||
int is_tag_ref;
|
||||
|
||||
/* Do not pack the symbolic refs */
|
||||
if ((flags & REF_ISSYMREF))
|
||||
return 0;
|
||||
is_tag_ref = !prefixcmp(path, "refs/tags/");
|
||||
|
||||
/* ALWAYS pack refs that were already packed or are tags */
|
||||
if (!(cb->flags & PACK_REFS_ALL) && !is_tag_ref && !(flags & REF_ISPACKED))
|
||||
return 0;
|
||||
|
||||
fprintf(cb->refs_file, "%s %s\n", sha1_to_hex(sha1), path);
|
||||
|
||||
o = parse_object_or_die(sha1, path);
|
||||
if (o->type == OBJ_TAG) {
|
||||
o = deref_tag(o, path, 0);
|
||||
if (o)
|
||||
fprintf(cb->refs_file, "^%s\n",
|
||||
sha1_to_hex(o->sha1));
|
||||
}
|
||||
|
||||
if ((cb->flags & PACK_REFS_PRUNE) && !do_not_prune(flags)) {
|
||||
int namelen = strlen(path) + 1;
|
||||
struct ref_to_prune *n = xcalloc(1, sizeof(*n) + namelen);
|
||||
hashcpy(n->sha1, sha1);
|
||||
strcpy(n->name, path);
|
||||
n->next = cb->ref_to_prune;
|
||||
cb->ref_to_prune = n;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Remove empty parents, but spare refs/ and immediate subdirs.
|
||||
* Note: munges *name.
|
||||
*/
|
||||
static void try_remove_empty_parents(char *name)
|
||||
{
|
||||
char *p, *q;
|
||||
int i;
|
||||
p = name;
|
||||
for (i = 0; i < 2; i++) { /* refs/{heads,tags,...}/ */
|
||||
while (*p && *p != '/')
|
||||
p++;
|
||||
/* tolerate duplicate slashes; see check_refname_format() */
|
||||
while (*p == '/')
|
||||
p++;
|
||||
}
|
||||
for (q = p; *q; q++)
|
||||
;
|
||||
while (1) {
|
||||
while (q > p && *q != '/')
|
||||
q--;
|
||||
while (q > p && *(q-1) == '/')
|
||||
q--;
|
||||
if (q == p)
|
||||
break;
|
||||
*q = '\0';
|
||||
if (rmdir(git_path("%s", name)))
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* make sure nobody touched the ref, and unlink */
|
||||
static void prune_ref(struct ref_to_prune *r)
|
||||
{
|
||||
struct ref_lock *lock = lock_ref_sha1(r->name + 5, r->sha1);
|
||||
|
||||
if (lock) {
|
||||
unlink_or_warn(git_path("%s", r->name));
|
||||
unlock_ref(lock);
|
||||
try_remove_empty_parents(r->name);
|
||||
}
|
||||
}
|
||||
|
||||
static void prune_refs(struct ref_to_prune *r)
|
||||
{
|
||||
while (r) {
|
||||
prune_ref(r);
|
||||
r = r->next;
|
||||
}
|
||||
}
|
||||
|
||||
static struct lock_file packed;
|
||||
|
||||
int pack_refs(unsigned int flags)
|
||||
{
|
||||
int fd;
|
||||
struct pack_refs_cb_data cbdata;
|
||||
|
||||
memset(&cbdata, 0, sizeof(cbdata));
|
||||
cbdata.flags = flags;
|
||||
|
||||
fd = hold_lock_file_for_update(&packed, git_path("packed-refs"),
|
||||
LOCK_DIE_ON_ERROR);
|
||||
cbdata.refs_file = fdopen(fd, "w");
|
||||
if (!cbdata.refs_file)
|
||||
die_errno("unable to create ref-pack file structure");
|
||||
|
||||
/* perhaps other traits later as well */
|
||||
fprintf(cbdata.refs_file, "# pack-refs with: peeled fully-peeled \n");
|
||||
|
||||
for_each_ref(handle_one_ref, &cbdata);
|
||||
if (ferror(cbdata.refs_file))
|
||||
die("failed to write ref-pack file");
|
||||
if (fflush(cbdata.refs_file) || fsync(fd) || fclose(cbdata.refs_file))
|
||||
die_errno("failed to write ref-pack file");
|
||||
/*
|
||||
* Since the lock file was fdopen()'ed and then fclose()'ed above,
|
||||
* assign -1 to the lock file descriptor so that commit_lock_file()
|
||||
* won't try to close() it.
|
||||
*/
|
||||
packed.fd = -1;
|
||||
if (commit_lock_file(&packed) < 0)
|
||||
die_errno("unable to overwrite old ref-pack file");
|
||||
prune_refs(cbdata.ref_to_prune);
|
||||
return 0;
|
||||
}
|
18
pack-refs.h
18
pack-refs.h
@ -1,18 +0,0 @@
|
||||
#ifndef PACK_REFS_H
|
||||
#define PACK_REFS_H
|
||||
|
||||
/*
|
||||
* Flags for controlling behaviour of pack_refs()
|
||||
* PACK_REFS_PRUNE: Prune loose refs after packing
|
||||
* PACK_REFS_ALL: Pack _all_ refs, not just tags and already packed refs
|
||||
*/
|
||||
#define PACK_REFS_PRUNE 0x0001
|
||||
#define PACK_REFS_ALL 0x0002
|
||||
|
||||
/*
|
||||
* Write a packed-refs file for the current repository.
|
||||
* flags: Combination of the above PACK_REFS_* flags.
|
||||
*/
|
||||
int pack_refs(unsigned int flags);
|
||||
|
||||
#endif /* PACK_REFS_H */
|
733
refs.c
733
refs.c
@ -109,7 +109,20 @@ struct ref_entry;
|
||||
* (ref_entry->flag & REF_DIR) is zero.
|
||||
*/
|
||||
struct ref_value {
|
||||
/*
|
||||
* The name of the object to which this reference resolves
|
||||
* (which may be a tag object). If REF_ISBROKEN, this is
|
||||
* null. If REF_ISSYMREF, then this is the name of the object
|
||||
* referred to by the last reference in the symlink chain.
|
||||
*/
|
||||
unsigned char sha1[20];
|
||||
|
||||
/*
|
||||
* If REF_KNOWS_PEELED, then this field holds the peeled value
|
||||
* of this reference, or null if the reference is known not to
|
||||
* be peelable. See the documentation for peel_ref() for an
|
||||
* exact definition of "peelable".
|
||||
*/
|
||||
unsigned char peeled[20];
|
||||
};
|
||||
|
||||
@ -158,7 +171,17 @@ struct ref_dir {
|
||||
struct ref_entry **entries;
|
||||
};
|
||||
|
||||
/* ISSYMREF=0x01, ISPACKED=0x02, and ISBROKEN=0x04 are public interfaces */
|
||||
/*
|
||||
* Bit values for ref_entry::flag. REF_ISSYMREF=0x01,
|
||||
* REF_ISPACKED=0x02, and REF_ISBROKEN=0x04 are public values; see
|
||||
* refs.h.
|
||||
*/
|
||||
|
||||
/*
|
||||
* The field ref_entry->u.value.peeled of this value entry contains
|
||||
* the correct peeled value for the reference, which might be
|
||||
* null_sha1 if the reference is not a tag or if it is broken.
|
||||
*/
|
||||
#define REF_KNOWS_PEELED 0x08
|
||||
|
||||
/* ref_entry represents a directory of references */
|
||||
@ -343,18 +366,17 @@ static int ref_entry_cmp_sslice(const void *key_, const void *ent_)
|
||||
}
|
||||
|
||||
/*
|
||||
* Return the entry with the given refname from the ref_dir
|
||||
* (non-recursively), sorting dir if necessary. Return NULL if no
|
||||
* such entry is found. dir must already be complete.
|
||||
* Return the index of the entry with the given refname from the
|
||||
* ref_dir (non-recursively), sorting dir if necessary. Return -1 if
|
||||
* no such entry is found. dir must already be complete.
|
||||
*/
|
||||
static struct ref_entry *search_ref_dir(struct ref_dir *dir,
|
||||
const char *refname, size_t len)
|
||||
static int search_ref_dir(struct ref_dir *dir, const char *refname, size_t len)
|
||||
{
|
||||
struct ref_entry **r;
|
||||
struct string_slice key;
|
||||
|
||||
if (refname == NULL || !dir->nr)
|
||||
return NULL;
|
||||
return -1;
|
||||
|
||||
sort_ref_dir(dir);
|
||||
key.len = len;
|
||||
@ -363,9 +385,9 @@ static struct ref_entry *search_ref_dir(struct ref_dir *dir,
|
||||
ref_entry_cmp_sslice);
|
||||
|
||||
if (r == NULL)
|
||||
return NULL;
|
||||
return -1;
|
||||
|
||||
return *r;
|
||||
return r - dir->entries;
|
||||
}
|
||||
|
||||
/*
|
||||
@ -379,8 +401,9 @@ static struct ref_dir *search_for_subdir(struct ref_dir *dir,
|
||||
const char *subdirname, size_t len,
|
||||
int mkdir)
|
||||
{
|
||||
struct ref_entry *entry = search_ref_dir(dir, subdirname, len);
|
||||
if (!entry) {
|
||||
int entry_index = search_ref_dir(dir, subdirname, len);
|
||||
struct ref_entry *entry;
|
||||
if (entry_index == -1) {
|
||||
if (!mkdir)
|
||||
return NULL;
|
||||
/*
|
||||
@ -391,6 +414,8 @@ static struct ref_dir *search_for_subdir(struct ref_dir *dir,
|
||||
*/
|
||||
entry = create_dir_entry(dir->ref_cache, subdirname, len, 0);
|
||||
add_entry_to_dir(dir, entry);
|
||||
} else {
|
||||
entry = dir->entries[entry_index];
|
||||
}
|
||||
return get_ref_dir(entry);
|
||||
}
|
||||
@ -429,12 +454,67 @@ static struct ref_dir *find_containing_dir(struct ref_dir *dir,
|
||||
*/
|
||||
static struct ref_entry *find_ref(struct ref_dir *dir, const char *refname)
|
||||
{
|
||||
int entry_index;
|
||||
struct ref_entry *entry;
|
||||
dir = find_containing_dir(dir, refname, 0);
|
||||
if (!dir)
|
||||
return NULL;
|
||||
entry = search_ref_dir(dir, refname, strlen(refname));
|
||||
return (entry && !(entry->flag & REF_DIR)) ? entry : NULL;
|
||||
entry_index = search_ref_dir(dir, refname, strlen(refname));
|
||||
if (entry_index == -1)
|
||||
return NULL;
|
||||
entry = dir->entries[entry_index];
|
||||
return (entry->flag & REF_DIR) ? NULL : entry;
|
||||
}
|
||||
|
||||
/*
|
||||
* Remove the entry with the given name from dir, recursing into
|
||||
* subdirectories as necessary. If refname is the name of a directory
|
||||
* (i.e., ends with '/'), then remove the directory and its contents.
|
||||
* If the removal was successful, return the number of entries
|
||||
* remaining in the directory entry that contained the deleted entry.
|
||||
* If the name was not found, return -1. Please note that this
|
||||
* function only deletes the entry from the cache; it does not delete
|
||||
* it from the filesystem or ensure that other cache entries (which
|
||||
* might be symbolic references to the removed entry) are updated.
|
||||
* Nor does it remove any containing dir entries that might be made
|
||||
* empty by the removal. dir must represent the top-level directory
|
||||
* and must already be complete.
|
||||
*/
|
||||
static int remove_entry(struct ref_dir *dir, const char *refname)
|
||||
{
|
||||
int refname_len = strlen(refname);
|
||||
int entry_index;
|
||||
struct ref_entry *entry;
|
||||
int is_dir = refname[refname_len - 1] == '/';
|
||||
if (is_dir) {
|
||||
/*
|
||||
* refname represents a reference directory. Remove
|
||||
* the trailing slash; otherwise we will get the
|
||||
* directory *representing* refname rather than the
|
||||
* one *containing* it.
|
||||
*/
|
||||
char *dirname = xmemdupz(refname, refname_len - 1);
|
||||
dir = find_containing_dir(dir, dirname, 0);
|
||||
free(dirname);
|
||||
} else {
|
||||
dir = find_containing_dir(dir, refname, 0);
|
||||
}
|
||||
if (!dir)
|
||||
return -1;
|
||||
entry_index = search_ref_dir(dir, refname, refname_len);
|
||||
if (entry_index == -1)
|
||||
return -1;
|
||||
entry = dir->entries[entry_index];
|
||||
|
||||
memmove(&dir->entries[entry_index],
|
||||
&dir->entries[entry_index + 1],
|
||||
(dir->nr - entry_index - 1) * sizeof(*dir->entries)
|
||||
);
|
||||
dir->nr--;
|
||||
if (dir->sorted > entry_index)
|
||||
dir->sorted--;
|
||||
free_ref_entry(entry);
|
||||
return dir->nr;
|
||||
}
|
||||
|
||||
/*
|
||||
@ -503,27 +583,64 @@ static void sort_ref_dir(struct ref_dir *dir)
|
||||
dir->sorted = dir->nr = i;
|
||||
}
|
||||
|
||||
#define DO_FOR_EACH_INCLUDE_BROKEN 01
|
||||
/* Include broken references in a do_for_each_ref*() iteration: */
|
||||
#define DO_FOR_EACH_INCLUDE_BROKEN 0x01
|
||||
|
||||
/*
|
||||
* Return true iff the reference described by entry can be resolved to
|
||||
* an object in the database. Emit a warning if the referred-to
|
||||
* object does not exist.
|
||||
*/
|
||||
static int ref_resolves_to_object(struct ref_entry *entry)
|
||||
{
|
||||
if (entry->flag & REF_ISBROKEN)
|
||||
return 0;
|
||||
if (!has_sha1_file(entry->u.value.sha1)) {
|
||||
error("%s does not point to a valid object!", entry->name);
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* current_ref is a performance hack: when iterating over references
|
||||
* using the for_each_ref*() functions, current_ref is set to the
|
||||
* current reference's entry before calling the callback function. If
|
||||
* the callback function calls peel_ref(), then peel_ref() first
|
||||
* checks whether the reference to be peeled is the current reference
|
||||
* (it usually is) and if so, returns that reference's peeled version
|
||||
* if it is available. This avoids a refname lookup in a common case.
|
||||
*/
|
||||
static struct ref_entry *current_ref;
|
||||
|
||||
static int do_one_ref(const char *base, each_ref_fn fn, int trim,
|
||||
int flags, void *cb_data, struct ref_entry *entry)
|
||||
typedef int each_ref_entry_fn(struct ref_entry *entry, void *cb_data);
|
||||
|
||||
struct ref_entry_cb {
|
||||
const char *base;
|
||||
int trim;
|
||||
int flags;
|
||||
each_ref_fn *fn;
|
||||
void *cb_data;
|
||||
};
|
||||
|
||||
/*
|
||||
* Handle one reference in a do_for_each_ref*()-style iteration,
|
||||
* calling an each_ref_fn for each entry.
|
||||
*/
|
||||
static int do_one_ref(struct ref_entry *entry, void *cb_data)
|
||||
{
|
||||
struct ref_entry_cb *data = cb_data;
|
||||
int retval;
|
||||
if (prefixcmp(entry->name, base))
|
||||
if (prefixcmp(entry->name, data->base))
|
||||
return 0;
|
||||
|
||||
if (!(data->flags & DO_FOR_EACH_INCLUDE_BROKEN) &&
|
||||
!ref_resolves_to_object(entry))
|
||||
return 0;
|
||||
|
||||
if (!(flags & DO_FOR_EACH_INCLUDE_BROKEN)) {
|
||||
if (entry->flag & REF_ISBROKEN)
|
||||
return 0; /* ignore broken refs e.g. dangling symref */
|
||||
if (!has_sha1_file(entry->u.value.sha1)) {
|
||||
error("%s does not point to a valid object!", entry->name);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
current_ref = entry;
|
||||
retval = fn(entry->name + trim, entry->u.value.sha1, entry->flag, cb_data);
|
||||
retval = data->fn(entry->name + data->trim, entry->u.value.sha1,
|
||||
entry->flag, data->cb_data);
|
||||
current_ref = NULL;
|
||||
return retval;
|
||||
}
|
||||
@ -532,11 +649,11 @@ static int do_one_ref(const char *base, each_ref_fn fn, int trim,
|
||||
* Call fn for each reference in dir that has index in the range
|
||||
* offset <= index < dir->nr. Recurse into subdirectories that are in
|
||||
* that index range, sorting them before iterating. This function
|
||||
* does not sort dir itself; it should be sorted beforehand.
|
||||
* does not sort dir itself; it should be sorted beforehand. fn is
|
||||
* called for all references, including broken ones.
|
||||
*/
|
||||
static int do_for_each_ref_in_dir(struct ref_dir *dir, int offset,
|
||||
const char *base,
|
||||
each_ref_fn fn, int trim, int flags, void *cb_data)
|
||||
static int do_for_each_entry_in_dir(struct ref_dir *dir, int offset,
|
||||
each_ref_entry_fn fn, void *cb_data)
|
||||
{
|
||||
int i;
|
||||
assert(dir->sorted == dir->nr);
|
||||
@ -546,10 +663,9 @@ static int do_for_each_ref_in_dir(struct ref_dir *dir, int offset,
|
||||
if (entry->flag & REF_DIR) {
|
||||
struct ref_dir *subdir = get_ref_dir(entry);
|
||||
sort_ref_dir(subdir);
|
||||
retval = do_for_each_ref_in_dir(subdir, 0,
|
||||
base, fn, trim, flags, cb_data);
|
||||
retval = do_for_each_entry_in_dir(subdir, 0, fn, cb_data);
|
||||
} else {
|
||||
retval = do_one_ref(base, fn, trim, flags, cb_data, entry);
|
||||
retval = fn(entry, cb_data);
|
||||
}
|
||||
if (retval)
|
||||
return retval;
|
||||
@ -562,12 +678,12 @@ static int do_for_each_ref_in_dir(struct ref_dir *dir, int offset,
|
||||
* by refname. Recurse into subdirectories. If a value entry appears
|
||||
* in both dir1 and dir2, then only process the version that is in
|
||||
* dir2. The input dirs must already be sorted, but subdirs will be
|
||||
* sorted as needed.
|
||||
* sorted as needed. fn is called for all references, including
|
||||
* broken ones.
|
||||
*/
|
||||
static int do_for_each_ref_in_dirs(struct ref_dir *dir1,
|
||||
struct ref_dir *dir2,
|
||||
const char *base, each_ref_fn fn, int trim,
|
||||
int flags, void *cb_data)
|
||||
static int do_for_each_entry_in_dirs(struct ref_dir *dir1,
|
||||
struct ref_dir *dir2,
|
||||
each_ref_entry_fn fn, void *cb_data)
|
||||
{
|
||||
int retval;
|
||||
int i1 = 0, i2 = 0;
|
||||
@ -578,12 +694,10 @@ static int do_for_each_ref_in_dirs(struct ref_dir *dir1,
|
||||
struct ref_entry *e1, *e2;
|
||||
int cmp;
|
||||
if (i1 == dir1->nr) {
|
||||
return do_for_each_ref_in_dir(dir2, i2,
|
||||
base, fn, trim, flags, cb_data);
|
||||
return do_for_each_entry_in_dir(dir2, i2, fn, cb_data);
|
||||
}
|
||||
if (i2 == dir2->nr) {
|
||||
return do_for_each_ref_in_dir(dir1, i1,
|
||||
base, fn, trim, flags, cb_data);
|
||||
return do_for_each_entry_in_dir(dir1, i1, fn, cb_data);
|
||||
}
|
||||
e1 = dir1->entries[i1];
|
||||
e2 = dir2->entries[i2];
|
||||
@ -595,14 +709,13 @@ static int do_for_each_ref_in_dirs(struct ref_dir *dir1,
|
||||
struct ref_dir *subdir2 = get_ref_dir(e2);
|
||||
sort_ref_dir(subdir1);
|
||||
sort_ref_dir(subdir2);
|
||||
retval = do_for_each_ref_in_dirs(
|
||||
subdir1, subdir2,
|
||||
base, fn, trim, flags, cb_data);
|
||||
retval = do_for_each_entry_in_dirs(
|
||||
subdir1, subdir2, fn, cb_data);
|
||||
i1++;
|
||||
i2++;
|
||||
} else if (!(e1->flag & REF_DIR) && !(e2->flag & REF_DIR)) {
|
||||
/* Both are references; ignore the one from dir1. */
|
||||
retval = do_one_ref(base, fn, trim, flags, cb_data, e2);
|
||||
retval = fn(e2, cb_data);
|
||||
i1++;
|
||||
i2++;
|
||||
} else {
|
||||
@ -621,23 +734,15 @@ static int do_for_each_ref_in_dirs(struct ref_dir *dir1,
|
||||
if (e->flag & REF_DIR) {
|
||||
struct ref_dir *subdir = get_ref_dir(e);
|
||||
sort_ref_dir(subdir);
|
||||
retval = do_for_each_ref_in_dir(
|
||||
subdir, 0,
|
||||
base, fn, trim, flags, cb_data);
|
||||
retval = do_for_each_entry_in_dir(
|
||||
subdir, 0, fn, cb_data);
|
||||
} else {
|
||||
retval = do_one_ref(base, fn, trim, flags, cb_data, e);
|
||||
retval = fn(e, cb_data);
|
||||
}
|
||||
}
|
||||
if (retval)
|
||||
return retval;
|
||||
}
|
||||
if (i1 < dir1->nr)
|
||||
return do_for_each_ref_in_dir(dir1, i1,
|
||||
base, fn, trim, flags, cb_data);
|
||||
if (i2 < dir2->nr)
|
||||
return do_for_each_ref_in_dir(dir2, i2,
|
||||
base, fn, trim, flags, cb_data);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
@ -661,14 +766,13 @@ struct name_conflict_cb {
|
||||
const char *conflicting_refname;
|
||||
};
|
||||
|
||||
static int name_conflict_fn(const char *existingrefname, const unsigned char *sha1,
|
||||
int flags, void *cb_data)
|
||||
static int name_conflict_fn(struct ref_entry *entry, void *cb_data)
|
||||
{
|
||||
struct name_conflict_cb *data = (struct name_conflict_cb *)cb_data;
|
||||
if (data->oldrefname && !strcmp(data->oldrefname, existingrefname))
|
||||
if (data->oldrefname && !strcmp(data->oldrefname, entry->name))
|
||||
return 0;
|
||||
if (names_conflict(data->refname, existingrefname)) {
|
||||
data->conflicting_refname = existingrefname;
|
||||
if (names_conflict(data->refname, entry->name)) {
|
||||
data->conflicting_refname = entry->name;
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
@ -676,7 +780,7 @@ static int name_conflict_fn(const char *existingrefname, const unsigned char *sh
|
||||
|
||||
/*
|
||||
* Return true iff a reference named refname could be created without
|
||||
* conflicting with the name of an existing reference in array. If
|
||||
* conflicting with the name of an existing reference in dir. If
|
||||
* oldrefname is non-NULL, ignore potential conflicts with oldrefname
|
||||
* (e.g., because oldrefname is scheduled for deletion in the same
|
||||
* operation).
|
||||
@ -690,9 +794,7 @@ static int is_refname_available(const char *refname, const char *oldrefname,
|
||||
data.conflicting_refname = NULL;
|
||||
|
||||
sort_ref_dir(dir);
|
||||
if (do_for_each_ref_in_dir(dir, 0, "", name_conflict_fn,
|
||||
0, DO_FOR_EACH_INCLUDE_BROKEN,
|
||||
&data)) {
|
||||
if (do_for_each_entry_in_dir(dir, 0, name_conflict_fn, &data)) {
|
||||
error("'%s' exists; cannot create '%s'",
|
||||
data.conflicting_refname, refname);
|
||||
return 0;
|
||||
@ -708,9 +810,13 @@ static struct ref_cache {
|
||||
struct ref_cache *next;
|
||||
struct ref_entry *loose;
|
||||
struct ref_entry *packed;
|
||||
/* The submodule name, or "" for the main repo. */
|
||||
char name[FLEX_ARRAY];
|
||||
} *ref_cache;
|
||||
/*
|
||||
* The submodule name, or "" for the main repo. We allocate
|
||||
* length 1 rather than FLEX_ARRAY so that the main ref_cache
|
||||
* is initialized correctly.
|
||||
*/
|
||||
char name[1];
|
||||
} ref_cache, *submodule_ref_caches;
|
||||
|
||||
static void clear_packed_ref_cache(struct ref_cache *refs)
|
||||
{
|
||||
@ -748,18 +854,18 @@ static struct ref_cache *create_ref_cache(const char *submodule)
|
||||
*/
|
||||
static struct ref_cache *get_ref_cache(const char *submodule)
|
||||
{
|
||||
struct ref_cache *refs = ref_cache;
|
||||
if (!submodule)
|
||||
submodule = "";
|
||||
while (refs) {
|
||||
struct ref_cache *refs;
|
||||
|
||||
if (!submodule || !*submodule)
|
||||
return &ref_cache;
|
||||
|
||||
for (refs = submodule_ref_caches; refs; refs = refs->next)
|
||||
if (!strcmp(submodule, refs->name))
|
||||
return refs;
|
||||
refs = refs->next;
|
||||
}
|
||||
|
||||
refs = create_ref_cache(submodule);
|
||||
refs->next = ref_cache;
|
||||
ref_cache = refs;
|
||||
refs->next = submodule_ref_caches;
|
||||
submodule_ref_caches = refs;
|
||||
return refs;
|
||||
}
|
||||
|
||||
@ -770,6 +876,16 @@ void invalidate_ref_cache(const char *submodule)
|
||||
clear_loose_ref_cache(refs);
|
||||
}
|
||||
|
||||
/* The length of a peeled reference line in packed-refs, including EOL: */
|
||||
#define PEELED_LINE_LENGTH 42
|
||||
|
||||
/*
|
||||
* The packed-refs header line that we write out. Perhaps other
|
||||
* traits will be added later. The trailing space is required.
|
||||
*/
|
||||
static const char PACKED_REFS_HEADER[] =
|
||||
"# pack-refs with: peeled fully-peeled \n";
|
||||
|
||||
/*
|
||||
* Parse one line from a packed-refs file. Write the SHA1 to sha1.
|
||||
* Return a pointer to the refname within the line (null-terminated),
|
||||
@ -862,8 +978,8 @@ static void read_packed_refs(FILE *f, struct ref_dir *dir)
|
||||
}
|
||||
if (last &&
|
||||
refline[0] == '^' &&
|
||||
strlen(refline) == 42 &&
|
||||
refline[41] == '\n' &&
|
||||
strlen(refline) == PEELED_LINE_LENGTH &&
|
||||
refline[PEELED_LINE_LENGTH - 1] == '\n' &&
|
||||
!get_sha1_hex(refline + 1, sha1)) {
|
||||
hashcpy(last->u.value.peeled, sha1);
|
||||
/*
|
||||
@ -898,8 +1014,8 @@ static struct ref_dir *get_packed_refs(struct ref_cache *refs)
|
||||
|
||||
void add_packed_ref(const char *refname, const unsigned char *sha1)
|
||||
{
|
||||
add_ref(get_packed_refs(get_ref_cache(NULL)),
|
||||
create_ref_entry(refname, sha1, REF_ISPACKED, 1));
|
||||
add_ref(get_packed_refs(&ref_cache),
|
||||
create_ref_entry(refname, sha1, REF_ISPACKED, 1));
|
||||
}
|
||||
|
||||
/*
|
||||
@ -1069,18 +1185,12 @@ int resolve_gitlink_ref(const char *path, const char *refname, unsigned char *sh
|
||||
}
|
||||
|
||||
/*
|
||||
* Try to read ref from the packed references. On success, set sha1
|
||||
* and return 0; otherwise, return -1.
|
||||
* Return the ref_entry for the given refname from the packed
|
||||
* references. If it does not exist, return NULL.
|
||||
*/
|
||||
static int get_packed_ref(const char *refname, unsigned char *sha1)
|
||||
static struct ref_entry *get_packed_ref(const char *refname)
|
||||
{
|
||||
struct ref_dir *packed = get_packed_refs(get_ref_cache(NULL));
|
||||
struct ref_entry *entry = find_ref(packed, refname);
|
||||
if (entry) {
|
||||
hashcpy(sha1, entry->u.value.sha1);
|
||||
return 0;
|
||||
}
|
||||
return -1;
|
||||
return find_ref(get_packed_refs(&ref_cache), refname);
|
||||
}
|
||||
|
||||
const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int reading, int *flag)
|
||||
@ -1108,13 +1218,17 @@ const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int rea
|
||||
git_snpath(path, sizeof(path), "%s", refname);
|
||||
|
||||
if (lstat(path, &st) < 0) {
|
||||
struct ref_entry *entry;
|
||||
|
||||
if (errno != ENOENT)
|
||||
return NULL;
|
||||
/*
|
||||
* The loose reference file does not exist;
|
||||
* check for a packed reference.
|
||||
*/
|
||||
if (!get_packed_ref(refname, sha1)) {
|
||||
entry = get_packed_ref(refname);
|
||||
if (entry) {
|
||||
hashcpy(sha1, entry->u.value.sha1);
|
||||
if (flag)
|
||||
*flag |= REF_ISPACKED;
|
||||
return refname;
|
||||
@ -1231,54 +1345,130 @@ static int filter_refs(const char *refname, const unsigned char *sha1, int flags
|
||||
return filter->fn(refname, sha1, flags, filter->cb_data);
|
||||
}
|
||||
|
||||
enum peel_status {
|
||||
/* object was peeled successfully: */
|
||||
PEEL_PEELED = 0,
|
||||
|
||||
/*
|
||||
* object cannot be peeled because the named object (or an
|
||||
* object referred to by a tag in the peel chain), does not
|
||||
* exist.
|
||||
*/
|
||||
PEEL_INVALID = -1,
|
||||
|
||||
/* object cannot be peeled because it is not a tag: */
|
||||
PEEL_NON_TAG = -2,
|
||||
|
||||
/* ref_entry contains no peeled value because it is a symref: */
|
||||
PEEL_IS_SYMREF = -3,
|
||||
|
||||
/*
|
||||
* ref_entry cannot be peeled because it is broken (i.e., the
|
||||
* symbolic reference cannot even be resolved to an object
|
||||
* name):
|
||||
*/
|
||||
PEEL_BROKEN = -4
|
||||
};
|
||||
|
||||
/*
|
||||
* Peel the named object; i.e., if the object is a tag, resolve the
|
||||
* tag recursively until a non-tag is found. If successful, store the
|
||||
* result to sha1 and return PEEL_PEELED. If the object is not a tag
|
||||
* or is not valid, return PEEL_NON_TAG or PEEL_INVALID, respectively,
|
||||
* and leave sha1 unchanged.
|
||||
*/
|
||||
static enum peel_status peel_object(const unsigned char *name, unsigned char *sha1)
|
||||
{
|
||||
struct object *o = lookup_unknown_object(name);
|
||||
|
||||
if (o->type == OBJ_NONE) {
|
||||
int type = sha1_object_info(name, NULL);
|
||||
if (type < 0)
|
||||
return PEEL_INVALID;
|
||||
o->type = type;
|
||||
}
|
||||
|
||||
if (o->type != OBJ_TAG)
|
||||
return PEEL_NON_TAG;
|
||||
|
||||
o = deref_tag_noverify(o);
|
||||
if (!o)
|
||||
return PEEL_INVALID;
|
||||
|
||||
hashcpy(sha1, o->sha1);
|
||||
return PEEL_PEELED;
|
||||
}
|
||||
|
||||
/*
|
||||
* Peel the entry (if possible) and return its new peel_status. If
|
||||
* repeel is true, re-peel the entry even if there is an old peeled
|
||||
* value that is already stored in it.
|
||||
*
|
||||
* It is OK to call this function with a packed reference entry that
|
||||
* might be stale and might even refer to an object that has since
|
||||
* been garbage-collected. In such a case, if the entry has
|
||||
* REF_KNOWS_PEELED then leave the status unchanged and return
|
||||
* PEEL_PEELED or PEEL_NON_TAG; otherwise, return PEEL_INVALID.
|
||||
*/
|
||||
static enum peel_status peel_entry(struct ref_entry *entry, int repeel)
|
||||
{
|
||||
enum peel_status status;
|
||||
|
||||
if (entry->flag & REF_KNOWS_PEELED) {
|
||||
if (repeel) {
|
||||
entry->flag &= ~REF_KNOWS_PEELED;
|
||||
hashclr(entry->u.value.peeled);
|
||||
} else {
|
||||
return is_null_sha1(entry->u.value.peeled) ?
|
||||
PEEL_NON_TAG : PEEL_PEELED;
|
||||
}
|
||||
}
|
||||
if (entry->flag & REF_ISBROKEN)
|
||||
return PEEL_BROKEN;
|
||||
if (entry->flag & REF_ISSYMREF)
|
||||
return PEEL_IS_SYMREF;
|
||||
|
||||
status = peel_object(entry->u.value.sha1, entry->u.value.peeled);
|
||||
if (status == PEEL_PEELED || status == PEEL_NON_TAG)
|
||||
entry->flag |= REF_KNOWS_PEELED;
|
||||
return status;
|
||||
}
|
||||
|
||||
int peel_ref(const char *refname, unsigned char *sha1)
|
||||
{
|
||||
int flag;
|
||||
unsigned char base[20];
|
||||
struct object *o;
|
||||
|
||||
if (current_ref && (current_ref->name == refname
|
||||
|| !strcmp(current_ref->name, refname))) {
|
||||
if (current_ref->flag & REF_KNOWS_PEELED) {
|
||||
if (is_null_sha1(current_ref->u.value.peeled))
|
||||
return -1;
|
||||
hashcpy(sha1, current_ref->u.value.peeled);
|
||||
return 0;
|
||||
}
|
||||
hashcpy(base, current_ref->u.value.sha1);
|
||||
goto fallback;
|
||||
|| !strcmp(current_ref->name, refname))) {
|
||||
if (peel_entry(current_ref, 0))
|
||||
return -1;
|
||||
hashcpy(sha1, current_ref->u.value.peeled);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (read_ref_full(refname, base, 1, &flag))
|
||||
return -1;
|
||||
|
||||
if ((flag & REF_ISPACKED)) {
|
||||
struct ref_dir *dir = get_packed_refs(get_ref_cache(NULL));
|
||||
struct ref_entry *r = find_ref(dir, refname);
|
||||
|
||||
if (r != NULL && r->flag & REF_KNOWS_PEELED) {
|
||||
/*
|
||||
* If the reference is packed, read its ref_entry from the
|
||||
* cache in the hope that we already know its peeled value.
|
||||
* We only try this optimization on packed references because
|
||||
* (a) forcing the filling of the loose reference cache could
|
||||
* be expensive and (b) loose references anyway usually do not
|
||||
* have REF_KNOWS_PEELED.
|
||||
*/
|
||||
if (flag & REF_ISPACKED) {
|
||||
struct ref_entry *r = get_packed_ref(refname);
|
||||
if (r) {
|
||||
if (peel_entry(r, 0))
|
||||
return -1;
|
||||
hashcpy(sha1, r->u.value.peeled);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
fallback:
|
||||
o = lookup_unknown_object(base);
|
||||
if (o->type == OBJ_NONE) {
|
||||
int type = sha1_object_info(base, NULL);
|
||||
if (type < 0)
|
||||
return -1;
|
||||
o->type = type;
|
||||
}
|
||||
|
||||
if (o->type == OBJ_TAG) {
|
||||
o = deref_tag_noverify(o);
|
||||
if (o) {
|
||||
hashcpy(sha1, o->sha1);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
return peel_object(base, sha1);
|
||||
}
|
||||
|
||||
struct warn_if_dangling_data {
|
||||
@ -1316,10 +1506,16 @@ void warn_dangling_symref(FILE *fp, const char *msg_fmt, const char *refname)
|
||||
for_each_rawref(warn_if_dangling_symref, &data);
|
||||
}
|
||||
|
||||
static int do_for_each_ref(const char *submodule, const char *base, each_ref_fn fn,
|
||||
int trim, int flags, void *cb_data)
|
||||
/*
|
||||
* Call fn for each reference in the specified ref_cache, omitting
|
||||
* references not in the containing_dir of base. fn is called for all
|
||||
* references, including broken ones. If fn ever returns a non-zero
|
||||
* value, stop the iteration and return that value; otherwise, return
|
||||
* 0.
|
||||
*/
|
||||
static int do_for_each_entry(struct ref_cache *refs, const char *base,
|
||||
each_ref_entry_fn fn, void *cb_data)
|
||||
{
|
||||
struct ref_cache *refs = get_ref_cache(submodule);
|
||||
struct ref_dir *packed_dir = get_packed_refs(refs);
|
||||
struct ref_dir *loose_dir = get_loose_refs(refs);
|
||||
int retval = 0;
|
||||
@ -1332,24 +1528,43 @@ static int do_for_each_ref(const char *submodule, const char *base, each_ref_fn
|
||||
if (packed_dir && loose_dir) {
|
||||
sort_ref_dir(packed_dir);
|
||||
sort_ref_dir(loose_dir);
|
||||
retval = do_for_each_ref_in_dirs(
|
||||
packed_dir, loose_dir,
|
||||
base, fn, trim, flags, cb_data);
|
||||
retval = do_for_each_entry_in_dirs(
|
||||
packed_dir, loose_dir, fn, cb_data);
|
||||
} else if (packed_dir) {
|
||||
sort_ref_dir(packed_dir);
|
||||
retval = do_for_each_ref_in_dir(
|
||||
packed_dir, 0,
|
||||
base, fn, trim, flags, cb_data);
|
||||
retval = do_for_each_entry_in_dir(
|
||||
packed_dir, 0, fn, cb_data);
|
||||
} else if (loose_dir) {
|
||||
sort_ref_dir(loose_dir);
|
||||
retval = do_for_each_ref_in_dir(
|
||||
loose_dir, 0,
|
||||
base, fn, trim, flags, cb_data);
|
||||
retval = do_for_each_entry_in_dir(
|
||||
loose_dir, 0, fn, cb_data);
|
||||
}
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
/*
|
||||
* Call fn for each reference in the specified ref_cache for which the
|
||||
* refname begins with base. If trim is non-zero, then trim that many
|
||||
* characters off the beginning of each refname before passing the
|
||||
* refname to fn. flags can be DO_FOR_EACH_INCLUDE_BROKEN to include
|
||||
* broken references in the iteration. If fn ever returns a non-zero
|
||||
* value, stop the iteration and return that value; otherwise, return
|
||||
* 0.
|
||||
*/
|
||||
static int do_for_each_ref(struct ref_cache *refs, const char *base,
|
||||
each_ref_fn fn, int trim, int flags, void *cb_data)
|
||||
{
|
||||
struct ref_entry_cb data;
|
||||
data.base = base;
|
||||
data.trim = trim;
|
||||
data.flags = flags;
|
||||
data.fn = fn;
|
||||
data.cb_data = cb_data;
|
||||
|
||||
return do_for_each_entry(refs, base, do_one_ref, &data);
|
||||
}
|
||||
|
||||
static int do_head_ref(const char *submodule, each_ref_fn fn, void *cb_data)
|
||||
{
|
||||
unsigned char sha1[20];
|
||||
@ -1380,23 +1595,23 @@ int head_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data)
|
||||
|
||||
int for_each_ref(each_ref_fn fn, void *cb_data)
|
||||
{
|
||||
return do_for_each_ref(NULL, "", fn, 0, 0, cb_data);
|
||||
return do_for_each_ref(&ref_cache, "", fn, 0, 0, cb_data);
|
||||
}
|
||||
|
||||
int for_each_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data)
|
||||
{
|
||||
return do_for_each_ref(submodule, "", fn, 0, 0, cb_data);
|
||||
return do_for_each_ref(get_ref_cache(submodule), "", fn, 0, 0, cb_data);
|
||||
}
|
||||
|
||||
int for_each_ref_in(const char *prefix, each_ref_fn fn, void *cb_data)
|
||||
{
|
||||
return do_for_each_ref(NULL, prefix, fn, strlen(prefix), 0, cb_data);
|
||||
return do_for_each_ref(&ref_cache, prefix, fn, strlen(prefix), 0, cb_data);
|
||||
}
|
||||
|
||||
int for_each_ref_in_submodule(const char *submodule, const char *prefix,
|
||||
each_ref_fn fn, void *cb_data)
|
||||
{
|
||||
return do_for_each_ref(submodule, prefix, fn, strlen(prefix), 0, cb_data);
|
||||
return do_for_each_ref(get_ref_cache(submodule), prefix, fn, strlen(prefix), 0, cb_data);
|
||||
}
|
||||
|
||||
int for_each_tag_ref(each_ref_fn fn, void *cb_data)
|
||||
@ -1431,7 +1646,7 @@ int for_each_remote_ref_submodule(const char *submodule, each_ref_fn fn, void *c
|
||||
|
||||
int for_each_replace_ref(each_ref_fn fn, void *cb_data)
|
||||
{
|
||||
return do_for_each_ref(NULL, "refs/replace/", fn, 13, 0, cb_data);
|
||||
return do_for_each_ref(&ref_cache, "refs/replace/", fn, 13, 0, cb_data);
|
||||
}
|
||||
|
||||
int head_ref_namespaced(each_ref_fn fn, void *cb_data)
|
||||
@ -1454,7 +1669,7 @@ int for_each_namespaced_ref(each_ref_fn fn, void *cb_data)
|
||||
struct strbuf buf = STRBUF_INIT;
|
||||
int ret;
|
||||
strbuf_addf(&buf, "%srefs/", get_git_namespace());
|
||||
ret = do_for_each_ref(NULL, buf.buf, fn, 0, 0, cb_data);
|
||||
ret = do_for_each_ref(&ref_cache, buf.buf, fn, 0, 0, cb_data);
|
||||
strbuf_release(&buf);
|
||||
return ret;
|
||||
}
|
||||
@ -1496,7 +1711,7 @@ int for_each_glob_ref(each_ref_fn fn, const char *pattern, void *cb_data)
|
||||
|
||||
int for_each_rawref(each_ref_fn fn, void *cb_data)
|
||||
{
|
||||
return do_for_each_ref(NULL, "", fn, 0,
|
||||
return do_for_each_ref(&ref_cache, "", fn, 0,
|
||||
DO_FOR_EACH_INCLUDE_BROKEN, cb_data);
|
||||
}
|
||||
|
||||
@ -1702,7 +1917,7 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname,
|
||||
* name is a proper prefix of our refname.
|
||||
*/
|
||||
if (missing &&
|
||||
!is_refname_available(refname, NULL, get_packed_refs(get_ref_cache(NULL)))) {
|
||||
!is_refname_available(refname, NULL, get_packed_refs(&ref_cache))) {
|
||||
last_errno = ENOTDIR;
|
||||
goto error_return;
|
||||
}
|
||||
@ -1754,47 +1969,224 @@ struct ref_lock *lock_any_ref_for_update(const char *refname,
|
||||
return lock_ref_sha1_basic(refname, old_sha1, flags, NULL);
|
||||
}
|
||||
|
||||
struct repack_without_ref_sb {
|
||||
const char *refname;
|
||||
int fd;
|
||||
};
|
||||
|
||||
static int repack_without_ref_fn(const char *refname, const unsigned char *sha1,
|
||||
int flags, void *cb_data)
|
||||
/*
|
||||
* Write an entry to the packed-refs file for the specified refname.
|
||||
* If peeled is non-NULL, write it as the entry's peeled value.
|
||||
*/
|
||||
static void write_packed_entry(int fd, char *refname, unsigned char *sha1,
|
||||
unsigned char *peeled)
|
||||
{
|
||||
struct repack_without_ref_sb *data = cb_data;
|
||||
char line[PATH_MAX + 100];
|
||||
int len;
|
||||
|
||||
if (!strcmp(data->refname, refname))
|
||||
return 0;
|
||||
len = snprintf(line, sizeof(line), "%s %s\n",
|
||||
sha1_to_hex(sha1), refname);
|
||||
/* this should not happen but just being defensive */
|
||||
if (len > sizeof(line))
|
||||
die("too long a refname '%s'", refname);
|
||||
write_or_die(data->fd, line, len);
|
||||
write_or_die(fd, line, len);
|
||||
|
||||
if (peeled) {
|
||||
if (snprintf(line, sizeof(line), "^%s\n",
|
||||
sha1_to_hex(peeled)) != PEELED_LINE_LENGTH)
|
||||
die("internal error");
|
||||
write_or_die(fd, line, PEELED_LINE_LENGTH);
|
||||
}
|
||||
}
|
||||
|
||||
struct ref_to_prune {
|
||||
struct ref_to_prune *next;
|
||||
unsigned char sha1[20];
|
||||
char name[FLEX_ARRAY];
|
||||
};
|
||||
|
||||
struct pack_refs_cb_data {
|
||||
unsigned int flags;
|
||||
struct ref_to_prune *ref_to_prune;
|
||||
int fd;
|
||||
};
|
||||
|
||||
static int pack_one_ref(struct ref_entry *entry, void *cb_data)
|
||||
{
|
||||
struct pack_refs_cb_data *cb = cb_data;
|
||||
enum peel_status peel_status;
|
||||
int is_tag_ref = !prefixcmp(entry->name, "refs/tags/");
|
||||
|
||||
/* ALWAYS pack refs that were already packed or are tags */
|
||||
if (!(cb->flags & PACK_REFS_ALL) && !is_tag_ref &&
|
||||
!(entry->flag & REF_ISPACKED))
|
||||
return 0;
|
||||
|
||||
/* Do not pack symbolic or broken refs: */
|
||||
if ((entry->flag & REF_ISSYMREF) || !ref_resolves_to_object(entry))
|
||||
return 0;
|
||||
|
||||
peel_status = peel_entry(entry, 1);
|
||||
if (peel_status != PEEL_PEELED && peel_status != PEEL_NON_TAG)
|
||||
die("internal error peeling reference %s (%s)",
|
||||
entry->name, sha1_to_hex(entry->u.value.sha1));
|
||||
write_packed_entry(cb->fd, entry->name, entry->u.value.sha1,
|
||||
peel_status == PEEL_PEELED ?
|
||||
entry->u.value.peeled : NULL);
|
||||
|
||||
/* If the ref was already packed, there is no need to prune it. */
|
||||
if ((cb->flags & PACK_REFS_PRUNE) && !(entry->flag & REF_ISPACKED)) {
|
||||
int namelen = strlen(entry->name) + 1;
|
||||
struct ref_to_prune *n = xcalloc(1, sizeof(*n) + namelen);
|
||||
hashcpy(n->sha1, entry->u.value.sha1);
|
||||
strcpy(n->name, entry->name);
|
||||
n->next = cb->ref_to_prune;
|
||||
cb->ref_to_prune = n;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Remove empty parents, but spare refs/ and immediate subdirs.
|
||||
* Note: munges *name.
|
||||
*/
|
||||
static void try_remove_empty_parents(char *name)
|
||||
{
|
||||
char *p, *q;
|
||||
int i;
|
||||
p = name;
|
||||
for (i = 0; i < 2; i++) { /* refs/{heads,tags,...}/ */
|
||||
while (*p && *p != '/')
|
||||
p++;
|
||||
/* tolerate duplicate slashes; see check_refname_format() */
|
||||
while (*p == '/')
|
||||
p++;
|
||||
}
|
||||
for (q = p; *q; q++)
|
||||
;
|
||||
while (1) {
|
||||
while (q > p && *q != '/')
|
||||
q--;
|
||||
while (q > p && *(q-1) == '/')
|
||||
q--;
|
||||
if (q == p)
|
||||
break;
|
||||
*q = '\0';
|
||||
if (rmdir(git_path("%s", name)))
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* make sure nobody touched the ref, and unlink */
|
||||
static void prune_ref(struct ref_to_prune *r)
|
||||
{
|
||||
struct ref_lock *lock = lock_ref_sha1(r->name + 5, r->sha1);
|
||||
|
||||
if (lock) {
|
||||
unlink_or_warn(git_path("%s", r->name));
|
||||
unlock_ref(lock);
|
||||
try_remove_empty_parents(r->name);
|
||||
}
|
||||
}
|
||||
|
||||
static void prune_refs(struct ref_to_prune *r)
|
||||
{
|
||||
while (r) {
|
||||
prune_ref(r);
|
||||
r = r->next;
|
||||
}
|
||||
}
|
||||
|
||||
static struct lock_file packlock;
|
||||
|
||||
int pack_refs(unsigned int flags)
|
||||
{
|
||||
struct pack_refs_cb_data cbdata;
|
||||
|
||||
memset(&cbdata, 0, sizeof(cbdata));
|
||||
cbdata.flags = flags;
|
||||
|
||||
cbdata.fd = hold_lock_file_for_update(&packlock, git_path("packed-refs"),
|
||||
LOCK_DIE_ON_ERROR);
|
||||
|
||||
write_or_die(cbdata.fd, PACKED_REFS_HEADER, strlen(PACKED_REFS_HEADER));
|
||||
|
||||
do_for_each_entry(&ref_cache, "", pack_one_ref, &cbdata);
|
||||
if (commit_lock_file(&packlock) < 0)
|
||||
die_errno("unable to overwrite old ref-pack file");
|
||||
prune_refs(cbdata.ref_to_prune);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int repack_ref_fn(struct ref_entry *entry, void *cb_data)
|
||||
{
|
||||
int *fd = cb_data;
|
||||
enum peel_status peel_status;
|
||||
|
||||
if (entry->flag & REF_ISBROKEN) {
|
||||
/* This shouldn't happen to packed refs. */
|
||||
error("%s is broken!", entry->name);
|
||||
return 0;
|
||||
}
|
||||
if (!has_sha1_file(entry->u.value.sha1)) {
|
||||
unsigned char sha1[20];
|
||||
int flags;
|
||||
|
||||
if (read_ref_full(entry->name, sha1, 0, &flags))
|
||||
/* We should at least have found the packed ref. */
|
||||
die("Internal error");
|
||||
if ((flags & REF_ISSYMREF) || !(flags & REF_ISPACKED))
|
||||
/*
|
||||
* This packed reference is overridden by a
|
||||
* loose reference, so it is OK that its value
|
||||
* is no longer valid; for example, it might
|
||||
* refer to an object that has been garbage
|
||||
* collected. For this purpose we don't even
|
||||
* care whether the loose reference itself is
|
||||
* invalid, broken, symbolic, etc. Silently
|
||||
* omit the packed reference from the output.
|
||||
*/
|
||||
return 0;
|
||||
/*
|
||||
* There is no overriding loose reference, so the fact
|
||||
* that this reference doesn't refer to a valid object
|
||||
* indicates some kind of repository corruption.
|
||||
* Report the problem, then omit the reference from
|
||||
* the output.
|
||||
*/
|
||||
error("%s does not point to a valid object!", entry->name);
|
||||
return 0;
|
||||
}
|
||||
|
||||
peel_status = peel_entry(entry, 0);
|
||||
write_packed_entry(*fd, entry->name, entry->u.value.sha1,
|
||||
peel_status == PEEL_PEELED ?
|
||||
entry->u.value.peeled : NULL);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int repack_without_ref(const char *refname)
|
||||
{
|
||||
struct repack_without_ref_sb data;
|
||||
struct ref_cache *refs = get_ref_cache(NULL);
|
||||
struct ref_dir *packed = get_packed_refs(refs);
|
||||
if (find_ref(packed, refname) == NULL)
|
||||
return 0;
|
||||
data.refname = refname;
|
||||
data.fd = hold_lock_file_for_update(&packlock, git_path("packed-refs"), 0);
|
||||
if (data.fd < 0) {
|
||||
int fd;
|
||||
struct ref_dir *packed;
|
||||
|
||||
if (!get_packed_ref(refname))
|
||||
return 0; /* refname does not exist in packed refs */
|
||||
|
||||
fd = hold_lock_file_for_update(&packlock, git_path("packed-refs"), 0);
|
||||
if (fd < 0) {
|
||||
unable_to_lock_error(git_path("packed-refs"), errno);
|
||||
return error("cannot delete '%s' from packed refs", refname);
|
||||
}
|
||||
clear_packed_ref_cache(refs);
|
||||
packed = get_packed_refs(refs);
|
||||
do_for_each_ref_in_dir(packed, 0, "", repack_without_ref_fn, 0, 0, &data);
|
||||
clear_packed_ref_cache(&ref_cache);
|
||||
packed = get_packed_refs(&ref_cache);
|
||||
/* Remove refname from the cache. */
|
||||
if (remove_entry(packed, refname) == -1) {
|
||||
/*
|
||||
* The packed entry disappeared while we were
|
||||
* acquiring the lock.
|
||||
*/
|
||||
rollback_lock_file(&packlock);
|
||||
return 0;
|
||||
}
|
||||
write_or_die(fd, PACKED_REFS_HEADER, strlen(PACKED_REFS_HEADER));
|
||||
do_for_each_entry_in_dir(packed, 0, repack_ref_fn, &fd);
|
||||
return commit_lock_file(&packlock);
|
||||
}
|
||||
|
||||
@ -1823,7 +2215,7 @@ int delete_ref(const char *refname, const unsigned char *sha1, int delopt)
|
||||
ret |= repack_without_ref(lock->ref_name);
|
||||
|
||||
unlink_or_warn(git_path("logs/%s", lock->ref_name));
|
||||
invalidate_ref_cache(NULL);
|
||||
clear_loose_ref_cache(&ref_cache);
|
||||
unlock_ref(lock);
|
||||
return ret;
|
||||
}
|
||||
@ -1845,7 +2237,6 @@ int rename_ref(const char *oldrefname, const char *newrefname, const char *logms
|
||||
struct stat loginfo;
|
||||
int log = !lstat(git_path("logs/%s", oldrefname), &loginfo);
|
||||
const char *symref = NULL;
|
||||
struct ref_cache *refs = get_ref_cache(NULL);
|
||||
|
||||
if (log && S_ISLNK(loginfo.st_mode))
|
||||
return error("reflog for %s is a symlink", oldrefname);
|
||||
@ -1857,10 +2248,10 @@ int rename_ref(const char *oldrefname, const char *newrefname, const char *logms
|
||||
if (!symref)
|
||||
return error("refname %s not found", oldrefname);
|
||||
|
||||
if (!is_refname_available(newrefname, oldrefname, get_packed_refs(refs)))
|
||||
if (!is_refname_available(newrefname, oldrefname, get_packed_refs(&ref_cache)))
|
||||
return 1;
|
||||
|
||||
if (!is_refname_available(newrefname, oldrefname, get_loose_refs(refs)))
|
||||
if (!is_refname_available(newrefname, oldrefname, get_loose_refs(&ref_cache)))
|
||||
return 1;
|
||||
|
||||
if (log && rename(git_path("logs/%s", oldrefname), git_path(TMP_RENAMED_LOG)))
|
||||
@ -2116,7 +2507,7 @@ int write_ref_sha1(struct ref_lock *lock,
|
||||
unlock_ref(lock);
|
||||
return -1;
|
||||
}
|
||||
clear_loose_ref_cache(get_ref_cache(NULL));
|
||||
clear_loose_ref_cache(&ref_cache);
|
||||
if (log_ref_write(lock->ref_name, lock->old_sha1, sha1, logmsg) < 0 ||
|
||||
(strcmp(lock->ref_name, lock->orig_ref_name) &&
|
||||
log_ref_write(lock->orig_ref_name, lock->old_sha1, sha1, logmsg) < 0)) {
|
||||
|
35
refs.h
35
refs.h
@ -10,8 +10,21 @@ struct ref_lock {
|
||||
int force_write;
|
||||
};
|
||||
|
||||
/*
|
||||
* Bit values set in the flags argument passed to each_ref_fn():
|
||||
*/
|
||||
|
||||
/* Reference is a symbolic reference. */
|
||||
#define REF_ISSYMREF 0x01
|
||||
|
||||
/* Reference is a packed reference. */
|
||||
#define REF_ISPACKED 0x02
|
||||
|
||||
/*
|
||||
* Reference cannot be resolved to an object name: dangling symbolic
|
||||
* reference (directly or indirectly), corrupt reference file, or
|
||||
* symbolic reference refers to ill-formatted reference name.
|
||||
*/
|
||||
#define REF_ISBROKEN 0x04
|
||||
|
||||
/*
|
||||
@ -59,8 +72,30 @@ extern void warn_dangling_symref(FILE *fp, const char *msg_fmt, const char *refn
|
||||
*/
|
||||
extern void add_packed_ref(const char *refname, const unsigned char *sha1);
|
||||
|
||||
/*
|
||||
* Flags for controlling behaviour of pack_refs()
|
||||
* PACK_REFS_PRUNE: Prune loose refs after packing
|
||||
* PACK_REFS_ALL: Pack _all_ refs, not just tags and already packed refs
|
||||
*/
|
||||
#define PACK_REFS_PRUNE 0x0001
|
||||
#define PACK_REFS_ALL 0x0002
|
||||
|
||||
/*
|
||||
* Write a packed-refs file for the current repository.
|
||||
* flags: Combination of the above PACK_REFS_* flags.
|
||||
*/
|
||||
int pack_refs(unsigned int flags);
|
||||
|
||||
extern int ref_exists(const char *);
|
||||
|
||||
/*
|
||||
* If refname is a non-symbolic reference that refers to a tag object,
|
||||
* and the tag can be (recursively) dereferenced to a non-tag object,
|
||||
* store the SHA1 of the referred-to object to sha1 and return 0. If
|
||||
* any of these conditions are not met, return a non-zero value.
|
||||
* Symbolic references are considered unpeelable, even if they
|
||||
* ultimately resolve to a peelable tag.
|
||||
*/
|
||||
extern int peel_ref(const char *refname, unsigned char *sha1);
|
||||
|
||||
/** Locks a "refs/" ref returning the lock on success and NULL on failure. **/
|
||||
|
@ -118,4 +118,37 @@ test_expect_success 'pack, prune and repack' '
|
||||
test_cmp all-of-them again
|
||||
'
|
||||
|
||||
test_expect_success 'explicit pack-refs with dangling packed reference' '
|
||||
git commit --allow-empty -m "soon to be garbage-collected" &&
|
||||
git pack-refs --all &&
|
||||
git reset --hard HEAD^ &&
|
||||
git reflog expire --expire=all --all &&
|
||||
git prune --expire=all &&
|
||||
git pack-refs --all 2>result &&
|
||||
test_cmp /dev/null result
|
||||
'
|
||||
|
||||
test_expect_success 'delete ref with dangling packed version' '
|
||||
git checkout -b lamb &&
|
||||
git commit --allow-empty -m "future garbage" &&
|
||||
git pack-refs --all &&
|
||||
git reset --hard HEAD^ &&
|
||||
git checkout master &&
|
||||
git reflog expire --expire=all --all &&
|
||||
git prune --expire=all &&
|
||||
git branch -d lamb 2>result &&
|
||||
test_cmp /dev/null result
|
||||
'
|
||||
|
||||
test_expect_success 'delete ref while another dangling packed ref' '
|
||||
git branch lamb &&
|
||||
git commit --allow-empty -m "future garbage" &&
|
||||
git pack-refs --all &&
|
||||
git reset --hard HEAD^ &&
|
||||
git reflog expire --expire=all --all &&
|
||||
git prune --expire=all &&
|
||||
git branch -d lamb 2>result &&
|
||||
test_cmp /dev/null result
|
||||
'
|
||||
|
||||
test_done
|
||||
|
@ -61,4 +61,13 @@ test_expect_success 'refs are peeled outside of refs/tags (old packed)' '
|
||||
test_cmp expect actual
|
||||
'
|
||||
|
||||
test_expect_success 'peeled refs survive deletion of packed ref' '
|
||||
git pack-refs --all &&
|
||||
cp .git/packed-refs fully-peeled &&
|
||||
git branch yadda &&
|
||||
git pack-refs --all &&
|
||||
git branch -d yadda &&
|
||||
test_cmp fully-peeled .git/packed-refs
|
||||
'
|
||||
|
||||
test_done
|
||||
|
Loading…
Reference in New Issue
Block a user