Sync with 1.8.1 maintenance track

* maint-1.8.1:
  Start preparing for 1.8.1.6
  git-tag(1): we tag HEAD by default
  Fix revision walk for commits with the same dates
  t2003: work around path mangling issue on Windows
  pack-refs: add fully-peeled trait
  pack-refs: write peeled entry for non-tags
  use parse_object_or_die instead of die("bad object")
  avoid segfaults on parse_object failure
  entry: fix filter lookup
  t2003: modernize style
  name-hash.c: fix endless loop with core.ignorecase=true
This commit is contained in:
Junio C Hamano 2013-04-03 09:18:01 -07:00
commit 92e0d91632
19 changed files with 462 additions and 164 deletions

View File

@ -0,0 +1,34 @@
Git 1.8.1.6 Release Notes
=========================
Fixes since v1.8.1.5
--------------------
* The code to keep track of what directory names are known to Git on
platforms with case insensitive filesystems can get confused upon a
hash collision between these pathnames and looped forever.
* When the "--prefix" option is used to "checkout-index", the code
did not pick the correct output filter based on the attribute
setting.
* Annotated tags outside refs/tags/ hierarchy were not advertised
correctly to the ls-remote and fetch with recent version of Git.
* The logic used by "git diff -M --stat" to shorten the names of
files before and after a rename did not work correctly when the
common prefix and suffix between the two filenames overlapped.
* "git update-index -h" did not do the usual "-h(elp)" thing.
* perl/Git.pm::cat_blob slurped everything in core only to write it
out to a file descriptor, which was not a very smart thing to do.
* The SSL peer verification done by "git imap-send" did not ask for
Server Name Indication (RFC 4366), failing to connect SSL/TLS
sites that serve multiple hostnames on a single IP.
* "git bundle verify" did not say "records a complete history" for a
bundle that does not have any prerequisites.
Also contains various documentation fixes.

View File

@ -126,6 +126,12 @@ This option is only applicable when listing tags without annotation lines.
linkgit:git-check-ref-format[1]. Some of these checks linkgit:git-check-ref-format[1]. Some of these checks
may restrict the characters allowed in a tag name. may restrict the characters allowed in a tag name.
<commit>::
<object>::
The object that the new tag will refer to, usually a commit.
Defaults to HEAD.
CONFIGURATION CONFIGURATION
------------- -------------
By default, 'git tag' in sign-with-default mode (-s) will use your By default, 'git tag' in sign-with-default mode (-s) will use your

View File

@ -820,9 +820,7 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
unsigned char sha1[20]; unsigned char sha1[20];
/* Is it a rev? */ /* Is it a rev? */
if (!get_sha1(arg, sha1)) { if (!get_sha1(arg, sha1)) {
struct object *object = parse_object(sha1); struct object *object = parse_object_or_die(sha1, arg);
if (!object)
die(_("bad object %s"), arg);
if (!seen_dashdash) if (!seen_dashdash)
verify_non_filename(prefix, arg); verify_non_filename(prefix, arg);
add_object_array(object, arg, &list); add_object_array(object, arg, &list);

View File

@ -149,9 +149,7 @@ int cmd_prune(int argc, const char **argv, const char *prefix)
const char *name = *argv++; const char *name = *argv++;
if (!get_sha1(name, sha1)) { if (!get_sha1(name, sha1)) {
struct object *object = parse_object(sha1); struct object *object = parse_object_or_die(sha1, name);
if (!object)
die("bad object: %s", name);
add_pending_object(&revs, object, ""); add_pending_object(&revs, object, "");
} }
else else

View File

@ -279,12 +279,12 @@ int create_bundle(struct bundle_header *header, const char *path,
if (buf.len > 0 && buf.buf[0] == '-') { if (buf.len > 0 && buf.buf[0] == '-') {
write_or_die(bundle_fd, buf.buf, buf.len); write_or_die(bundle_fd, buf.buf, buf.len);
if (!get_sha1_hex(buf.buf + 1, sha1)) { if (!get_sha1_hex(buf.buf + 1, sha1)) {
struct object *object = parse_object(sha1); struct object *object = parse_object_or_die(sha1, buf.buf);
object->flags |= UNINTERESTING; object->flags |= UNINTERESTING;
add_pending_object(&revs, object, xstrdup(buf.buf)); add_pending_object(&revs, object, xstrdup(buf.buf));
} }
} else if (!get_sha1_hex(buf.buf, sha1)) { } else if (!get_sha1_hex(buf.buf, sha1)) {
struct object *object = parse_object(sha1); struct object *object = parse_object_or_die(sha1, buf.buf);
object->flags |= SHOWN; object->flags |= SHOWN;
} }
} }
@ -361,7 +361,7 @@ int create_bundle(struct bundle_header *header, const char *path,
* end up triggering "empty bundle" * end up triggering "empty bundle"
* error. * error.
*/ */
obj = parse_object(sha1); obj = parse_object_or_die(sha1, e->name);
obj->flags |= SHOWN; obj->flags |= SHOWN;
add_pending_object(&revs, obj, e->name); add_pending_object(&revs, obj, e->name);
} }

17
cache.h
View File

@ -131,7 +131,6 @@ struct cache_entry {
unsigned int ce_namelen; unsigned int ce_namelen;
unsigned char sha1[20]; unsigned char sha1[20];
struct cache_entry *next; struct cache_entry *next;
struct cache_entry *dir_next;
char name[FLEX_ARRAY]; /* more */ char name[FLEX_ARRAY]; /* more */
}; };
@ -267,25 +266,15 @@ struct index_state {
unsigned name_hash_initialized : 1, unsigned name_hash_initialized : 1,
initialized : 1; initialized : 1;
struct hash_table name_hash; struct hash_table name_hash;
struct hash_table dir_hash;
}; };
extern struct index_state the_index; extern struct index_state the_index;
/* Name hashing */ /* Name hashing */
extern void add_name_hash(struct index_state *istate, struct cache_entry *ce); extern void add_name_hash(struct index_state *istate, struct cache_entry *ce);
/* extern void remove_name_hash(struct index_state *istate, struct cache_entry *ce);
* We don't actually *remove* it, we can just mark it invalid so that extern void free_name_hash(struct index_state *istate);
* we won't find it in lookups.
*
* Not only would we have to search the lists (simple enough), but
* we'd also have to rehash other hash buckets in case this makes the
* hash bucket empty (common). So it's much better to just mark
* it.
*/
static inline void remove_name_hash(struct cache_entry *ce)
{
ce->ce_flags |= CE_UNHASHED;
}
#ifndef NO_THE_INDEX_COMPATIBILITY_MACROS #ifndef NO_THE_INDEX_COMPATIBILITY_MACROS

View File

@ -145,7 +145,7 @@ static int write_entry(struct cache_entry *ce, char *path, const struct checkout
struct stat st; struct stat st;
if (ce_mode_s_ifmt == S_IFREG) { if (ce_mode_s_ifmt == S_IFREG) {
struct stream_filter *filter = get_stream_filter(path, ce->sha1); struct stream_filter *filter = get_stream_filter(ce->name, ce->sha1);
if (filter && if (filter &&
!streaming_write_entry(ce, path, filter, !streaming_write_entry(ce, path, filter,
state, to_tempfile, state, to_tempfile,

View File

@ -32,38 +32,96 @@ static unsigned int hash_name(const char *name, int namelen)
return hash; return hash;
} }
static void hash_index_entry_directories(struct index_state *istate, struct cache_entry *ce) struct dir_entry {
struct dir_entry *next;
struct dir_entry *parent;
struct cache_entry *ce;
int nr;
unsigned int namelen;
};
static struct dir_entry *find_dir_entry(struct index_state *istate,
const char *name, unsigned int namelen)
{
unsigned int hash = hash_name(name, namelen);
struct dir_entry *dir;
for (dir = lookup_hash(hash, &istate->dir_hash); dir; dir = dir->next)
if (dir->namelen == namelen &&
!strncasecmp(dir->ce->name, name, namelen))
return dir;
return NULL;
}
static struct dir_entry *hash_dir_entry(struct index_state *istate,
struct cache_entry *ce, int namelen)
{ {
/* /*
* Throw each directory component in the hash for quick lookup * Throw each directory component in the hash for quick lookup
* during a git status. Directory components are stored with their * during a git status. Directory components are stored with their
* closing slash. Despite submodules being a directory, they never * closing slash. Despite submodules being a directory, they never
* reach this point, because they are stored without a closing slash * reach this point, because they are stored without a closing slash
* in the cache. * in index_state.name_hash (as ordinary cache_entries).
* *
* Note that the cache_entry stored with the directory does not * Note that the cache_entry stored with the dir_entry merely
* represent the directory itself. It is a pointer to an existing * supplies the name of the directory (up to dir_entry.namelen). We
* filename, and its only purpose is to represent existence of the * track the number of 'active' files in a directory in dir_entry.nr,
* directory in the cache. It is very possible multiple directory * so we can tell if the directory is still relevant, e.g. for git
* hash entries may point to the same cache_entry. * status. However, if cache_entries are removed, we cannot pinpoint
* an exact cache_entry that's still active. It is very possible that
* multiple dir_entries point to the same cache_entry.
*/ */
unsigned int hash; struct dir_entry *dir;
void **pos;
const char *ptr = ce->name; /* get length of parent directory */
while (*ptr) { while (namelen > 0 && !is_dir_sep(ce->name[namelen - 1]))
while (*ptr && *ptr != '/') namelen--;
++ptr; if (namelen <= 0)
if (*ptr == '/') { return NULL;
++ptr;
hash = hash_name(ce->name, ptr - ce->name); /* lookup existing entry for that directory */
pos = insert_hash(hash, ce, &istate->name_hash); dir = find_dir_entry(istate, ce->name, namelen);
if (pos) { if (!dir) {
ce->dir_next = *pos; /* not found, create it and add to hash table */
*pos = ce; void **pdir;
} unsigned int hash = hash_name(ce->name, namelen);
dir = xcalloc(1, sizeof(struct dir_entry));
dir->namelen = namelen;
dir->ce = ce;
pdir = insert_hash(hash, dir, &istate->dir_hash);
if (pdir) {
dir->next = *pdir;
*pdir = dir;
} }
/* recursively add missing parent directories */
dir->parent = hash_dir_entry(istate, ce, namelen - 1);
} }
return dir;
}
static void add_dir_entry(struct index_state *istate, struct cache_entry *ce)
{
/* Add reference to the directory entry (and parents if 0). */
struct dir_entry *dir = hash_dir_entry(istate, ce, ce_namelen(ce));
while (dir && !(dir->nr++))
dir = dir->parent;
}
static void remove_dir_entry(struct index_state *istate, struct cache_entry *ce)
{
/*
* Release reference to the directory entry (and parents if 0).
*
* Note: we do not remove / free the entry because there's no
* hash.[ch]::remove_hash and dir->next may point to other entries
* that are still valid, so we must not free the memory.
*/
struct dir_entry *dir = hash_dir_entry(istate, ce, ce_namelen(ce));
while (dir && dir->nr && !(--dir->nr))
dir = dir->parent;
} }
static void hash_index_entry(struct index_state *istate, struct cache_entry *ce) static void hash_index_entry(struct index_state *istate, struct cache_entry *ce)
@ -74,7 +132,7 @@ static void hash_index_entry(struct index_state *istate, struct cache_entry *ce)
if (ce->ce_flags & CE_HASHED) if (ce->ce_flags & CE_HASHED)
return; return;
ce->ce_flags |= CE_HASHED; ce->ce_flags |= CE_HASHED;
ce->next = ce->dir_next = NULL; ce->next = NULL;
hash = hash_name(ce->name, ce_namelen(ce)); hash = hash_name(ce->name, ce_namelen(ce));
pos = insert_hash(hash, ce, &istate->name_hash); pos = insert_hash(hash, ce, &istate->name_hash);
if (pos) { if (pos) {
@ -82,8 +140,8 @@ static void hash_index_entry(struct index_state *istate, struct cache_entry *ce)
*pos = ce; *pos = ce;
} }
if (ignore_case) if (ignore_case && !(ce->ce_flags & CE_UNHASHED))
hash_index_entry_directories(istate, ce); add_dir_entry(istate, ce);
} }
static void lazy_init_name_hash(struct index_state *istate) static void lazy_init_name_hash(struct index_state *istate)
@ -99,11 +157,33 @@ static void lazy_init_name_hash(struct index_state *istate)
void add_name_hash(struct index_state *istate, struct cache_entry *ce) void add_name_hash(struct index_state *istate, struct cache_entry *ce)
{ {
/* if already hashed, add reference to directory entries */
if (ignore_case && (ce->ce_flags & CE_STATE_MASK) == CE_STATE_MASK)
add_dir_entry(istate, ce);
ce->ce_flags &= ~CE_UNHASHED; ce->ce_flags &= ~CE_UNHASHED;
if (istate->name_hash_initialized) if (istate->name_hash_initialized)
hash_index_entry(istate, ce); hash_index_entry(istate, ce);
} }
/*
* We don't actually *remove* it, we can just mark it invalid so that
* we won't find it in lookups.
*
* Not only would we have to search the lists (simple enough), but
* we'd also have to rehash other hash buckets in case this makes the
* hash bucket empty (common). So it's much better to just mark
* it.
*/
void remove_name_hash(struct index_state *istate, struct cache_entry *ce)
{
/* if already hashed, release reference to directory entries */
if (ignore_case && (ce->ce_flags & CE_STATE_MASK) == CE_HASHED)
remove_dir_entry(istate, ce);
ce->ce_flags |= CE_UNHASHED;
}
static int slow_same_name(const char *name1, int len1, const char *name2, int len2) static int slow_same_name(const char *name1, int len1, const char *name2, int len2)
{ {
if (len1 != len2) if (len1 != len2)
@ -137,18 +217,7 @@ static int same_name(const struct cache_entry *ce, const char *name, int namelen
if (!icase) if (!icase)
return 0; return 0;
/* return slow_same_name(name, namelen, ce->name, len);
* If the entry we're comparing is a filename (no trailing slash), then compare
* the lengths exactly.
*/
if (name[namelen - 1] != '/')
return slow_same_name(name, namelen, ce->name, len);
/*
* For a directory, we point to an arbitrary cache_entry filename. Just
* make sure the directory portion matches.
*/
return slow_same_name(name, namelen, ce->name, namelen < len ? namelen : len);
} }
struct cache_entry *index_name_exists(struct index_state *istate, const char *name, int namelen, int icase) struct cache_entry *index_name_exists(struct index_state *istate, const char *name, int namelen, int icase)
@ -164,27 +233,54 @@ struct cache_entry *index_name_exists(struct index_state *istate, const char *na
if (same_name(ce, name, namelen, icase)) if (same_name(ce, name, namelen, icase))
return ce; return ce;
} }
if (icase && name[namelen - 1] == '/') ce = ce->next;
ce = ce->dir_next;
else
ce = ce->next;
} }
/* /*
* Might be a submodule. Despite submodules being directories, * When looking for a directory (trailing '/'), it might be a
* submodule or a directory. Despite submodules being directories,
* they are stored in the name hash without a closing slash. * they are stored in the name hash without a closing slash.
* When ignore_case is 1, directories are stored in the name hash * When ignore_case is 1, directories are stored in a separate hash
* with their closing slash. * table *with* their closing slash.
* *
* The side effect of this storage technique is we have need to * The side effect of this storage technique is we have need to
* lookup the directory in a separate hash table, and if not found
* remove the slash from name and perform the lookup again without * remove the slash from name and perform the lookup again without
* the slash. If a match is made, S_ISGITLINK(ce->mode) will be * the slash. If a match is made, S_ISGITLINK(ce->mode) will be
* true. * true.
*/ */
if (icase && name[namelen - 1] == '/') { if (icase && name[namelen - 1] == '/') {
struct dir_entry *dir = find_dir_entry(istate, name, namelen);
if (dir && dir->nr)
return dir->ce;
ce = index_name_exists(istate, name, namelen - 1, icase); ce = index_name_exists(istate, name, namelen - 1, icase);
if (ce && S_ISGITLINK(ce->ce_mode)) if (ce && S_ISGITLINK(ce->ce_mode))
return ce; return ce;
} }
return NULL; return NULL;
} }
static int free_dir_entry(void *entry, void *unused)
{
struct dir_entry *dir = entry;
while (dir) {
struct dir_entry *next = dir->next;
free(dir);
dir = next;
}
return 0;
}
void free_name_hash(struct index_state *istate)
{
if (!istate->name_hash_initialized)
return;
istate->name_hash_initialized = 0;
if (ignore_case)
/* free directory entries */
for_each_hash(&istate->dir_hash, free_dir_entry, NULL);
free_hash(&istate->name_hash);
free_hash(&istate->dir_hash);
}

View File

@ -185,6 +185,16 @@ struct object *parse_object_buffer(const unsigned char *sha1, enum object_type t
return obj; return obj;
} }
struct object *parse_object_or_die(const unsigned char *sha1,
const char *name)
{
struct object *o = parse_object(sha1);
if (o)
return o;
die(_("unable to parse object: %s"), name ? name : sha1_to_hex(sha1));
}
struct object *parse_object(const unsigned char *sha1) struct object *parse_object(const unsigned char *sha1)
{ {
unsigned long size; unsigned long size;

View File

@ -54,9 +54,20 @@ struct object *lookup_object(const unsigned char *sha1);
extern void *create_object(const unsigned char *sha1, int type, void *obj); extern void *create_object(const unsigned char *sha1, int type, void *obj);
/** Returns the object, having parsed it to find out what it is. **/ /*
* Returns the object, having parsed it to find out what it is.
*
* Returns NULL if the object is missing or corrupt.
*/
struct object *parse_object(const unsigned char *sha1); struct object *parse_object(const unsigned char *sha1);
/*
* Like parse_object, but will die() instead of returning NULL. If the
* "name" parameter is not NULL, it is included in the error message
* (otherwise, the sha1 hex is given).
*/
struct object *parse_object_or_die(const unsigned char *sha1, const char *name);
/* Given the result of read_sha1_file(), returns the object after /* Given the result of read_sha1_file(), returns the object after
* parsing it. eaten_p indicates if the object has a borrowed copy * parsing it. eaten_p indicates if the object has a borrowed copy
* of buffer and the caller should not free() it. * of buffer and the caller should not free() it.

View File

@ -27,6 +27,7 @@ static int handle_one_ref(const char *path, const unsigned char *sha1,
int flags, void *cb_data) int flags, void *cb_data)
{ {
struct pack_refs_cb_data *cb = cb_data; struct pack_refs_cb_data *cb = cb_data;
struct object *o;
int is_tag_ref; int is_tag_ref;
/* Do not pack the symbolic refs */ /* Do not pack the symbolic refs */
@ -39,14 +40,13 @@ static int handle_one_ref(const char *path, const unsigned char *sha1,
return 0; return 0;
fprintf(cb->refs_file, "%s %s\n", sha1_to_hex(sha1), path); fprintf(cb->refs_file, "%s %s\n", sha1_to_hex(sha1), path);
if (is_tag_ref) {
struct object *o = parse_object(sha1); o = parse_object_or_die(sha1, path);
if (o->type == OBJ_TAG) { if (o->type == OBJ_TAG) {
o = deref_tag(o, path, 0); o = deref_tag(o, path, 0);
if (o) if (o)
fprintf(cb->refs_file, "^%s\n", fprintf(cb->refs_file, "^%s\n",
sha1_to_hex(o->sha1)); sha1_to_hex(o->sha1));
}
} }
if ((cb->flags & PACK_REFS_PRUNE) && !do_not_prune(flags)) { if ((cb->flags & PACK_REFS_PRUNE) && !do_not_prune(flags)) {
@ -128,7 +128,7 @@ int pack_refs(unsigned int flags)
die_errno("unable to create ref-pack file structure"); die_errno("unable to create ref-pack file structure");
/* perhaps other traits later as well */ /* perhaps other traits later as well */
fprintf(cbdata.refs_file, "# pack-refs with: peeled \n"); fprintf(cbdata.refs_file, "# pack-refs with: peeled fully-peeled \n");
for_each_ref(handle_one_ref, &cbdata); for_each_ref(handle_one_ref, &cbdata);
if (ferror(cbdata.refs_file)) if (ferror(cbdata.refs_file))

View File

@ -152,11 +152,9 @@ static int add_one_reflog_ent(unsigned char *osha1, unsigned char *nsha1,
static int add_one_ref(const char *path, const unsigned char *sha1, int flag, void *cb_data) static int add_one_ref(const char *path, const unsigned char *sha1, int flag, void *cb_data)
{ {
struct object *object = parse_object(sha1); struct object *object = parse_object_or_die(sha1, path);
struct rev_info *revs = (struct rev_info *)cb_data; struct rev_info *revs = (struct rev_info *)cb_data;
if (!object)
die("bad object ref: %s:%s", path, sha1_to_hex(sha1));
add_pending_object(revs, object, ""); add_pending_object(revs, object, "");
return 0; return 0;

View File

@ -46,7 +46,7 @@ static void replace_index_entry(struct index_state *istate, int nr, struct cache
{ {
struct cache_entry *old = istate->cache[nr]; struct cache_entry *old = istate->cache[nr];
remove_name_hash(old); remove_name_hash(istate, old);
set_index_entry(istate, nr, ce); set_index_entry(istate, nr, ce);
istate->cache_changed = 1; istate->cache_changed = 1;
} }
@ -460,7 +460,7 @@ int remove_index_entry_at(struct index_state *istate, int pos)
struct cache_entry *ce = istate->cache[pos]; struct cache_entry *ce = istate->cache[pos];
record_resolve_undo(istate, ce); record_resolve_undo(istate, ce);
remove_name_hash(ce); remove_name_hash(istate, ce);
istate->cache_changed = 1; istate->cache_changed = 1;
istate->cache_nr--; istate->cache_nr--;
if (pos >= istate->cache_nr) if (pos >= istate->cache_nr)
@ -483,7 +483,7 @@ void remove_marked_cache_entries(struct index_state *istate)
for (i = j = 0; i < istate->cache_nr; i++) { for (i = j = 0; i < istate->cache_nr; i++) {
if (ce_array[i]->ce_flags & CE_REMOVE) if (ce_array[i]->ce_flags & CE_REMOVE)
remove_name_hash(ce_array[i]); remove_name_hash(istate, ce_array[i]);
else else
ce_array[j++] = ce_array[i]; ce_array[j++] = ce_array[i];
} }
@ -1515,8 +1515,7 @@ int discard_index(struct index_state *istate)
istate->cache_changed = 0; istate->cache_changed = 0;
istate->timestamp.sec = 0; istate->timestamp.sec = 0;
istate->timestamp.nsec = 0; istate->timestamp.nsec = 0;
istate->name_hash_initialized = 0; free_name_hash(istate);
free_hash(&istate->name_hash);
cache_tree_free(&(istate->cache_tree)); cache_tree_free(&(istate->cache_tree));
istate->initialized = 0; istate->initialized = 0;

49
refs.c
View File

@ -803,11 +803,38 @@ static const char *parse_ref_line(char *line, unsigned char *sha1)
return line; return line;
} }
/*
* Read f, which is a packed-refs file, into dir.
*
* A comment line of the form "# pack-refs with: " may contain zero or
* more traits. We interpret the traits as follows:
*
* No traits:
*
* Probably no references are peeled. But if the file contains a
* peeled value for a reference, we will use it.
*
* peeled:
*
* References under "refs/tags/", if they *can* be peeled, *are*
* peeled in this file. References outside of "refs/tags/" are
* probably not peeled even if they could have been, but if we find
* a peeled value for such a reference we will use it.
*
* fully-peeled:
*
* All references in the file that can be peeled are peeled.
* Inversely (and this is more important), any references in the
* file for which no peeled value is recorded is not peelable. This
* trait should typically be written alongside "peeled" for
* compatibility with older clients, but we do not require it
* (i.e., "peeled" is a no-op if "fully-peeled" is set).
*/
static void read_packed_refs(FILE *f, struct ref_dir *dir) static void read_packed_refs(FILE *f, struct ref_dir *dir)
{ {
struct ref_entry *last = NULL; struct ref_entry *last = NULL;
char refline[PATH_MAX]; char refline[PATH_MAX];
int flag = REF_ISPACKED; enum { PEELED_NONE, PEELED_TAGS, PEELED_FULLY } peeled = PEELED_NONE;
while (fgets(refline, sizeof(refline), f)) { while (fgets(refline, sizeof(refline), f)) {
unsigned char sha1[20]; unsigned char sha1[20];
@ -816,15 +843,20 @@ static void read_packed_refs(FILE *f, struct ref_dir *dir)
if (!strncmp(refline, header, sizeof(header)-1)) { if (!strncmp(refline, header, sizeof(header)-1)) {
const char *traits = refline + sizeof(header) - 1; const char *traits = refline + sizeof(header) - 1;
if (strstr(traits, " peeled ")) if (strstr(traits, " fully-peeled "))
flag |= REF_KNOWS_PEELED; peeled = PEELED_FULLY;
else if (strstr(traits, " peeled "))
peeled = PEELED_TAGS;
/* perhaps other traits later as well */ /* perhaps other traits later as well */
continue; continue;
} }
refname = parse_ref_line(refline, sha1); refname = parse_ref_line(refline, sha1);
if (refname) { if (refname) {
last = create_ref_entry(refname, sha1, flag, 1); last = create_ref_entry(refname, sha1, REF_ISPACKED, 1);
if (peeled == PEELED_FULLY ||
(peeled == PEELED_TAGS && !prefixcmp(refname, "refs/tags/")))
last->flag |= REF_KNOWS_PEELED;
add_ref(dir, last); add_ref(dir, last);
continue; continue;
} }
@ -832,8 +864,15 @@ static void read_packed_refs(FILE *f, struct ref_dir *dir)
refline[0] == '^' && refline[0] == '^' &&
strlen(refline) == 42 && strlen(refline) == 42 &&
refline[41] == '\n' && refline[41] == '\n' &&
!get_sha1_hex(refline + 1, sha1)) !get_sha1_hex(refline + 1, sha1)) {
hashcpy(last->u.value.peeled, sha1); hashcpy(last->u.value.peeled, sha1);
/*
* Regardless of what the file header said,
* we definitely know the value of *this*
* reference:
*/
last->flag |= REF_KNOWS_PEELED;
}
} }
} }

View File

@ -709,7 +709,7 @@ static int still_interesting(struct commit_list *src, unsigned long date, int sl
* Does the destination list contain entries with a date * Does the destination list contain entries with a date
* before the source list? Definitely _not_ done. * before the source list? Definitely _not_ done.
*/ */
if (date < src->item->date) if (date <= src->item->date)
return SLOP; return SLOP;
/* /*

View File

@ -12,85 +12,108 @@ the GIT controlled paths.
. ./test-lib.sh . ./test-lib.sh
test_expect_success \ test_expect_success 'setup' '
'setup' \ mkdir path1 &&
'mkdir path1 && echo frotz >path0 &&
echo frotz >path0 && echo rezrov >path1/file1 &&
echo rezrov >path1/file1 && git update-index --add path0 path1/file1
git update-index --add path0 path1/file1' '
test_expect_success SYMLINKS \ test_expect_success SYMLINKS 'have symlink in place where dir is expected.' '
'have symlink in place where dir is expected.' \ rm -fr path0 path1 &&
'rm -fr path0 path1 && mkdir path2 &&
mkdir path2 && ln -s path2 path1 &&
ln -s path2 path1 && git checkout-index -f -a &&
git checkout-index -f -a && test ! -h path1 && test -d path1 &&
test ! -h path1 && test -d path1 && test -f path1/file1 && test ! -f path2/file1
test -f path1/file1 && test ! -f path2/file1' '
test_expect_success \ test_expect_success 'use --prefix=path2/' '
'use --prefix=path2/' \ rm -fr path0 path1 path2 &&
'rm -fr path0 path1 path2 && mkdir path2 &&
mkdir path2 && git checkout-index --prefix=path2/ -f -a &&
git checkout-index --prefix=path2/ -f -a && test -f path2/path0 &&
test -f path2/path0 && test -f path2/path1/file1 &&
test -f path2/path1/file1 && test ! -f path0 &&
test ! -f path0 && test ! -f path1/file1
test ! -f path1/file1' '
test_expect_success \ test_expect_success 'use --prefix=tmp-' '
'use --prefix=tmp-' \ rm -fr path0 path1 path2 tmp* &&
'rm -fr path0 path1 path2 tmp* && git checkout-index --prefix=tmp- -f -a &&
git checkout-index --prefix=tmp- -f -a && test -f tmp-path0 &&
test -f tmp-path0 && test -f tmp-path1/file1 &&
test -f tmp-path1/file1 && test ! -f path0 &&
test ! -f path0 && test ! -f path1/file1
test ! -f path1/file1' '
test_expect_success \ test_expect_success 'use --prefix=tmp- but with a conflicting file and dir' '
'use --prefix=tmp- but with a conflicting file and dir' \ rm -fr path0 path1 path2 tmp* &&
'rm -fr path0 path1 path2 tmp* && echo nitfol >tmp-path1 &&
echo nitfol >tmp-path1 && mkdir tmp-path0 &&
mkdir tmp-path0 && git checkout-index --prefix=tmp- -f -a &&
git checkout-index --prefix=tmp- -f -a && test -f tmp-path0 &&
test -f tmp-path0 && test -f tmp-path1/file1 &&
test -f tmp-path1/file1 && test ! -f path0 &&
test ! -f path0 && test ! -f path1/file1
test ! -f path1/file1' '
# Linus fix #1 test_expect_success SYMLINKS 'use --prefix=tmp/orary/ where tmp is a symlink' '
test_expect_success SYMLINKS \ rm -fr path0 path1 path2 tmp* &&
'use --prefix=tmp/orary/ where tmp is a symlink' \ mkdir tmp1 tmp1/orary &&
'rm -fr path0 path1 path2 tmp* && ln -s tmp1 tmp &&
mkdir tmp1 tmp1/orary && git checkout-index --prefix=tmp/orary/ -f -a &&
ln -s tmp1 tmp && test -d tmp1/orary &&
git checkout-index --prefix=tmp/orary/ -f -a && test -f tmp1/orary/path0 &&
test -d tmp1/orary && test -f tmp1/orary/path1/file1 &&
test -f tmp1/orary/path0 && test -h tmp
test -f tmp1/orary/path1/file1 && '
test -h tmp'
# Linus fix #2 test_expect_success SYMLINKS 'use --prefix=tmp/orary- where tmp is a symlink' '
test_expect_success SYMLINKS \ rm -fr path0 path1 path2 tmp* &&
'use --prefix=tmp/orary- where tmp is a symlink' \ mkdir tmp1 &&
'rm -fr path0 path1 path2 tmp* && ln -s tmp1 tmp &&
mkdir tmp1 && git checkout-index --prefix=tmp/orary- -f -a &&
ln -s tmp1 tmp && test -f tmp1/orary-path0 &&
git checkout-index --prefix=tmp/orary- -f -a && test -f tmp1/orary-path1/file1 &&
test -f tmp1/orary-path0 && test -h tmp
test -f tmp1/orary-path1/file1 && '
test -h tmp'
# Linus fix #3 test_expect_success SYMLINKS 'use --prefix=tmp- where tmp-path1 is a symlink' '
test_expect_success SYMLINKS \ rm -fr path0 path1 path2 tmp* &&
'use --prefix=tmp- where tmp-path1 is a symlink' \ mkdir tmp1 &&
'rm -fr path0 path1 path2 tmp* && ln -s tmp1 tmp-path1 &&
mkdir tmp1 && git checkout-index --prefix=tmp- -f -a &&
ln -s tmp1 tmp-path1 && test -f tmp-path0 &&
git checkout-index --prefix=tmp- -f -a && test ! -h tmp-path1 &&
test -f tmp-path0 && test -d tmp-path1 &&
test ! -h tmp-path1 && test -f tmp-path1/file1
test -d tmp-path1 && '
test -f tmp-path1/file1'
test_expect_success 'apply filter from working tree .gitattributes with --prefix' '
rm -fr path0 path1 path2 tmp* &&
mkdir path1 &&
mkdir tmp &&
git config filter.replace-all.smudge "sed -e s/./,/g" &&
git config filter.replace-all.clean cat &&
git config filter.replace-all.required true &&
echo "file1 filter=replace-all" >path1/.gitattributes &&
git checkout-index --prefix=tmp/ -f -a &&
echo frotz >expected &&
test_cmp expected tmp/path0 &&
echo ,,,,,, >expected &&
test_cmp expected tmp/path1/file1
'
test_expect_success 'apply CRLF filter from working tree .gitattributes with --prefix' '
rm -fr path0 path1 path2 tmp* &&
mkdir path1 &&
mkdir tmp &&
echo "file1 eol=crlf" >path1/.gitattributes &&
git checkout-index --prefix=tmp/ -f -a &&
echo rezrovQ >expected &&
tr \\015 Q <tmp/path1/file1 >actual &&
test_cmp expected actual
'
test_done test_done

64
t/t3211-peel-ref.sh Executable file
View File

@ -0,0 +1,64 @@
#!/bin/sh
test_description='tests for the peel_ref optimization of packed-refs'
. ./test-lib.sh
test_expect_success 'create annotated tag in refs/tags' '
test_commit base &&
git tag -m annotated foo
'
test_expect_success 'create annotated tag outside of refs/tags' '
git update-ref refs/outside/foo refs/tags/foo
'
# This matches show-ref's output
print_ref() {
echo "$(git rev-parse "$1") $1"
}
test_expect_success 'set up expected show-ref output' '
{
print_ref "refs/heads/master" &&
print_ref "refs/outside/foo" &&
print_ref "refs/outside/foo^{}" &&
print_ref "refs/tags/base" &&
print_ref "refs/tags/foo" &&
print_ref "refs/tags/foo^{}"
} >expect
'
test_expect_success 'refs are peeled outside of refs/tags (loose)' '
git show-ref -d >actual &&
test_cmp expect actual
'
test_expect_success 'refs are peeled outside of refs/tags (packed)' '
git pack-refs --all &&
git show-ref -d >actual &&
test_cmp expect actual
'
test_expect_success 'create old-style pack-refs without fully-peeled' '
# Git no longer writes without fully-peeled, so we just write our own
# from scratch; we could also munge the existing file to remove the
# fully-peeled bits, but that seems even more prone to failure,
# especially if the format ever changes again. At least this way we
# know we are emulating exactly what an older git would have written.
{
echo "# pack-refs with: peeled " &&
print_ref "refs/heads/master" &&
print_ref "refs/outside/foo" &&
print_ref "refs/tags/base" &&
print_ref "refs/tags/foo" &&
echo "^$(git rev-parse "refs/tags/foo^{}")"
} >tmp &&
mv tmp .git/packed-refs
'
test_expect_success 'refs are peeled outside of refs/tags (old packed)' '
git show-ref -d >actual &&
test_cmp expect actual
'
test_done

View File

@ -133,4 +133,17 @@ test_expect_success 'dodecapus' '
check_revlist "--min-parents=13" && check_revlist "--min-parents=13" &&
check_revlist "--min-parents=4 --max-parents=11" tetrapus check_revlist "--min-parents=4 --max-parents=11" tetrapus
' '
test_expect_success 'ancestors with the same commit time' '
test_tick_keep=$test_tick &&
for i in 1 2 3 4 5 6 7 8; do
test_tick=$test_tick_keep
test_commit t$i
done &&
git rev-list t1^! --not t$i >result &&
>expect &&
test_cmp expect result
'
test_done test_done

20
t/t7062-wtstatus-ignorecase.sh Executable file
View File

@ -0,0 +1,20 @@
#!/bin/sh
test_description='git-status with core.ignorecase=true'
. ./test-lib.sh
test_expect_success 'status with hash collisions' '
# note: "V/", "V/XQANY/" and "WURZAUP/" produce the same hash code
# in name-hash.c::hash_name
mkdir V &&
mkdir V/XQANY &&
mkdir WURZAUP &&
touch V/XQANY/test &&
git config core.ignorecase true &&
git add . &&
# test is successful if git status completes (no endless loop)
git status
'
test_done