Merge branch 'jk/alt-odb-cleanup'

Codepaths involved in interacting alternate object store have
been cleaned up.

* jk/alt-odb-cleanup:
  alternates: use fspathcmp to detect duplicates
  sha1_file: always allow relative paths to alternates
  count-objects: report alternates via verbose mode
  fill_sha1_file: write into a strbuf
  alternates: store scratch buffer as strbuf
  fill_sha1_file: write "boring" characters
  alternates: use a separate scratch space
  alternates: encapsulate alt->base munging
  alternates: provide helper for allocating alternate
  alternates: provide helper for adding to alternates list
  link_alt_odb_entry: refactor string handling
  link_alt_odb_entry: handle normalize_path errors
  t5613: clarify "too deep" recursion tests
  t5613: do not chdir in main process
  t5613: whitespace/style cleanups
  t5613: use test_must_fail
  t5613: drop test_valid_repo function
  t5613: drop reachable_via function
This commit is contained in:
Junio C Hamano 2016-10-17 13:25:19 -07:00
Родитель af9a70c8de ea0fc3b417
Коммит dec040192f
12 изменённых файлов: 319 добавлений и 233 удалений

Просмотреть файл

@ -38,6 +38,11 @@ objects nor valid packs
+
size-garbage: disk space consumed by garbage files, in KiB (unless -H is
specified)
+
alternate: absolute path of alternate object databases; may appear
multiple times, one line per path. Note that if the path contains
non-printable characters, it may be surrounded by double-quotes and
contain C-style backslashed escape sequences.
-H::
--human-readable::

Просмотреть файл

@ -8,6 +8,7 @@
#include "dir.h"
#include "builtin.h"
#include "parse-options.h"
#include "quote.h"
static unsigned long garbage;
static off_t size_garbage;
@ -73,6 +74,14 @@ static int count_cruft(const char *basename, const char *path, void *data)
return 0;
}
static int print_alternate(struct alternate_object_database *alt, void *data)
{
printf("alternate: ");
quote_c_style(alt->path, NULL, stdout, 0);
putchar('\n');
return 0;
}
static char const * const count_objects_usage[] = {
N_("git count-objects [-v] [-H | --human-readable]"),
NULL
@ -88,6 +97,8 @@ int cmd_count_objects(int argc, const char **argv, const char *prefix)
OPT_END(),
};
git_config(git_default_config, NULL);
argc = parse_options(argc, argv, prefix, opts, count_objects_usage, 0);
/* we do not take arguments other than flags for now */
if (argc)
@ -140,6 +151,7 @@ int cmd_count_objects(int argc, const char **argv, const char *prefix)
printf("prune-packable: %lu\n", packed_loose);
printf("garbage: %lu\n", garbage);
printf("size-garbage: %s\n", garbage_buf.buf);
foreach_alt_odb(print_alternate, NULL);
strbuf_release(&loose_buf);
strbuf_release(&pack_buf);
strbuf_release(&garbage_buf);

Просмотреть файл

@ -644,14 +644,8 @@ int cmd_fsck(int argc, const char **argv, const char *prefix)
fsck_object_dir(get_object_directory());
prepare_alt_odb();
for (alt = alt_odb_list; alt; alt = alt->next) {
/* directory name, minus trailing slash */
size_t namelen = alt->name - alt->base - 1;
struct strbuf name = STRBUF_INIT;
strbuf_add(&name, alt->base, namelen);
fsck_object_dir(name.buf);
strbuf_release(&name);
}
for (alt = alt_odb_list; alt; alt = alt->next)
fsck_object_dir(alt->path);
}
if (check_full) {

Просмотреть файл

@ -492,20 +492,16 @@ static int add_possible_reference_from_superproject(
{
struct submodule_alternate_setup *sas = sas_cb;
/* directory name, minus trailing slash */
size_t namelen = alt->name - alt->base - 1;
struct strbuf name = STRBUF_INIT;
strbuf_add(&name, alt->base, namelen);
/*
* If the alternate object store is another repository, try the
* standard layout with .git/modules/<name>/objects
*/
if (ends_with(name.buf, ".git/objects")) {
if (ends_with(alt->path, ".git/objects")) {
char *sm_alternate;
struct strbuf sb = STRBUF_INIT;
struct strbuf err = STRBUF_INIT;
strbuf_add(&sb, name.buf, name.len - strlen("objects"));
strbuf_add(&sb, alt->path, strlen(alt->path) - strlen("objects"));
/*
* We need to end the new path with '/' to mark it as a dir,
* otherwise a submodule name containing '/' will be broken
@ -533,7 +529,6 @@ static int add_possible_reference_from_superproject(
strbuf_release(&sb);
}
strbuf_release(&name);
return 0;
}

36
cache.h
Просмотреть файл

@ -1390,16 +1390,46 @@ extern void remove_scheduled_dirs(void);
extern struct alternate_object_database {
struct alternate_object_database *next;
char *name;
char base[FLEX_ARRAY]; /* more */
/* see alt_scratch_buf() */
struct strbuf scratch;
size_t base_len;
char path[FLEX_ARRAY];
} *alt_odb_list;
extern void prepare_alt_odb(void);
extern void read_info_alternates(const char * relative_base, int depth);
extern char *compute_alternate_path(const char *path, struct strbuf *err);
extern void add_to_alternates_file(const char *reference);
typedef int alt_odb_fn(struct alternate_object_database *, void *);
extern int foreach_alt_odb(alt_odb_fn, void*);
/*
* Allocate a "struct alternate_object_database" but do _not_ actually
* add it to the list of alternates.
*/
struct alternate_object_database *alloc_alt_odb(const char *dir);
/*
* Add the directory to the on-disk alternates file; the new entry will also
* take effect in the current process.
*/
extern void add_to_alternates_file(const char *dir);
/*
* Add the directory to the in-memory list of alternates (along with any
* recursive alternates it points to), but do not modify the on-disk alternates
* file.
*/
extern void add_to_alternates_memory(const char *dir);
/*
* Returns a scratch strbuf pre-filled with the alternate object directory,
* including a trailing slash, which can be used to access paths in the
* alternate. Always use this over direct access to alt->scratch, as it
* cleans up any previous use of the scratch buffer.
*/
extern struct strbuf *alt_scratch_buf(struct alternate_object_database *alt);
struct pack_window {
struct pack_window *next;
unsigned char *base;

Просмотреть файл

@ -172,36 +172,42 @@ enum scld_error safe_create_leading_directories_const(const char *path)
return result;
}
static void fill_sha1_path(char *pathbuf, const unsigned char *sha1)
static void fill_sha1_path(struct strbuf *buf, const unsigned char *sha1)
{
int i;
for (i = 0; i < 20; i++) {
static char hex[] = "0123456789abcdef";
unsigned int val = sha1[i];
char *pos = pathbuf + i*2 + (i > 0);
*pos++ = hex[val >> 4];
*pos = hex[val & 0xf];
strbuf_addch(buf, hex[val >> 4]);
strbuf_addch(buf, hex[val & 0xf]);
if (!i)
strbuf_addch(buf, '/');
}
}
const char *sha1_file_name(const unsigned char *sha1)
{
static char buf[PATH_MAX];
const char *objdir;
int len;
static struct strbuf buf = STRBUF_INIT;
objdir = get_object_directory();
len = strlen(objdir);
strbuf_reset(&buf);
strbuf_addf(&buf, "%s/", get_object_directory());
/* '/' + sha1(2) + '/' + sha1(38) + '\0' */
if (len + 43 > PATH_MAX)
die("insanely long object directory %s", objdir);
memcpy(buf, objdir, len);
buf[len] = '/';
buf[len+3] = '/';
buf[len+42] = '\0';
fill_sha1_path(buf + len + 1, sha1);
return buf;
fill_sha1_path(&buf, sha1);
return buf.buf;
}
struct strbuf *alt_scratch_buf(struct alternate_object_database *alt)
{
strbuf_setlen(&alt->scratch, alt->base_len);
return &alt->scratch;
}
static const char *alt_sha1_path(struct alternate_object_database *alt,
const unsigned char *sha1)
{
struct strbuf *buf = alt_scratch_buf(alt);
fill_sha1_path(buf, sha1);
return buf->buf;
}
/*
@ -234,6 +240,35 @@ char *sha1_pack_index_name(const unsigned char *sha1)
struct alternate_object_database *alt_odb_list;
static struct alternate_object_database **alt_odb_tail;
/*
* Return non-zero iff the path is usable as an alternate object database.
*/
static int alt_odb_usable(struct strbuf *path, const char *normalized_objdir)
{
struct alternate_object_database *alt;
/* Detect cases where alternate disappeared */
if (!is_directory(path->buf)) {
error("object directory %s does not exist; "
"check .git/objects/info/alternates.",
path->buf);
return 0;
}
/*
* Prevent the common mistake of listing the same
* thing twice, or object directory itself.
*/
for (alt = alt_odb_list; alt; alt = alt->next) {
if (!fspathcmp(path->buf, alt->path))
return 0;
}
if (!fspathcmp(path->buf, normalized_objdir))
return 0;
return 1;
}
/*
* Prepare alternate object database registry.
*
@ -253,8 +288,6 @@ static int link_alt_odb_entry(const char *entry, const char *relative_base,
int depth, const char *normalized_objdir)
{
struct alternate_object_database *ent;
struct alternate_object_database *alt;
size_t pfxlen, entlen;
struct strbuf pathbuf = STRBUF_INIT;
if (!is_absolute_path(entry) && relative_base) {
@ -263,49 +296,26 @@ static int link_alt_odb_entry(const char *entry, const char *relative_base,
}
strbuf_addstr(&pathbuf, entry);
normalize_path_copy(pathbuf.buf, pathbuf.buf);
pfxlen = strlen(pathbuf.buf);
if (strbuf_normalize_path(&pathbuf) < 0) {
error("unable to normalize alternate object path: %s",
pathbuf.buf);
strbuf_release(&pathbuf);
return -1;
}
/*
* The trailing slash after the directory name is given by
* this function at the end. Remove duplicates.
*/
while (pfxlen && pathbuf.buf[pfxlen-1] == '/')
pfxlen -= 1;
while (pathbuf.len && pathbuf.buf[pathbuf.len - 1] == '/')
strbuf_setlen(&pathbuf, pathbuf.len - 1);
entlen = st_add(pfxlen, 43); /* '/' + 2 hex + '/' + 38 hex + NUL */
ent = xmalloc(st_add(sizeof(*ent), entlen));
memcpy(ent->base, pathbuf.buf, pfxlen);
strbuf_release(&pathbuf);
ent->name = ent->base + pfxlen + 1;
ent->base[pfxlen + 3] = '/';
ent->base[pfxlen] = ent->base[entlen-1] = 0;
/* Detect cases where alternate disappeared */
if (!is_directory(ent->base)) {
error("object directory %s does not exist; "
"check .git/objects/info/alternates.",
ent->base);
free(ent);
if (!alt_odb_usable(&pathbuf, normalized_objdir)) {
strbuf_release(&pathbuf);
return -1;
}
/* Prevent the common mistake of listing the same
* thing twice, or object directory itself.
*/
for (alt = alt_odb_list; alt; alt = alt->next) {
if (pfxlen == alt->name - alt->base - 1 &&
!memcmp(ent->base, alt->base, pfxlen)) {
free(ent);
return -1;
}
}
if (!fspathcmp(ent->base, normalized_objdir)) {
free(ent);
return -1;
}
ent = alloc_alt_odb(pathbuf.buf);
/* add the alternate entry */
*alt_odb_tail = ent;
@ -313,10 +323,9 @@ static int link_alt_odb_entry(const char *entry, const char *relative_base,
ent->next = NULL;
/* recursively add alternates */
read_info_alternates(ent->base, depth + 1);
ent->base[pfxlen] = '/';
read_info_alternates(pathbuf.buf, depth + 1);
strbuf_release(&pathbuf);
return 0;
}
@ -335,7 +344,9 @@ static void link_alt_odb_entries(const char *alt, int len, int sep,
}
strbuf_add_absolute_path(&objdirbuf, get_object_directory());
normalize_path_copy(objdirbuf.buf, objdirbuf.buf);
if (strbuf_normalize_path(&objdirbuf) < 0)
die("unable to normalize object directory: %s",
objdirbuf.buf);
alt_copy = xmemdupz(alt, len);
string_list_split_in_place(&entries, alt_copy, sep, -1);
@ -343,12 +354,7 @@ static void link_alt_odb_entries(const char *alt, int len, int sep,
const char *entry = entries.items[i].string;
if (entry[0] == '\0' || entry[0] == '#')
continue;
if (!is_absolute_path(entry) && depth) {
error("%s: ignoring relative alternate object store %s",
relative_base, entry);
} else {
link_alt_odb_entry(entry, relative_base, depth, objdirbuf.buf);
}
link_alt_odb_entry(entry, relative_base, depth, objdirbuf.buf);
}
string_list_clear(&entries, 0);
free(alt_copy);
@ -381,6 +387,18 @@ void read_info_alternates(const char * relative_base, int depth)
munmap(map, mapsz);
}
struct alternate_object_database *alloc_alt_odb(const char *dir)
{
struct alternate_object_database *ent;
FLEX_ALLOC_STR(ent, path, dir);
strbuf_init(&ent->scratch, 0);
strbuf_addf(&ent->scratch, "%s/", dir);
ent->base_len = ent->scratch.len;
return ent;
}
void add_to_alternates_file(const char *reference)
{
struct lock_file *lock = xcalloc(1, sizeof(struct lock_file));
@ -426,6 +444,17 @@ void add_to_alternates_file(const char *reference)
free(alts);
}
void add_to_alternates_memory(const char *reference)
{
/*
* Make sure alternates are initialized, or else our entry may be
* overwritten when they are.
*/
prepare_alt_odb();
link_alt_odb_entries(reference, strlen(reference), '\n', NULL, 0);
}
/*
* Compute the exact path an alternate is at and returns it. In case of
* error NULL is returned and the human readable error is added to `err`
@ -566,8 +595,8 @@ static int check_and_freshen_nonlocal(const unsigned char *sha1, int freshen)
struct alternate_object_database *alt;
prepare_alt_odb();
for (alt = alt_odb_list; alt; alt = alt->next) {
fill_sha1_path(alt->name, sha1);
if (check_and_freshen_file(alt->base, freshen))
const char *path = alt_sha1_path(alt, sha1);
if (check_and_freshen_file(path, freshen))
return 1;
}
return 0;
@ -1443,11 +1472,8 @@ void prepare_packed_git(void)
return;
prepare_packed_git_one(get_object_directory(), 1);
prepare_alt_odb();
for (alt = alt_odb_list; alt; alt = alt->next) {
alt->name[-1] = 0;
prepare_packed_git_one(alt->base, 0);
alt->name[-1] = '/';
}
for (alt = alt_odb_list; alt; alt = alt->next)
prepare_packed_git_one(alt->path, 0);
rearrange_packed_git();
prepare_packed_git_mru();
prepare_packed_git_run_once = 1;
@ -1565,8 +1591,8 @@ static int stat_sha1_file(const unsigned char *sha1, struct stat *st)
prepare_alt_odb();
errno = ENOENT;
for (alt = alt_odb_list; alt; alt = alt->next) {
fill_sha1_path(alt->name, sha1);
if (!lstat(alt->base, st))
const char *path = alt_sha1_path(alt, sha1);
if (!lstat(path, st))
return 0;
}
@ -1586,8 +1612,8 @@ static int open_sha1_file(const unsigned char *sha1)
prepare_alt_odb();
for (alt = alt_odb_list; alt; alt = alt->next) {
fill_sha1_path(alt->name, sha1);
fd = git_open_noatime(alt->base);
const char *path = alt_sha1_path(alt, sha1);
fd = git_open_noatime(path);
if (fd >= 0)
return fd;
if (most_interesting_errno == ENOENT)
@ -3648,8 +3674,7 @@ static int loose_from_alt_odb(struct alternate_object_database *alt,
struct strbuf buf = STRBUF_INIT;
int r;
/* copy base not including trailing '/' */
strbuf_add(&buf, alt->base, alt->name - alt->base - 1);
strbuf_addstr(&buf, alt->path);
r = for_each_loose_file_in_objdir_buf(&buf,
data->cb, NULL, NULL,
data->data);

Просмотреть файл

@ -91,25 +91,18 @@ static void find_short_object_filename(struct disambiguate_state *ds)
* alt->name/alt->base while iterating over the
* object databases including our own.
*/
const char *objdir = get_object_directory();
size_t objdir_len = strlen(objdir);
fakeent = xmalloc(st_add3(sizeof(*fakeent), objdir_len, 43));
memcpy(fakeent->base, objdir, objdir_len);
fakeent->name = fakeent->base + objdir_len + 1;
fakeent->name[-1] = '/';
fakeent = alloc_alt_odb(get_object_directory());
}
fakeent->next = alt_odb_list;
xsnprintf(hex, sizeof(hex), "%.2s", ds->hex_pfx);
for (alt = fakeent; alt && !ds->ambiguous; alt = alt->next) {
struct strbuf *buf = alt_scratch_buf(alt);
struct dirent *de;
DIR *dir;
/*
* every alt_odb struct has 42 extra bytes after the base
* for exactly this purpose
*/
xsnprintf(alt->name, 42, "%.2s/", ds->hex_pfx);
dir = opendir(alt->base);
strbuf_addf(buf, "%.2s/", ds->hex_pfx);
dir = opendir(buf->buf);
if (!dir)
continue;

Просмотреть файл

@ -870,3 +870,23 @@ void strbuf_stripspace(struct strbuf *sb, int skip_comments)
strbuf_setlen(sb, j);
}
int strbuf_normalize_path(struct strbuf *src)
{
struct strbuf dst = STRBUF_INIT;
strbuf_grow(&dst, src->len);
if (normalize_path_copy(dst.buf, src->buf) < 0) {
strbuf_release(&dst);
return -1;
}
/*
* normalize_path does not tell us the new length, so we have to
* compute it by looking for the new NUL it placed
*/
strbuf_setlen(&dst, strlen(dst.buf));
strbuf_swap(src, &dst);
strbuf_release(&dst);
return 0;
}

Просмотреть файл

@ -443,6 +443,14 @@ extern int strbuf_getcwd(struct strbuf *sb);
*/
extern void strbuf_add_absolute_path(struct strbuf *sb, const char *path);
/**
* Normalize in-place the path contained in the strbuf. See
* normalize_path_copy() for details. If an error occurs, the contents of "sb"
* are left untouched, and -1 is returned.
*/
extern int strbuf_normalize_path(struct strbuf *sb);
/**
* Strip whitespace from a buffer. The second parameter controls if
* comments are considered contents to be removed or not.

Просмотреть файл

@ -123,9 +123,7 @@ void stage_updated_gitmodules(void)
static int add_submodule_odb(const char *path)
{
struct strbuf objects_directory = STRBUF_INIT;
struct alternate_object_database *alt_odb;
int ret = 0;
size_t alloc;
ret = strbuf_git_path_submodule(&objects_directory, path, "objects/");
if (ret)
@ -134,26 +132,7 @@ static int add_submodule_odb(const char *path)
ret = -1;
goto done;
}
/* avoid adding it twice */
prepare_alt_odb();
for (alt_odb = alt_odb_list; alt_odb; alt_odb = alt_odb->next)
if (alt_odb->name - alt_odb->base == objects_directory.len &&
!strncmp(alt_odb->base, objects_directory.buf,
objects_directory.len))
goto done;
alloc = st_add(objects_directory.len, 42); /* for "12/345..." sha1 */
alt_odb = xmalloc(st_add(sizeof(*alt_odb), alloc));
alt_odb->next = alt_odb_list;
xsnprintf(alt_odb->base, alloc, "%s", objects_directory.buf);
alt_odb->name = alt_odb->base + objects_directory.len;
alt_odb->name[2] = '/';
alt_odb->name[40] = '\0';
alt_odb->name[41] = '\0';
alt_odb_list = alt_odb;
/* add possible alternates from the submodule */
read_info_alternates(objects_directory.buf, 0);
add_to_alternates_memory(objects_directory.buf);
done:
strbuf_release(&objects_directory);
return ret;

Просмотреть файл

@ -6,107 +6,134 @@
test_description='test transitive info/alternate entries'
. ./test-lib.sh
# test that a file is not reachable in the current repository
# but that it is after creating a info/alternate entry
reachable_via() {
alternate="$1"
file="$2"
if git cat-file -e "HEAD:$file"; then return 1; fi
echo "$alternate" >> .git/objects/info/alternate
git cat-file -e "HEAD:$file"
}
test_valid_repo() {
git fsck --full > fsck.log &&
test_line_count = 0 fsck.log
}
base_dir=$(pwd)
test_expect_success 'preparing first repository' \
'test_create_repo A && cd A &&
echo "Hello World" > file1 &&
git add file1 &&
git commit -m "Initial commit" file1 &&
git repack -a -d &&
git prune'
cd "$base_dir"
test_expect_success 'preparing second repository' \
'git clone -l -s A B && cd B &&
echo "foo bar" > file2 &&
git add file2 &&
git commit -m "next commit" file2 &&
git repack -a -d -l &&
git prune'
cd "$base_dir"
test_expect_success 'preparing third repository' \
'git clone -l -s B C && cd C &&
echo "Goodbye, cruel world" > file3 &&
git add file3 &&
git commit -m "one more" file3 &&
git repack -a -d -l &&
git prune'
cd "$base_dir"
test_expect_success 'creating too deep nesting' \
'git clone -l -s C D &&
git clone -l -s D E &&
git clone -l -s E F &&
git clone -l -s F G &&
git clone --bare -l -s G H'
test_expect_success 'invalidity of deepest repository' \
'cd H && {
test_valid_repo
test $? -ne 0
}'
cd "$base_dir"
test_expect_success 'validity of third repository' \
'cd C &&
test_valid_repo'
cd "$base_dir"
test_expect_success 'validity of fourth repository' \
'cd D &&
test_valid_repo'
cd "$base_dir"
test_expect_success 'breaking of loops' \
'echo "$base_dir"/B/.git/objects >> "$base_dir"/A/.git/objects/info/alternates&&
cd C &&
test_valid_repo'
cd "$base_dir"
test_expect_success 'that info/alternates is necessary' \
'cd C &&
rm -f .git/objects/info/alternates &&
! (test_valid_repo)'
cd "$base_dir"
test_expect_success 'that relative alternate is possible for current dir' \
'cd C &&
echo "../../../B/.git/objects" > .git/objects/info/alternates &&
test_valid_repo'
cd "$base_dir"
test_expect_success \
'that relative alternate is only possible for current dir' '
cd D &&
! (test_valid_repo)
test_expect_success 'preparing first repository' '
test_create_repo A && (
cd A &&
echo "Hello World" > file1 &&
git add file1 &&
git commit -m "Initial commit" file1 &&
git repack -a -d &&
git prune
)
'
cd "$base_dir"
test_expect_success 'preparing second repository' '
git clone -l -s A B && (
cd B &&
echo "foo bar" > file2 &&
git add file2 &&
git commit -m "next commit" file2 &&
git repack -a -d -l &&
git prune
)
'
test_expect_success 'preparing third repository' '
git clone -l -s B C && (
cd C &&
echo "Goodbye, cruel world" > file3 &&
git add file3 &&
git commit -m "one more" file3 &&
git repack -a -d -l &&
git prune
)
'
test_expect_success 'count-objects shows the alternates' '
cat >expect <<-EOF &&
alternate: $(pwd)/B/.git/objects
alternate: $(pwd)/A/.git/objects
EOF
git -C C count-objects -v >actual &&
grep ^alternate: actual >actual.alternates &&
test_cmp expect actual.alternates
'
# Note: These tests depend on the hard-coded value of 5 as the maximum depth
# we will follow recursion. We start the depth at 0 and count links, not
# repositories. This means that in a chain like:
#
# A --> B --> C --> D --> E --> F --> G --> H
# 0 1 2 3 4 5 6
#
# we are OK at "G", but break at "H", even though "H" is actually the 8th
# repository, not the 6th, which you might expect. Counting the links allows
# N+1 repositories, and counting from 0 to 5 inclusive allows 6 links.
#
# Note also that we must use "--bare -l" to make the link to H. The "-l"
# ensures we do not do a connectivity check, and the "--bare" makes sure
# we do not try to checkout the result (which needs objects), either of
# which would cause the clone to fail.
test_expect_success 'creating too deep nesting' '
git clone -l -s C D &&
git clone -l -s D E &&
git clone -l -s E F &&
git clone -l -s F G &&
git clone --bare -l -s G H
'
test_expect_success 'validity of seventh repository' '
git -C G fsck
'
test_expect_success 'invalidity of eighth repository' '
test_must_fail git -C H fsck
'
test_expect_success 'breaking of loops' '
echo "$(pwd)"/B/.git/objects >>A/.git/objects/info/alternates &&
git -C C fsck
'
test_expect_success 'that info/alternates is necessary' '
rm -f C/.git/objects/info/alternates &&
test_must_fail git -C C fsck
'
test_expect_success 'that relative alternate is possible for current dir' '
echo "../../../B/.git/objects" >C/.git/objects/info/alternates &&
git fsck
'
test_expect_success 'that relative alternate is recursive' '
git -C D fsck
'
# we can reach "A" from our new repo both directly, and via "C".
# The deep/subdir is there to make sure we are not doing a stupid
# pure-text comparison of the alternate names.
test_expect_success 'relative duplicates are eliminated' '
mkdir -p deep/subdir &&
git init --bare deep/subdir/duplicate.git &&
cat >deep/subdir/duplicate.git/objects/info/alternates <<-\EOF &&
../../../../C/.git/objects
../../../../A/.git/objects
EOF
cat >expect <<-EOF &&
alternate: $(pwd)/C/.git/objects
alternate: $(pwd)/B/.git/objects
alternate: $(pwd)/A/.git/objects
EOF
git -C deep/subdir/duplicate.git count-objects -v >actual &&
grep ^alternate: actual >actual.alternates &&
test_cmp expect actual.alternates
'
test_expect_success CASE_INSENSITIVE_FS 'dup finding can be case-insensitive' '
git init --bare insensitive.git &&
# the previous entry for "A" will have used uppercase
cat >insensitive.git/objects/info/alternates <<-\EOF &&
../../C/.git/objects
../../a/.git/objects
EOF
cat >expect <<-EOF &&
alternate: $(pwd)/C/.git/objects
alternate: $(pwd)/B/.git/objects
alternate: $(pwd)/A/.git/objects
EOF
git -C insensitive.git count-objects -v >actual &&
grep ^alternate: actual >actual.alternates &&
test_cmp expect actual.alternates
'
test_done

Просмотреть файл

@ -1096,9 +1096,7 @@ static int refs_from_alternate_cb(struct alternate_object_database *e,
const struct ref *extra;
struct alternate_refs_data *cb = data;
e->name[-1] = '\0';
other = xstrdup(real_path(e->base));
e->name[-1] = '/';
other = xstrdup(real_path(e->path));
len = strlen(other);
while (other[len-1] == '/')