Merge branch 'mh/ref-remove-empty-directory'
authorJunio C Hamano <gitster@pobox.com>
Mon, 27 Feb 2017 21:57:12 +0000 (13:57 -0800)
committerJunio C Hamano <gitster@pobox.com>
Mon, 27 Feb 2017 21:57:12 +0000 (13:57 -0800)
Deletion of a branch "foo/bar" could remove .git/refs/heads/foo
once there no longer is any other branch whose name begins with
"foo/", but we didn't do so so far. Now we do.

* mh/ref-remove-empty-directory: (23 commits)
files_transaction_commit(): clean up empty directories
try_remove_empty_parents(): teach to remove parents of reflogs, too
try_remove_empty_parents(): don't trash argument contents
try_remove_empty_parents(): rename parameter "name" -> "refname"
delete_ref_loose(): inline function
delete_ref_loose(): derive loose reference path from lock
log_ref_write_1(): inline function
log_ref_setup(): manage the name of the reflog file internally
log_ref_write_1(): don't depend on logfile argument
log_ref_setup(): pass the open file descriptor back to the caller
log_ref_setup(): improve robustness against races
log_ref_setup(): separate code for create vs non-create
log_ref_write(): inline function
rename_tmp_log(): improve error reporting
rename_tmp_log(): use raceproof_create_file()
lock_ref_sha1_basic(): use raceproof_create_file()
lock_ref_sha1_basic(): inline constant
raceproof_create_file(): new function
safe_create_leading_directories(): set errno on SCLD_EXISTS
safe_create_leading_directories_const(): preserve errno
...

1  2 
cache.h
refs/files-backend.c
refs/refs-internal.h
sha1_file.c
t/t1400-update-ref.sh
t/t5505-remote.sh
diff --combined cache.h
index 61fc86e6d7199518555632a0b0b584471b9083a8,95cde149a9aed4560db53da929c3866c09200881..80b6372cf765155b4ea34720e6d65684600c7c12
+++ b/cache.h
@@@ -507,16 -507,14 +507,16 @@@ extern int is_nonbare_repository_dir(st
  #define READ_GITFILE_ERR_NO_PATH 6
  #define READ_GITFILE_ERR_NOT_A_REPO 7
  #define READ_GITFILE_ERR_TOO_LARGE 8
 +extern void read_gitfile_error_die(int error_code, const char *path, const char *dir);
  extern const char *read_gitfile_gently(const char *path, int *return_error_code);
  #define read_gitfile(path) read_gitfile_gently((path), NULL)
 -extern const char *resolve_gitdir(const char *suspect);
 +extern const char *resolve_gitdir_gently(const char *suspect, int *return_error_code);
 +#define resolve_gitdir(path) resolve_gitdir_gently((path), NULL)
 +
  extern void set_git_work_tree(const char *tree);
  
  #define ALTERNATE_DB_ENVIRONMENT "GIT_ALTERNATE_OBJECT_DIRECTORIES"
  
 -extern const char **get_pathspec(const char *prefix, const char **pathspec);
  extern void setup_work_tree(void);
  extern const char *setup_git_directory_gently(int *);
  extern const char *setup_git_directory(void);
@@@ -577,26 -575,7 +577,26 @@@ extern int verify_path(const char *path
  extern int index_dir_exists(struct index_state *istate, const char *name, int namelen);
  extern void adjust_dirname_case(struct index_state *istate, char *name);
  extern struct cache_entry *index_file_exists(struct index_state *istate, const char *name, int namelen, int igncase);
 +
 +/*
 + * Searches for an entry defined by name and namelen in the given index.
 + * If the return value is positive (including 0) it is the position of an
 + * exact match. If the return value is negative, the negated value minus 1
 + * is the position where the entry would be inserted.
 + * Example: The current index consists of these files and its stages:
 + *
 + *   b#0, d#0, f#1, f#3
 + *
 + * index_name_pos(&index, "a", 1) -> -1
 + * index_name_pos(&index, "b", 1) ->  0
 + * index_name_pos(&index, "c", 1) -> -2
 + * index_name_pos(&index, "d", 1) ->  1
 + * index_name_pos(&index, "e", 1) -> -3
 + * index_name_pos(&index, "f", 1) -> -3
 + * index_name_pos(&index, "g", 1) -> -5
 + */
  extern int index_name_pos(const struct index_state *, const char *name, int namelen);
 +
  #define ADD_CACHE_OK_TO_ADD 1         /* Ok to add */
  #define ADD_CACHE_OK_TO_REPLACE 2     /* Ok to replace file/directory */
  #define ADD_CACHE_SKIP_DFCHECK 4      /* Ok to skip DF conflict checks */
  #define ADD_CACHE_KEEP_CACHE_TREE 32  /* Do not invalidate cache-tree */
  extern int add_index_entry(struct index_state *, struct cache_entry *ce, int option);
  extern void rename_index_entry_at(struct index_state *, int pos, const char *new_name);
 +
 +/* Remove entry, return true if there are more entries to go. */
  extern int remove_index_entry_at(struct index_state *, int pos);
 +
  extern void remove_marked_cache_entries(struct index_state *istate);
  extern int remove_file_from_index(struct index_state *, const char *path);
  #define ADD_CACHE_VERBOSE 1
  #define ADD_CACHE_IGNORE_ERRORS       4
  #define ADD_CACHE_IGNORE_REMOVAL 8
  #define ADD_CACHE_INTENT 16
 +/*
 + * These two are used to add the contents of the file at path
 + * to the index, marking the working tree up-to-date by storing
 + * the cached stat info in the resulting cache entry.  A caller
 + * that has already run lstat(2) on the path can call
 + * add_to_index(), and all others can call add_file_to_index();
 + * the latter will do necessary lstat(2) internally before
 + * calling the former.
 + */
  extern int add_to_index(struct index_state *, const char *path, struct stat *, int flags);
  extern int add_file_to_index(struct index_state *, const char *path, int flags);
 +
  extern struct cache_entry *make_cache_entry(unsigned int mode, const unsigned char *sha1, const char *path, int stage, unsigned int refresh_options);
  extern int chmod_index_entry(struct index_state *, struct cache_entry *ce, char flip);
  extern int ce_same_name(const struct cache_entry *a, const struct cache_entry *b);
  extern void set_object_name_for_intent_to_add_entry(struct cache_entry *ce);
  extern int index_name_is_other(const struct index_state *, const char *, int);
 -extern void *read_blob_data_from_index(struct index_state *, const char *, unsigned long *);
 +extern void *read_blob_data_from_index(const struct index_state *, const char *, unsigned long *);
  
  /* do stat comparison even if CE_VALID is true */
  #define CE_MATCH_IGNORE_VALID         01
@@@ -695,6 -661,7 +695,6 @@@ extern int minimum_abbrev, default_abbr
  extern int ignore_case;
  extern int assume_unchanged;
  extern int prefer_symlink_refs;
 -extern int log_all_ref_updates;
  extern int warn_ambiguous_refs;
  extern int warn_on_object_refname_ambiguity;
  extern const char *apply_default_whitespace;
@@@ -703,7 -670,7 +703,7 @@@ extern const char *git_attributes_file
  extern const char *git_hooks_path;
  extern int zlib_compression_level;
  extern int core_compression_level;
 -extern int core_compression_seen;
 +extern int pack_compression_level;
  extern size_t packed_git_window_size;
  extern size_t packed_git_limit;
  extern size_t delta_base_cache_limit;
@@@ -762,14 -729,6 +762,14 @@@ enum hide_dotfiles_type 
  };
  extern enum hide_dotfiles_type hide_dotfiles;
  
 +enum log_refs_config {
 +      LOG_REFS_UNSET = -1,
 +      LOG_REFS_NONE = 0,
 +      LOG_REFS_NORMAL,
 +      LOG_REFS_ALWAYS
 +};
 +extern enum log_refs_config log_all_ref_updates;
 +
  enum branch_track {
        BRANCH_TRACK_UNSPECIFIED = -1,
        BRANCH_TRACK_NEVER = 0,
@@@ -1072,8 -1031,9 +1072,9 @@@ int adjust_shared_perm(const char *path
  
  /*
   * Create the directory containing the named path, using care to be
-  * somewhat safe against races.  Return one of the scld_error values
-  * to indicate success/failure.
+  * somewhat safe against races. Return one of the scld_error values to
+  * indicate success/failure. On error, set errno to describe the
+  * problem.
   *
   * SCLD_VANISHED indicates that one of the ancestor directories of the
   * path existed at one point during the function call and then
@@@ -1097,6 -1057,49 +1098,49 @@@ enum scld_error 
  enum scld_error safe_create_leading_directories(char *path);
  enum scld_error safe_create_leading_directories_const(const char *path);
  
+ /*
+  * Callback function for raceproof_create_file(). This function is
+  * expected to do something that makes dirname(path) permanent despite
+  * the fact that other processes might be cleaning up empty
+  * directories at the same time. Usually it will create a file named
+  * path, but alternatively it could create another file in that
+  * directory, or even chdir() into that directory. The function should
+  * return 0 if the action was completed successfully. On error, it
+  * should return a nonzero result and set errno.
+  * raceproof_create_file() treats two errno values specially:
+  *
+  * - ENOENT -- dirname(path) does not exist. In this case,
+  *             raceproof_create_file() tries creating dirname(path)
+  *             (and any parent directories, if necessary) and calls
+  *             the function again.
+  *
+  * - EISDIR -- the file already exists and is a directory. In this
+  *             case, raceproof_create_file() removes the directory if
+  *             it is empty (and recursively any empty directories that
+  *             it contains) and calls the function again.
+  *
+  * Any other errno causes raceproof_create_file() to fail with the
+  * callback's return value and errno.
+  *
+  * Obviously, this function should be OK with being called again if it
+  * fails with ENOENT or EISDIR. In other scenarios it will not be
+  * called again.
+  */
+ typedef int create_file_fn(const char *path, void *cb);
+ /*
+  * Create a file in dirname(path) by calling fn, creating leading
+  * directories if necessary. Retry a few times in case we are racing
+  * with another process that is trying to clean up the directory that
+  * contains path. See the documentation for create_file_fn for more
+  * details.
+  *
+  * Return the value and set the errno that resulted from the most
+  * recent call of fn. fn is always called at least once, and will be
+  * called more than once if it returns ENOENT or EISDIR.
+  */
+ int raceproof_create_file(const char *path, create_file_fn fn, void *cb);
  int mkdir_in_gitdir(const char *path);
  extern char *expand_user_path(const char *path);
  const char *enter_repo(const char *path, int strict);
@@@ -1105,13 -1108,9 +1149,13 @@@ static inline int is_absolute_path(cons
        return is_dir_sep(path[0]) || has_dos_drive_prefix(path);
  }
  int is_directory(const char *);
 +char *strbuf_realpath(struct strbuf *resolved, const char *path,
 +                    int die_on_error);
  const char *real_path(const char *path);
  const char *real_path_if_valid(const char *path);
 +char *real_pathdup(const char *path);
  const char *absolute_path(const char *path);
 +char *absolute_pathdup(const char *path);
  const char *remove_leading_path(const char *in, const char *prefix);
  const char *relative_path(const char *in, const char *prefix, struct strbuf *sb);
  int normalize_path_copy_len(char *dst, const char *src, int *prefix_len);
@@@ -1170,8 -1169,7 +1214,8 @@@ extern int write_sha1_file(const void *
  extern int hash_sha1_file_literally(const void *buf, unsigned long len, const char *type, unsigned char *sha1, unsigned flags);
  extern int pretend_sha1_file(void *, unsigned long, enum object_type, unsigned char *);
  extern int force_object_loose(const unsigned char *sha1, time_t mtime);
 -extern int git_open(const char *name);
 +extern int git_open_cloexec(const char *name, int flags);
 +#define git_open(name) git_open_cloexec(name, O_RDONLY)
  extern void *map_sha1_file(const unsigned char *sha1, unsigned long *size);
  extern int unpack_sha1_header(git_zstream *stream, unsigned char *map, unsigned long mapsize, void *buffer, unsigned long bufsiz);
  extern int parse_sha1_header(const char *hdr, unsigned long *sizep);
@@@ -1185,19 -1183,6 +1229,19 @@@ extern int finalize_object_file(const c
  
  extern int has_sha1_pack(const unsigned char *sha1);
  
 +/*
 + * Open the loose object at path, check its sha1, and return the contents,
 + * type, and size. If the object is a blob, then "contents" may return NULL,
 + * to allow streaming of large blobs.
 + *
 + * Returns 0 on success, negative on error (details may be written to stderr).
 + */
 +int read_loose_object(const char *path,
 +                    const unsigned char *expected_sha1,
 +                    enum object_type *type,
 +                    unsigned long *size,
 +                    void **contents);
 +
  /*
   * Return true iff we have an object named sha1, whether local or in
   * an alternate object database, and whether packed or loose.  This
@@@ -1749,8 -1734,6 +1793,8 @@@ extern int git_default_config(const cha
  extern int git_config_from_file(config_fn_t fn, const char *, void *);
  extern int git_config_from_mem(config_fn_t fn, const enum config_origin_type,
                                        const char *name, const char *buf, size_t len, void *data);
 +extern int git_config_from_blob_sha1(config_fn_t fn, const char *name,
 +                                   const unsigned char *sha1, void *data);
  extern void git_config_push_parameter(const char *text);
  extern int git_config_from_parameters(config_fn_t fn, void *data);
  extern void git_config(config_fn_t fn, void *);
diff --combined refs/files-backend.c
index c041d4ba21a8fe70eb6d8277358e7e3f29a69bfb,c426575fe9d4832dc101c08ff86964f129f3a94b..21e116d4e620b9de77def18bf89054399477cf62
@@@ -697,7 -697,7 +697,7 @@@ static int cache_ref_iterator_peel(stru
  
        if (peel_entry(entry, 0))
                return -1;
 -      hashcpy(peeled->hash, entry->u.value.peeled.hash);
 +      oidcpy(peeled, &entry->u.value.peeled);
        return 0;
  }
  
@@@ -1985,6 -1985,13 +1985,13 @@@ static int remove_empty_directories(str
        return remove_dir_recursively(path, REMOVE_DIR_EMPTY_ONLY);
  }
  
+ static int create_reflock(const char *path, void *cb)
+ {
+       struct lock_file *lk = cb;
+       return hold_lock_file_for_update(lk, path, LOCK_NO_DEREF) < 0 ? -1 : 0;
+ }
  /*
   * Locks a ref returning the lock on success and NULL on failure.
   * On failure errno is set to something meaningful.
@@@ -2000,10 -2007,8 +2007,8 @@@ static struct ref_lock *lock_ref_sha1_b
        struct strbuf ref_file = STRBUF_INIT;
        struct ref_lock *lock;
        int last_errno = 0;
-       int lflags = LOCK_NO_DEREF;
        int mustexist = (old_sha1 && !is_null_sha1(old_sha1));
        int resolve_flags = RESOLVE_REF_NO_RECURSE;
-       int attempts_remaining = 3;
        int resolved;
  
        assert_main_repository(&refs->base, "lock_ref_sha1_basic");
  
        lock->ref_name = xstrdup(refname);
  
-  retry:
-       switch (safe_create_leading_directories_const(ref_file.buf)) {
-       case SCLD_OK:
-               break; /* success */
-       case SCLD_VANISHED:
-               if (--attempts_remaining > 0)
-                       goto retry;
-               /* fall through */
-       default:
+       if (raceproof_create_file(ref_file.buf, create_reflock, lock->lk)) {
                last_errno = errno;
-               strbuf_addf(err, "unable to create directory for '%s'",
-                           ref_file.buf);
+               unable_to_lock_message(ref_file.buf, errno, err);
                goto error_return;
        }
  
-       if (hold_lock_file_for_update(lock->lk, ref_file.buf, lflags) < 0) {
-               last_errno = errno;
-               if (errno == ENOENT && --attempts_remaining > 0)
-                       /*
-                        * Maybe somebody just deleted one of the
-                        * directories leading to ref_file.  Try
-                        * again:
-                        */
-                       goto retry;
-               else {
-                       unable_to_lock_message(ref_file.buf, errno, err);
-                       goto error_return;
-               }
-       }
        if (verify_lock(lock, old_sha1, mustexist, err)) {
                last_errno = errno;
                goto error_return;
@@@ -2298,15 -2280,25 +2280,25 @@@ static int pack_if_possible_fn(struct r
        return 0;
  }
  
+ enum {
+       REMOVE_EMPTY_PARENTS_REF = 0x01,
+       REMOVE_EMPTY_PARENTS_REFLOG = 0x02
+ };
  /*
-  * Remove empty parents, but spare refs/ and immediate subdirs.
-  * Note: munges *name.
+  * Remove empty parent directories associated with the specified
+  * reference and/or its reflog, but spare [logs/]refs/ and immediate
+  * subdirs. flags is a combination of REMOVE_EMPTY_PARENTS_REF and/or
+  * REMOVE_EMPTY_PARENTS_REFLOG.
   */
- static void try_remove_empty_parents(char *name)
+ static void try_remove_empty_parents(const char *refname, unsigned int flags)
  {
+       struct strbuf buf = STRBUF_INIT;
        char *p, *q;
        int i;
-       p = name;
+       strbuf_addstr(&buf, refname);
+       p = buf.buf;
        for (i = 0; i < 2; i++) { /* refs/{heads,tags,...}/ */
                while (*p && *p != '/')
                        p++;
                while (*p == '/')
                        p++;
        }
-       for (q = p; *q; q++)
-               ;
-       while (1) {
+       q = buf.buf + buf.len;
+       while (flags & (REMOVE_EMPTY_PARENTS_REF | REMOVE_EMPTY_PARENTS_REFLOG)) {
                while (q > p && *q != '/')
                        q--;
                while (q > p && *(q-1) == '/')
                        q--;
                if (q == p)
                        break;
-               *q = '\0';
-               if (rmdir(git_path("%s", name)))
-                       break;
+               strbuf_setlen(&buf, q - buf.buf);
+               if ((flags & REMOVE_EMPTY_PARENTS_REF) &&
+                   rmdir(git_path("%s", buf.buf)))
+                       flags &= ~REMOVE_EMPTY_PARENTS_REF;
+               if ((flags & REMOVE_EMPTY_PARENTS_REFLOG) &&
+                   rmdir(git_path("logs/%s", buf.buf)))
+                       flags &= ~REMOVE_EMPTY_PARENTS_REFLOG;
        }
+       strbuf_release(&buf);
  }
  
  /* make sure nobody touched the ref, and unlink */
@@@ -2350,7 -2346,6 +2346,6 @@@ static void prune_ref(struct ref_to_pru
        }
        ref_transaction_free(transaction);
        strbuf_release(&err);
-       try_remove_empty_parents(r->name);
  }
  
  static void prune_refs(struct ref_to_prune *r)
@@@ -2439,24 -2434,6 +2434,6 @@@ static int repack_without_refs(struct f
        return ret;
  }
  
- static int delete_ref_loose(struct ref_lock *lock, int flag, struct strbuf *err)
- {
-       assert(err);
-       if (!(flag & REF_ISPACKED) || flag & REF_ISSYMREF) {
-               /*
-                * loose.  The loose file name is the same as the
-                * lockfile name, minus ".lock":
-                */
-               char *loose_filename = get_locked_file_path(lock->lk);
-               int res = unlink_or_msg(loose_filename, err);
-               free(loose_filename);
-               if (res)
-                       return 1;
-       }
-       return 0;
- }
  static int files_delete_refs(struct ref_store *ref_store,
                             struct string_list *refnames, unsigned int flags)
  {
   */
  #define TMP_RENAMED_LOG  "logs/refs/.tmp-renamed-log"
  
- static int rename_tmp_log(const char *newrefname)
+ static int rename_tmp_log_callback(const char *path, void *cb)
  {
-       int attempts_remaining = 4;
-       struct strbuf path = STRBUF_INIT;
-       int ret = -1;
+       int *true_errno = cb;
  
-  retry:
-       strbuf_reset(&path);
-       strbuf_git_path(&path, "logs/%s", newrefname);
-       switch (safe_create_leading_directories_const(path.buf)) {
-       case SCLD_OK:
-               break; /* success */
-       case SCLD_VANISHED:
-               if (--attempts_remaining > 0)
-                       goto retry;
-               /* fall through */
-       default:
-               error("unable to create directory for %s", newrefname);
-               goto out;
+       if (rename(git_path(TMP_RENAMED_LOG), path)) {
+               /*
+                * rename(a, b) when b is an existing directory ought
+                * to result in ISDIR, but Solaris 5.8 gives ENOTDIR.
+                * Sheesh. Record the true errno for error reporting,
+                * but report EISDIR to raceproof_create_file() so
+                * that it knows to retry.
+                */
+               *true_errno = errno;
+               if (errno == ENOTDIR)
+                       errno = EISDIR;
+               return -1;
+       } else {
+               return 0;
        }
+ }
  
-       if (rename(git_path(TMP_RENAMED_LOG), path.buf)) {
-               if ((errno==EISDIR || errno==ENOTDIR) && --attempts_remaining > 0) {
-                       /*
-                        * rename(a, b) when b is an existing
-                        * directory ought to result in ISDIR, but
-                        * Solaris 5.8 gives ENOTDIR.  Sheesh.
-                        */
-                       if (remove_empty_directories(&path)) {
-                               error("Directory not empty: logs/%s", newrefname);
-                               goto out;
-                       }
-                       goto retry;
-               } else if (errno == ENOENT && --attempts_remaining > 0) {
-                       /*
-                        * Maybe another process just deleted one of
-                        * the directories in the path to newrefname.
-                        * Try again from the beginning.
-                        */
-                       goto retry;
-               } else {
-                       error("unable to move logfile "TMP_RENAMED_LOG" to logs/%s: %s",
-                               newrefname, strerror(errno));
-                       goto out;
-               }
+ static int rename_tmp_log(const char *newrefname)
+ {
+       char *path = git_pathdup("logs/%s", newrefname);
+       int ret, true_errno;
+       ret = raceproof_create_file(path, rename_tmp_log_callback, &true_errno);
+       if (ret) {
+               if (errno == EISDIR)
+                       error("directory not empty: %s", path);
+               else
+                       error("unable to move logfile %s to %s: %s",
+                             git_path(TMP_RENAMED_LOG), path,
+                             strerror(true_errno));
        }
-       ret = 0;
- out:
-       strbuf_release(&path);
+       free(path);
        return ret;
  }
  
@@@ -2631,7 -2596,7 +2596,7 @@@ static int files_rename_ref(struct ref_
        if (!read_ref_full(newrefname, RESOLVE_REF_READING | RESOLVE_REF_NO_RECURSE,
                           sha1, NULL) &&
            delete_ref(newrefname, NULL, REF_NODEREF)) {
-               if (errno==EISDIR) {
+               if (errno == EISDIR) {
                        struct strbuf path = STRBUF_INIT;
                        int result;
  
        }
  
        flag = log_all_ref_updates;
 -      log_all_ref_updates = 0;
 +      log_all_ref_updates = LOG_REFS_NONE;
        if (write_ref_to_lockfile(lock, orig_sha1, &err) ||
            commit_ref_update(refs, lock, orig_sha1, NULL, &err)) {
                error("unable to write current sha1 into %s: %s", oldrefname, err.buf);
@@@ -2740,66 -2705,89 +2705,89 @@@ static int commit_ref(struct ref_lock *
        return 0;
  }
  
+ static int open_or_create_logfile(const char *path, void *cb)
+ {
+       int *fd = cb;
+       *fd = open(path, O_APPEND | O_WRONLY | O_CREAT, 0666);
+       return (*fd < 0) ? -1 : 0;
+ }
  /*
-  * Create a reflog for a ref.  If force_create = 0, the reflog will
-  * only be created for certain refs (those for which
-  * should_autocreate_reflog returns non-zero.  Otherwise, create it
-  * regardless of the ref name.  Fill in *err and return -1 on failure.
+  * Create a reflog for a ref. If force_create = 0, only create the
+  * reflog for certain refs (those for which should_autocreate_reflog
+  * returns non-zero). Otherwise, create it regardless of the reference
+  * name. If the logfile already existed or was created, return 0 and
+  * set *logfd to the file descriptor opened for appending to the file.
+  * If no logfile exists and we decided not to create one, return 0 and
+  * set *logfd to -1. On failure, fill in *err, set *logfd to -1, and
+  * return -1.
   */
- static int log_ref_setup(const char *refname, struct strbuf *logfile, struct strbuf *err, int force_create)
+ static int log_ref_setup(const char *refname, int force_create,
+                        int *logfd, struct strbuf *err)
  {
-       int logfd, oflags = O_APPEND | O_WRONLY;
+       char *logfile = git_pathdup("logs/%s", refname);
  
-       strbuf_git_path(logfile, "logs/%s", refname);
        if (force_create || should_autocreate_reflog(refname)) {
-               if (safe_create_leading_directories(logfile->buf) < 0) {
-                       strbuf_addf(err, "unable to create directory for '%s': "
-                                   "%s", logfile->buf, strerror(errno));
-                       return -1;
-               }
-               oflags |= O_CREAT;
-       }
-       logfd = open(logfile->buf, oflags, 0666);
-       if (logfd < 0) {
-               if (!(oflags & O_CREAT) && (errno == ENOENT || errno == EISDIR))
-                       return 0;
+               if (raceproof_create_file(logfile, open_or_create_logfile, logfd)) {
+                       if (errno == ENOENT)
+                               strbuf_addf(err, "unable to create directory for '%s': "
+                                           "%s", logfile, strerror(errno));
+                       else if (errno == EISDIR)
+                               strbuf_addf(err, "there are still logs under '%s'",
+                                           logfile);
+                       else
+                               strbuf_addf(err, "unable to append to '%s': %s",
+                                           logfile, strerror(errno));
  
-               if (errno == EISDIR) {
-                       if (remove_empty_directories(logfile)) {
-                               strbuf_addf(err, "there are still logs under "
-                                           "'%s'", logfile->buf);
-                               return -1;
-                       }
-                       logfd = open(logfile->buf, oflags, 0666);
+                       goto error;
                }
-               if (logfd < 0) {
-                       strbuf_addf(err, "unable to append to '%s': %s",
-                                   logfile->buf, strerror(errno));
-                       return -1;
+       } else {
+               *logfd = open(logfile, O_APPEND | O_WRONLY, 0666);
+               if (*logfd < 0) {
+                       if (errno == ENOENT || errno == EISDIR) {
+                               /*
+                                * The logfile doesn't already exist,
+                                * but that is not an error; it only
+                                * means that we won't write log
+                                * entries to it.
+                                */
+                               ;
+                       } else {
+                               strbuf_addf(err, "unable to append to '%s': %s",
+                                           logfile, strerror(errno));
+                               goto error;
+                       }
                }
        }
  
-       adjust_shared_perm(logfile->buf);
-       close(logfd);
+       if (*logfd >= 0)
+               adjust_shared_perm(logfile);
+       free(logfile);
        return 0;
- }
  
+ error:
+       free(logfile);
+       return -1;
+ }
  
  static int files_create_reflog(struct ref_store *ref_store,
                               const char *refname, int force_create,
                               struct strbuf *err)
  {
-       int ret;
-       struct strbuf sb = STRBUF_INIT;
+       int fd;
  
        /* Check validity (but we don't need the result): */
        files_downcast(ref_store, 0, "create_reflog");
  
-       ret = log_ref_setup(refname, &sb, err, force_create);
-       strbuf_release(&sb);
-       return ret;
+       if (log_ref_setup(refname, force_create, &fd, err))
+               return -1;
+       if (fd >= 0)
+               close(fd);
+       return 0;
  }
  
  static int log_ref_write_fd(int fd, const unsigned char *old_sha1,
        return 0;
  }
  
- static int log_ref_write_1(const char *refname, const unsigned char *old_sha1,
-                          const unsigned char *new_sha1, const char *msg,
-                          struct strbuf *logfile, int flags,
-                          struct strbuf *err)
+ int files_log_ref_write(const char *refname, const unsigned char *old_sha1,
+                       const unsigned char *new_sha1, const char *msg,
+                       int flags, struct strbuf *err)
  {
-       int logfd, result, oflags = O_APPEND | O_WRONLY;
+       int logfd, result;
  
 -      if (log_all_ref_updates < 0)
 -              log_all_ref_updates = !is_bare_repository();
 +      if (log_all_ref_updates == LOG_REFS_UNSET)
 +              log_all_ref_updates = is_bare_repository() ? LOG_REFS_NONE : LOG_REFS_NORMAL;
  
-       result = log_ref_setup(refname, logfile, err, flags & REF_FORCE_CREATE_REFLOG);
+       result = log_ref_setup(refname, flags & REF_FORCE_CREATE_REFLOG,
+                              &logfd, err);
  
        if (result)
                return result;
  
-       logfd = open(logfile->buf, oflags);
        if (logfd < 0)
                return 0;
        result = log_ref_write_fd(logfd, old_sha1, new_sha1,
                                  git_committer_info(0), msg);
        if (result) {
-               strbuf_addf(err, "unable to append to '%s': %s", logfile->buf,
-                           strerror(errno));
+               int save_errno = errno;
+               strbuf_addf(err, "unable to append to '%s': %s",
+                           git_path("logs/%s", refname), strerror(save_errno));
                close(logfd);
                return -1;
        }
        if (close(logfd)) {
-               strbuf_addf(err, "unable to append to '%s': %s", logfile->buf,
-                           strerror(errno));
+               int save_errno = errno;
+               strbuf_addf(err, "unable to append to '%s': %s",
+                           git_path("logs/%s", refname), strerror(save_errno));
                return -1;
        }
        return 0;
  }
  
- static int log_ref_write(const char *refname, const unsigned char *old_sha1,
-                        const unsigned char *new_sha1, const char *msg,
-                        int flags, struct strbuf *err)
- {
-       return files_log_ref_write(refname, old_sha1, new_sha1, msg, flags,
-                                  err);
- }
- int files_log_ref_write(const char *refname, const unsigned char *old_sha1,
-                       const unsigned char *new_sha1, const char *msg,
-                       int flags, struct strbuf *err)
- {
-       struct strbuf sb = STRBUF_INIT;
-       int ret = log_ref_write_1(refname, old_sha1, new_sha1, msg, &sb, flags,
-                                 err);
-       strbuf_release(&sb);
-       return ret;
- }
  /*
   * Write sha1 into the open lockfile, then close the lockfile. On
   * errors, rollback the lockfile, fill in *err and
@@@ -2933,7 -2905,8 +2905,8 @@@ static int commit_ref_update(struct fil
        assert_main_repository(&refs->base, "commit_ref_update");
  
        clear_loose_ref_cache(refs);
-       if (log_ref_write(lock->ref_name, lock->old_oid.hash, sha1, logmsg, 0, err)) {
+       if (files_log_ref_write(lock->ref_name, lock->old_oid.hash, sha1,
+                               logmsg, 0, err)) {
                char *old_msg = strbuf_detach(err, NULL);
                strbuf_addf(err, "cannot update the ref '%s': %s",
                            lock->ref_name, old_msg);
                if (head_ref && (head_flag & REF_ISSYMREF) &&
                    !strcmp(head_ref, lock->ref_name)) {
                        struct strbuf log_err = STRBUF_INIT;
-                       if (log_ref_write("HEAD", lock->old_oid.hash, sha1,
+                       if (files_log_ref_write("HEAD", lock->old_oid.hash, sha1,
                                          logmsg, 0, &log_err)) {
                                error("%s", log_err.buf);
                                strbuf_release(&log_err);
@@@ -3003,7 -2976,8 +2976,8 @@@ static void update_symref_reflog(struc
        struct strbuf err = STRBUF_INIT;
        unsigned char new_sha1[20];
        if (logmsg && !read_ref(target, new_sha1) &&
-           log_ref_write(refname, lock->old_oid.hash, new_sha1, logmsg, 0, &err)) {
+           files_log_ref_write(refname, lock->old_oid.hash, new_sha1,
+                               logmsg, 0, &err)) {
                error("%s", err.buf);
                strbuf_release(&err);
        }
@@@ -3778,9 -3752,11 +3752,11 @@@ static int files_transaction_commit(str
  
                if (update->flags & REF_NEEDS_COMMIT ||
                    update->flags & REF_LOG_ONLY) {
-                       if (log_ref_write(lock->ref_name, lock->old_oid.hash,
-                                         update->new_sha1,
-                                         update->msg, update->flags, err)) {
+                       if (files_log_ref_write(lock->ref_name,
+                                               lock->old_oid.hash,
+                                               update->new_sha1,
+                                               update->msg, update->flags,
+                                               err)) {
                                char *old_msg = strbuf_detach(err, NULL);
  
                                strbuf_addf(err, "cannot update the ref '%s': %s",
  
                if (update->flags & REF_DELETING &&
                    !(update->flags & REF_LOG_ONLY)) {
-                       if (delete_ref_loose(lock, update->type, err)) {
-                               ret = TRANSACTION_GENERIC_ERROR;
-                               goto cleanup;
+                       if (!(update->type & REF_ISPACKED) ||
+                           update->type & REF_ISSYMREF) {
+                               /* It is a loose reference. */
+                               if (unlink_or_msg(git_path("%s", lock->ref_name), err)) {
+                                       ret = TRANSACTION_GENERIC_ERROR;
+                                       goto cleanup;
+                               }
+                               update->flags |= REF_DELETED_LOOSE;
                        }
  
                        if (!(update->flags & REF_ISPRUNING))
                ret = TRANSACTION_GENERIC_ERROR;
                goto cleanup;
        }
-       for_each_string_list_item(ref_to_delete, &refs_to_delete)
-               unlink_or_warn(git_path("logs/%s", ref_to_delete->string));
+       /* Delete the reflogs of any references that were deleted: */
+       for_each_string_list_item(ref_to_delete, &refs_to_delete) {
+               if (!unlink_or_warn(git_path("logs/%s", ref_to_delete->string)))
+                       try_remove_empty_parents(ref_to_delete->string,
+                                                REMOVE_EMPTY_PARENTS_REFLOG);
+       }
        clear_loose_ref_cache(refs);
  
  cleanup:
        transaction->state = REF_TRANSACTION_CLOSED;
  
-       for (i = 0; i < transaction->nr; i++)
-               if (transaction->updates[i]->backend_data)
-                       unlock_ref(transaction->updates[i]->backend_data);
+       for (i = 0; i < transaction->nr; i++) {
+               struct ref_update *update = transaction->updates[i];
+               struct ref_lock *lock = update->backend_data;
+               if (lock)
+                       unlock_ref(lock);
+               if (update->flags & REF_DELETED_LOOSE) {
+                       /*
+                        * The loose reference was deleted. Delete any
+                        * empty parent directories. (Note that this
+                        * can only work because we have already
+                        * removed the lockfile.)
+                        */
+                       try_remove_empty_parents(update->refname,
+                                                REMOVE_EMPTY_PARENTS_REF);
+               }
+       }
        string_list_clear(&refs_to_delete, 0);
        free(head_ref);
        string_list_clear(&affected_refnames, 0);
diff --combined refs/refs-internal.h
index 25444cf5b0f6448e4a1ada56d48f09ab3017a759,15d5a1e08842f8ec0b35d67cfb573d6ad8784521..611ab5b1d1ca250fca54749119489dbaba6dcc22
   */
  #define REF_UPDATE_VIA_HEAD 0x100
  
+ /*
+  * Used as a flag in ref_update::flags when the loose reference has
+  * been deleted.
+  */
+ #define REF_DELETED_LOOSE 0x200
  /*
   * Return true iff refname is minimally safe. "Safe" here means that
   * deleting a loose reference by this name will not do any damage, for
   * This function does not check that the reference name is legal; for
   * that, use check_refname_format().
   *
-  * We consider a refname that starts with "refs/" to be safe as long
-  * as any ".." components that it might contain do not escape "refs/".
-  * Names that do not start with "refs/" are considered safe iff they
-  * consist entirely of upper case characters and '_' (like "HEAD" and
-  * "MERGE_HEAD" but not "config" or "FOO/BAR").
+  * A refname that starts with "refs/" is considered safe iff it
+  * doesn't contain any "." or ".." components or consecutive '/'
+  * characters, end with '/', or (on Windows) contain any '\'
+  * characters. Names that do not start with "refs/" are considered
+  * safe iff they consist entirely of upper case characters and '_'
+  * (like "HEAD" and "MERGE_HEAD" but not "config" or "FOO/BAR").
   */
  int refname_is_safe(const char *refname);
  
@@@ -133,6 -140,8 +140,6 @@@ int verify_refname_available(const cha
   */
  int copy_reflog_msg(char *buf, const char *msg);
  
 -int should_autocreate_reflog(const char *refname);
 -
  /**
   * Information needed for a single ref update. Set new_sha1 to the new
   * value or to null_sha1 to delete the ref. To check the old value
@@@ -155,8 -164,9 +162,9 @@@ struct ref_update 
  
        /*
         * One or more of REF_HAVE_NEW, REF_HAVE_OLD, REF_NODEREF,
-        * REF_DELETING, REF_ISPRUNING, REF_LOG_ONLY, and
-        * REF_UPDATE_VIA_HEAD:
+        * REF_DELETING, REF_ISPRUNING, REF_LOG_ONLY,
+        * REF_UPDATE_VIA_HEAD, REF_NEEDS_COMMIT, and
+        * REF_DELETED_LOOSE:
         */
        unsigned int flags;
  
diff --combined sha1_file.c
index ec957db5e1c2620b4a95e4f4d028f01794cef02a,b08f54c44d16ff31dce2bcd436877bf4fe88a67c..6628f06da3ccefde8c8be70b17dd63dc17077d37
  #include "mergesort.h"
  #include "quote.h"
  
 -#ifndef O_NOATIME
 -#if defined(__linux__) && (defined(__i386__) || defined(__PPC__))
 -#define O_NOATIME 01000000
 -#else
 -#define O_NOATIME 0
 -#endif
 -#endif
 -
  #define SZ_FMT PRIuMAX
  static inline uintmax_t sz_fmt(size_t s) { return s; }
  
@@@ -129,8 -137,10 +129,10 @@@ enum scld_error safe_create_leading_dir
                *slash = '\0';
                if (!stat(path, &st)) {
                        /* path exists */
-                       if (!S_ISDIR(st.st_mode))
+                       if (!S_ISDIR(st.st_mode)) {
+                               errno = ENOTDIR;
                                ret = SCLD_EXISTS;
+                       }
                } else if (mkdir(path, 0777)) {
                        if (errno == EEXIST &&
                            !stat(path, &st) && S_ISDIR(st.st_mode))
  
  enum scld_error safe_create_leading_directories_const(const char *path)
  {
+       int save_errno;
        /* path points to cache entries, so xstrdup before messing with it */
        char *buf = xstrdup(path);
        enum scld_error result = safe_create_leading_directories(buf);
+       save_errno = errno;
        free(buf);
+       errno = save_errno;
        return result;
  }
  
+ int raceproof_create_file(const char *path, create_file_fn fn, void *cb)
+ {
+       /*
+        * The number of times we will try to remove empty directories
+        * in the way of path. This is only 1 because if another
+        * process is racily creating directories that conflict with
+        * us, we don't want to fight against them.
+        */
+       int remove_directories_remaining = 1;
+       /*
+        * The number of times that we will try to create the
+        * directories containing path. We are willing to attempt this
+        * more than once, because another process could be trying to
+        * clean up empty directories at the same time as we are
+        * trying to create them.
+        */
+       int create_directories_remaining = 3;
+       /* A scratch copy of path, filled lazily if we need it: */
+       struct strbuf path_copy = STRBUF_INIT;
+       int ret, save_errno;
+       /* Sanity check: */
+       assert(*path);
+ retry_fn:
+       ret = fn(path, cb);
+       save_errno = errno;
+       if (!ret)
+               goto out;
+       if (errno == EISDIR && remove_directories_remaining-- > 0) {
+               /*
+                * A directory is in the way. Maybe it is empty; try
+                * to remove it:
+                */
+               if (!path_copy.len)
+                       strbuf_addstr(&path_copy, path);
+               if (!remove_dir_recursively(&path_copy, REMOVE_DIR_EMPTY_ONLY))
+                       goto retry_fn;
+       } else if (errno == ENOENT && create_directories_remaining-- > 0) {
+               /*
+                * Maybe the containing directory didn't exist, or
+                * maybe it was just deleted by a process that is
+                * racing with us to clean up empty directories. Try
+                * to create it:
+                */
+               enum scld_error scld_result;
+               if (!path_copy.len)
+                       strbuf_addstr(&path_copy, path);
+               do {
+                       scld_result = safe_create_leading_directories(path_copy.buf);
+                       if (scld_result == SCLD_OK)
+                               goto retry_fn;
+               } while (scld_result == SCLD_VANISHED && create_directories_remaining-- > 0);
+       }
+ out:
+       strbuf_release(&path_copy);
+       errno = save_errno;
+       return ret;
+ }
  static void fill_sha1_path(struct strbuf *buf, const unsigned char *sha1)
  {
        int i;
@@@ -284,7 -366,7 +358,7 @@@ static int link_alt_odb_entry(const cha
        struct strbuf pathbuf = STRBUF_INIT;
  
        if (!is_absolute_path(entry) && relative_base) {
 -              strbuf_addstr(&pathbuf, real_path(relative_base));
 +              strbuf_realpath(&pathbuf, relative_base, 1);
                strbuf_addch(&pathbuf, '/');
        }
        strbuf_addstr(&pathbuf, entry);
@@@ -1603,81 -1685,66 +1677,81 @@@ int check_sha1_signature(const unsigne
        return hashcmp(sha1, real_sha1) ? -1 : 0;
  }
  
 -int git_open(const char *name)
 +int git_open_cloexec(const char *name, int flags)
  {
 -      static int sha1_file_open_flag = O_NOATIME | O_CLOEXEC;
 -
 -      for (;;) {
 -              int fd;
 -
 -              errno = 0;
 -              fd = open(name, O_RDONLY | sha1_file_open_flag);
 -              if (fd >= 0)
 -                      return fd;
 +      int fd;
 +      static int o_cloexec = O_CLOEXEC;
  
 +      fd = open(name, flags | o_cloexec);
 +      if ((o_cloexec & O_CLOEXEC) && fd < 0 && errno == EINVAL) {
                /* Try again w/o O_CLOEXEC: the kernel might not support it */
 -              if ((sha1_file_open_flag & O_CLOEXEC) && errno == EINVAL) {
 -                      sha1_file_open_flag &= ~O_CLOEXEC;
 -                      continue;
 -              }
 +              o_cloexec &= ~O_CLOEXEC;
 +              fd = open(name, flags | o_cloexec);
 +      }
  
 -              /* Might the failure be due to O_NOATIME? */
 -              if (errno != ENOENT && (sha1_file_open_flag & O_NOATIME)) {
 -                      sha1_file_open_flag &= ~O_NOATIME;
 -                      continue;
 +#if defined(F_GETFL) && defined(F_SETFL) && defined(FD_CLOEXEC)
 +      {
 +              static int fd_cloexec = FD_CLOEXEC;
 +
 +              if (!o_cloexec && 0 <= fd && fd_cloexec) {
 +                      /* Opened w/o O_CLOEXEC?  try with fcntl(2) to add it */
 +                      int flags = fcntl(fd, F_GETFL);
 +                      if (fcntl(fd, F_SETFL, flags | fd_cloexec))
 +                              fd_cloexec = 0;
                }
 -              return -1;
        }
 +#endif
 +      return fd;
  }
  
 -static int stat_sha1_file(const unsigned char *sha1, struct stat *st)
 +/*
 + * Find "sha1" as a loose object in the local repository or in an alternate.
 + * Returns 0 on success, negative on failure.
 + *
 + * The "path" out-parameter will give the path of the object we found (if any).
 + * Note that it may point to static storage and is only valid until another
 + * call to sha1_file_name(), etc.
 + */
 +static int stat_sha1_file(const unsigned char *sha1, struct stat *st,
 +                        const char **path)
  {
        struct alternate_object_database *alt;
  
 -      if (!lstat(sha1_file_name(sha1), st))
 +      *path = sha1_file_name(sha1);
 +      if (!lstat(*path, st))
                return 0;
  
        prepare_alt_odb();
        errno = ENOENT;
        for (alt = alt_odb_list; alt; alt = alt->next) {
 -              const char *path = alt_sha1_path(alt, sha1);
 -              if (!lstat(path, st))
 +              *path = alt_sha1_path(alt, sha1);
 +              if (!lstat(*path, st))
                        return 0;
        }
  
        return -1;
  }
  
 -static int open_sha1_file(const unsigned char *sha1)
 +/*
 + * Like stat_sha1_file(), but actually open the object and return the
 + * descriptor. See the caveats on the "path" parameter above.
 + */
 +static int open_sha1_file(const unsigned char *sha1, const char **path)
  {
        int fd;
        struct alternate_object_database *alt;
        int most_interesting_errno;
  
 -      fd = git_open(sha1_file_name(sha1));
 +      *path = sha1_file_name(sha1);
 +      fd = git_open(*path);
        if (fd >= 0)
                return fd;
        most_interesting_errno = errno;
  
        prepare_alt_odb();
        for (alt = alt_odb_list; alt; alt = alt->next) {
 -              const char *path = alt_sha1_path(alt, sha1);
 -              fd = git_open(path);
 +              *path = alt_sha1_path(alt, sha1);
 +              fd = git_open(*path);
                if (fd >= 0)
                        return fd;
                if (most_interesting_errno == ENOENT)
        return -1;
  }
  
 -void *map_sha1_file(const unsigned char *sha1, unsigned long *size)
 +/*
 + * Map the loose object at "path" if it is not NULL, or the path found by
 + * searching for a loose object named "sha1".
 + */
 +static void *map_sha1_file_1(const char *path,
 +                           const unsigned char *sha1,
 +                           unsigned long *size)
  {
        void *map;
        int fd;
  
 -      fd = open_sha1_file(sha1);
 +      if (path)
 +              fd = git_open(path);
 +      else
 +              fd = open_sha1_file(sha1, &path);
        map = NULL;
        if (fd >= 0) {
                struct stat st;
                        *size = xsize_t(st.st_size);
                        if (!*size) {
                                /* mmap() is forbidden on empty files */
 -                              error("object file %s is empty", sha1_file_name(sha1));
 +                              error("object file %s is empty", path);
                                return NULL;
                        }
                        map = xmmap(NULL, *size, PROT_READ, MAP_PRIVATE, fd, 0);
        return map;
  }
  
 +void *map_sha1_file(const unsigned char *sha1, unsigned long *size)
 +{
 +      return map_sha1_file_1(NULL, sha1, size);
 +}
 +
  unsigned long unpack_object_header_buffer(const unsigned char *buf,
                unsigned long len, enum object_type *type, unsigned long *sizep)
  {
@@@ -2371,10 -2424,11 +2445,10 @@@ static inline void release_delta_base_c
  
  void clear_delta_base_cache(void)
  {
 -      struct hashmap_iter iter;
 -      struct delta_base_cache_entry *entry;
 -      for (entry = hashmap_iter_first(&delta_base_cache, &iter);
 -           entry;
 -           entry = hashmap_iter_next(&iter)) {
 +      struct list_head *lru, *tmp;
 +      list_for_each_safe(lru, tmp, &delta_base_cache_lru) {
 +              struct delta_base_cache_entry *entry =
 +                      list_entry(lru, struct delta_base_cache_entry, lru);
                release_delta_base_cache(entry);
        }
  }
@@@ -2834,9 -2888,8 +2908,9 @@@ static int sha1_loose_object_info(cons
         * object even exists.
         */
        if (!oi->typep && !oi->typename && !oi->sizep) {
 +              const char *path;
                struct stat st;
 -              if (stat_sha1_file(sha1, &st) < 0)
 +              if (stat_sha1_file(sha1, &st, &path) < 0)
                        return -1;
                if (oi->disk_sizep)
                        *oi->disk_sizep = st.st_size;
@@@ -3032,8 -3085,6 +3106,8 @@@ void *read_sha1_file_extended(const uns
  {
        void *data;
        const struct packed_git *p;
 +      const char *path;
 +      struct stat st;
        const unsigned char *repl = lookup_replace_object_extended(sha1, flag);
  
        errno = 0;
                die("replacement %s not found for %s",
                    sha1_to_hex(repl), sha1_to_hex(sha1));
  
 -      if (has_loose_object(repl)) {
 -              const char *path = sha1_file_name(sha1);
 -
 +      if (!stat_sha1_file(repl, &st, &path))
                die("loose object %s (stored in %s) is corrupt",
                    sha1_to_hex(repl), path);
 -      }
  
        if ((p = has_packed_and_bad(repl)) != NULL)
                die("packed object %s (stored in %s) is corrupt",
@@@ -3821,122 -3875,3 +3895,122 @@@ int for_each_packed_object(each_packed_
        }
        return r ? r : pack_errors;
  }
 +
 +static int check_stream_sha1(git_zstream *stream,
 +                           const char *hdr,
 +                           unsigned long size,
 +                           const char *path,
 +                           const unsigned char *expected_sha1)
 +{
 +      git_SHA_CTX c;
 +      unsigned char real_sha1[GIT_SHA1_RAWSZ];
 +      unsigned char buf[4096];
 +      unsigned long total_read;
 +      int status = Z_OK;
 +
 +      git_SHA1_Init(&c);
 +      git_SHA1_Update(&c, hdr, stream->total_out);
 +
 +      /*
 +       * We already read some bytes into hdr, but the ones up to the NUL
 +       * do not count against the object's content size.
 +       */
 +      total_read = stream->total_out - strlen(hdr) - 1;
 +
 +      /*
 +       * This size comparison must be "<=" to read the final zlib packets;
 +       * see the comment in unpack_sha1_rest for details.
 +       */
 +      while (total_read <= size &&
 +             (status == Z_OK || status == Z_BUF_ERROR)) {
 +              stream->next_out = buf;
 +              stream->avail_out = sizeof(buf);
 +              if (size - total_read < stream->avail_out)
 +                      stream->avail_out = size - total_read;
 +              status = git_inflate(stream, Z_FINISH);
 +              git_SHA1_Update(&c, buf, stream->next_out - buf);
 +              total_read += stream->next_out - buf;
 +      }
 +      git_inflate_end(stream);
 +
 +      if (status != Z_STREAM_END) {
 +              error("corrupt loose object '%s'", sha1_to_hex(expected_sha1));
 +              return -1;
 +      }
 +      if (stream->avail_in) {
 +              error("garbage at end of loose object '%s'",
 +                    sha1_to_hex(expected_sha1));
 +              return -1;
 +      }
 +
 +      git_SHA1_Final(real_sha1, &c);
 +      if (hashcmp(expected_sha1, real_sha1)) {
 +              error("sha1 mismatch for %s (expected %s)", path,
 +                    sha1_to_hex(expected_sha1));
 +              return -1;
 +      }
 +
 +      return 0;
 +}
 +
 +int read_loose_object(const char *path,
 +                    const unsigned char *expected_sha1,
 +                    enum object_type *type,
 +                    unsigned long *size,
 +                    void **contents)
 +{
 +      int ret = -1;
 +      int fd = -1;
 +      void *map = NULL;
 +      unsigned long mapsize;
 +      git_zstream stream;
 +      char hdr[32];
 +
 +      *contents = NULL;
 +
 +      map = map_sha1_file_1(path, NULL, &mapsize);
 +      if (!map) {
 +              error_errno("unable to mmap %s", path);
 +              goto out;
 +      }
 +
 +      if (unpack_sha1_header(&stream, map, mapsize, hdr, sizeof(hdr)) < 0) {
 +              error("unable to unpack header of %s", path);
 +              goto out;
 +      }
 +
 +      *type = parse_sha1_header(hdr, size);
 +      if (*type < 0) {
 +              error("unable to parse header of %s", path);
 +              git_inflate_end(&stream);
 +              goto out;
 +      }
 +
 +      if (*type == OBJ_BLOB) {
 +              if (check_stream_sha1(&stream, hdr, *size, path, expected_sha1) < 0)
 +                      goto out;
 +      } else {
 +              *contents = unpack_sha1_rest(&stream, hdr, *size, expected_sha1);
 +              if (!*contents) {
 +                      error("unable to unpack contents of %s", path);
 +                      git_inflate_end(&stream);
 +                      goto out;
 +              }
 +              if (check_sha1_signature(expected_sha1, *contents,
 +                                       *size, typename(*type))) {
 +                      error("sha1 mismatch for %s (expected %s)", path,
 +                            sha1_to_hex(expected_sha1));
 +                      free(*contents);
 +                      goto out;
 +              }
 +      }
 +
 +      ret = 0; /* everything checks out */
 +
 +out:
 +      if (map)
 +              munmap(map, mapsize);
 +      if (fd >= 0)
 +              close(fd);
 +      return ret;
 +}
diff --combined t/t1400-update-ref.sh
index b0ffc0b5731dd746ed170604784287d3e48e9c30,97d87936ff77d08cf4b3d4ef01d21b7fc73c090f..e208009906f13fa48a404ea9e84cf5b037ce0837
@@@ -8,33 -8,23 +8,33 @@@ test_description='Test git update-ref a
  
  Z=$_z40
  
 -test_expect_success setup '
 +m=refs/heads/master
 +n_dir=refs/heads/gu
 +n=$n_dir/fixes
 +outside=refs/foo
 +bare=bare-repo
  
 +create_test_commits ()
 +{
 +      prfx="$1"
        for name in A B C D E F
        do
                test_tick &&
                T=$(git write-tree) &&
                sha1=$(echo $name | git commit-tree $T) &&
 -              eval $name=$sha1
 +              eval $prfx$name=$sha1
        done
 +}
  
 +test_expect_success setup '
 +      create_test_commits "" &&
 +      mkdir $bare &&
 +      cd $bare &&
 +      git init --bare &&
 +      create_test_commits "bare" &&
 +      cd -
  '
  
 -m=refs/heads/master
 -n_dir=refs/heads/gu
 -n=$n_dir/fixes
 -outside=refs/foo
 -
  test_expect_success \
        "create $m" \
        "git update-ref $m $A &&
@@@ -103,61 -93,6 +103,61 @@@ test_expect_success 'update-ref create
        git reflog exists $outside
  '
  
 +test_expect_success 'creates no reflog in bare repository' '
 +      git -C $bare update-ref $m $bareA &&
 +      git -C $bare rev-parse $bareA >expect &&
 +      git -C $bare rev-parse $m >actual &&
 +      test_cmp expect actual &&
 +      test_must_fail git -C $bare reflog exists $m
 +'
 +
 +test_expect_success 'core.logAllRefUpdates=true creates reflog in bare repository' '
 +      test_when_finished "git -C $bare config --unset core.logAllRefUpdates && \
 +              rm $bare/logs/$m" &&
 +      git -C $bare config core.logAllRefUpdates true &&
 +      git -C $bare update-ref $m $bareB &&
 +      git -C $bare rev-parse $bareB >expect &&
 +      git -C $bare rev-parse $m >actual &&
 +      test_cmp expect actual &&
 +      git -C $bare reflog exists $m
 +'
 +
 +test_expect_success 'core.logAllRefUpdates=true does not create reflog by default' '
 +      test_config core.logAllRefUpdates true &&
 +      test_when_finished "git update-ref -d $outside" &&
 +      git update-ref $outside $A &&
 +      git rev-parse $A >expect &&
 +      git rev-parse $outside >actual &&
 +      test_cmp expect actual &&
 +      test_must_fail git reflog exists $outside
 +'
 +
 +test_expect_success 'core.logAllRefUpdates=always creates reflog by default' '
 +      test_config core.logAllRefUpdates always &&
 +      test_when_finished "git update-ref -d $outside" &&
 +      git update-ref $outside $A &&
 +      git rev-parse $A >expect &&
 +      git rev-parse $outside >actual &&
 +      test_cmp expect actual &&
 +      git reflog exists $outside
 +'
 +
 +test_expect_success 'core.logAllRefUpdates=always creates no reflog for ORIG_HEAD' '
 +      test_config core.logAllRefUpdates always &&
 +      git update-ref ORIG_HEAD $A &&
 +      test_must_fail git reflog exists ORIG_HEAD
 +'
 +
 +test_expect_success '--no-create-reflog overrides core.logAllRefUpdates=always' '
 +      test_config core.logAllRefUpdates true &&
 +      test_when_finished "git update-ref -d $outside" &&
 +      git update-ref --no-create-reflog $outside $A &&
 +      git rev-parse $A >expect &&
 +      git rev-parse $outside >actual &&
 +      test_cmp expect actual &&
 +      test_must_fail git reflog exists $outside
 +'
 +
  test_expect_success \
        "create $m (by HEAD)" \
        "git update-ref HEAD $A &&
@@@ -256,6 -191,33 +256,33 @@@ test_expect_success 
         git update-ref HEAD'" $A &&
         test $A"' = $(cat .git/'"$m"')'
  
+ test_expect_success "empty directory removal" '
+       git branch d1/d2/r1 HEAD &&
+       git branch d1/r2 HEAD &&
+       test -f .git/refs/heads/d1/d2/r1 &&
+       test -f .git/logs/refs/heads/d1/d2/r1 &&
+       git branch -d d1/d2/r1 &&
+       ! test -e .git/refs/heads/d1/d2 &&
+       ! test -e .git/logs/refs/heads/d1/d2 &&
+       test -f .git/refs/heads/d1/r2 &&
+       test -f .git/logs/refs/heads/d1/r2
+ '
+ test_expect_success "symref empty directory removal" '
+       git branch e1/e2/r1 HEAD &&
+       git branch e1/r2 HEAD &&
+       git checkout e1/e2/r1 &&
+       test_when_finished "git checkout master" &&
+       test -f .git/refs/heads/e1/e2/r1 &&
+       test -f .git/logs/refs/heads/e1/e2/r1 &&
+       git update-ref -d HEAD &&
+       ! test -e .git/refs/heads/e1/e2 &&
+       ! test -e .git/logs/refs/heads/e1/e2 &&
+       test -f .git/refs/heads/e1/r2 &&
+       test -f .git/logs/refs/heads/e1/r2 &&
+       test -f .git/logs/HEAD
+ '
  cat >expect <<EOF
  $Z $A $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150200 +0000     Initial Creation
  $A $B $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150260 +0000     Switch
@@@ -566,7 -528,6 +593,7 @@@ test_expect_success 'stdin does not cre
  '
  
  test_expect_success 'stdin creates reflogs with --create-reflog' '
 +      test_when_finished "git update-ref -d $outside" &&
        echo "create $outside $m" >stdin &&
        git update-ref --create-reflog --stdin <stdin &&
        git rev-parse $m >expect &&
diff --combined t/t5505-remote.sh
index ba46e86de0a7285bf88ff5fe05e378c8bfd256f9,65030fbc8ac5a8753b4a077cd06de4d14101bb3a..535d53fa633e56b2d54bdd648c7627eb5c89a2ed
@@@ -725,7 -725,7 +725,7 @@@ test_expect_success 'rename a remote' 
        (
                cd four &&
                git remote rename origin upstream &&
-               rmdir .git/refs/remotes/origin &&
+               test -z "$(git for-each-ref refs/remotes/origin)" &&
                test "$(git symbolic-ref refs/remotes/upstream/HEAD)" = "refs/remotes/upstream/master" &&
                test "$(git rev-parse upstream/master)" = "$(git rev-parse master)" &&
                test "$(git config remote.upstream.fetch)" = "+refs/heads/*:refs/remotes/upstream/*" &&
@@@ -764,13 -764,6 +764,13 @@@ test_expect_success 'rename a remote wi
        )
  '
  
 +test_expect_success 'rename succeeds with existing remote.<target>.prune' '
 +      git clone one four.four &&
 +      test_when_finished git config --global --unset remote.upstream.prune &&
 +      git config --global remote.upstream.prune true &&
 +      git -C four.four remote rename origin upstream
 +'
 +
  cat >remotes_origin <<EOF
  URL: $(pwd)/one
  Push: refs/heads/master:refs/heads/upstream