Merge branch 'jk/write-packed-refs-via-stdio'
authorJunio C Hamano <gitster@pobox.com>
Fri, 26 Sep 2014 21:39:42 +0000 (14:39 -0700)
committerJunio C Hamano <gitster@pobox.com>
Fri, 26 Sep 2014 21:39:42 +0000 (14:39 -0700)
Optimize the code path to write out the packed-refs file, which
especially matters in a repository with a large number of refs.

* jk/write-packed-refs-via-stdio:
refs: write packed_refs file using stdio

1  2 
cache.h
refs.c
diff --combined cache.h
index af590d5077d534503263dfe0ee80a69374c27053,bc286ce22db4c921caeadb8c33e81102ee7d30a2..a9eb7902c97dc33cbeefcb7d5c1c3a98d6cd2a65
+++ b/cache.h
@@@ -8,7 -8,6 +8,7 @@@
  #include "gettext.h"
  #include "convert.h"
  #include "trace.h"
 +#include "string-list.h"
  
  #include SHA1_HEADER
  #ifndef git_SHA_CTX
@@@ -1039,7 -1038,6 +1039,7 @@@ enum date_mode 
        DATE_SHORT,
        DATE_LOCAL,
        DATE_ISO8601,
 +      DATE_ISO8601_STRICT,
        DATE_RFC2822,
        DATE_RAW
  };
  const char *show_date(unsigned long time, int timezone, enum date_mode mode);
  void show_date_relative(unsigned long time, int tz, const struct timeval *now,
                        struct strbuf *timebuf);
 -int parse_date(const char *date, char *buf, int bufsize);
 +int parse_date(const char *date, struct strbuf *out);
  int parse_date_basic(const char *date, unsigned long *timestamp, int *offset);
  int parse_expiry_date(const char *date, unsigned long *timestamp);
 -void datestamp(char *buf, int bufsize);
 +void datestamp(struct strbuf *out);
  #define approxidate(s) approxidate_careful((s), NULL)
  unsigned long approxidate_careful(const char *, int *);
  unsigned long approxidate_relative(const char *date, const struct timeval *now);
@@@ -1285,8 -1283,6 +1285,8 @@@ extern int update_server_info(int)
  #define CONFIG_INVALID_PATTERN 6
  #define CONFIG_GENERIC_ERROR 7
  
 +#define CONFIG_REGEX_NONE ((void *)1)
 +
  struct git_config_source {
        unsigned int use_stdin:1;
        const char *file;
@@@ -1300,7 -1296,7 +1300,7 @@@ extern int git_config_from_buf(config_f
                               const char *buf, size_t len, void *data);
  extern void git_config_push_parameter(const char *text);
  extern int git_config_from_parameters(config_fn_t fn, void *data);
 -extern int git_config(config_fn_t fn, void *);
 +extern void git_config(config_fn_t fn, void *);
  extern int git_config_with_options(config_fn_t fn, void *,
                                   struct git_config_source *config_source,
                                   int respect_includes);
@@@ -1357,32 -1353,9 +1357,32 @@@ extern int parse_config_key(const char 
                            const char **subsection, int *subsection_len,
                            const char **key);
  
 +struct config_set_element {
 +      struct hashmap_entry ent;
 +      char *key;
 +      struct string_list value_list;
 +};
 +
 +struct configset_list_item {
 +      struct config_set_element *e;
 +      int value_index;
 +};
 +
 +/*
 + * the contents of the list are ordered according to their
 + * position in the config files and order of parsing the files.
 + * (i.e. key-value pair at the last position of .git/config will
 + * be at the last item of the list)
 + */
 +struct configset_list {
 +      struct configset_list_item *items;
 +      unsigned int nr, alloc;
 +};
 +
  struct config_set {
        struct hashmap config_hash;
        int hash_initialized;
 +      struct configset_list list;
  };
  
  extern void git_configset_init(struct config_set *cs);
@@@ -1412,14 -1385,6 +1412,14 @@@ extern int git_config_get_bool_or_int(c
  extern int git_config_get_maybe_bool(const char *key, int *dest);
  extern int git_config_get_pathname(const char *key, const char **dest);
  
 +struct key_value_info {
 +      const char *filename;
 +      int linenr;
 +};
 +
 +extern NORETURN void git_die_config(const char *key, const char *err, ...) __attribute__((format(printf, 2, 3)));
 +extern NORETURN void git_die_config_linenr(const char *key, const char *filename, int linenr);
 +
  extern int committer_ident_sufficiently_given(void);
  extern int author_ident_sufficiently_given(void);
  
@@@ -1430,6 -1395,8 +1430,8 @@@ extern const char *git_mailmap_blob
  
  /* IO helper functions */
  extern void maybe_flush_or_die(FILE *, const char *);
+ __attribute__((format (printf, 2, 3)))
+ extern void fprintf_or_die(FILE *, const char *fmt, ...);
  extern int copy_fd(int ifd, int ofd);
  extern int copy_file(const char *dst, const char *src, int mode);
  extern int copy_file_with_time(const char *dst, const char *src, int mode);
diff --combined refs.c
index 2ce5d690907d33bda557f66150ef1317f39b4f93,f08faeda23657dca278e9f032c14ab03dcb537ac..6378c9829589d3aec886f953af1d7e4fdaee6681
--- 1/refs.c
--- 2/refs.c
+++ b/refs.c
@@@ -24,11 -24,6 +24,11 @@@ static unsigned char refname_dispositio
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 4, 4
  };
  
 +/*
 + * Used as a flag to ref_transaction_delete when a loose ref is being
 + * pruned.
 + */
 +#define REF_ISPRUNING 0x0100
  /*
   * Try to read one refname component from the front of refname.
   * Return the length of the component found, or -1 if the component is
@@@ -2073,10 -2068,7 +2073,10 @@@ int dwim_log(const char *str, int len, 
        return logs_found;
  }
  
 -/* This function should make sure errno is meaningful on error */
 +/*
 + * Locks a "refs/" ref returning the lock on success and NULL on failure.
 + * On failure errno is set to something meaningful.
 + */
  static struct ref_lock *lock_ref_sha1_basic(const char *refname,
                                            const unsigned char *old_sha1,
                                            int flags, int *type_p)
        return NULL;
  }
  
 -struct ref_lock *lock_ref_sha1(const char *refname, const unsigned char *old_sha1)
 -{
 -      char refpath[PATH_MAX];
 -      if (check_refname_format(refname, 0))
 -              return NULL;
 -      strcpy(refpath, mkpath("refs/%s", refname));
 -      return lock_ref_sha1_basic(refpath, old_sha1, 0, NULL);
 -}
 -
  struct ref_lock *lock_any_ref_for_update(const char *refname,
                                         const unsigned char *old_sha1,
                                         int flags, int *type_p)
   * Write an entry to the packed-refs file for the specified refname.
   * If peeled is non-NULL, write it as the entry's peeled value.
   */
- static void write_packed_entry(int fd, char *refname, unsigned char *sha1,
+ static void write_packed_entry(FILE *fh, char *refname, unsigned char *sha1,
                               unsigned char *peeled)
  {
-       char line[PATH_MAX + 100];
-       int len;
-       len = snprintf(line, sizeof(line), "%s %s\n",
-                      sha1_to_hex(sha1), refname);
-       /* this should not happen but just being defensive */
-       if (len > sizeof(line))
-               die("too long a refname '%s'", refname);
-       write_or_die(fd, line, len);
-       if (peeled) {
-               if (snprintf(line, sizeof(line), "^%s\n",
-                            sha1_to_hex(peeled)) != PEELED_LINE_LENGTH)
-                       die("internal error");
-               write_or_die(fd, line, PEELED_LINE_LENGTH);
-       }
+       fprintf_or_die(fh, "%s %s\n", sha1_to_hex(sha1), refname);
+       if (peeled)
+               fprintf_or_die(fh, "^%s\n", sha1_to_hex(peeled));
  }
  
  /*
   */
  static int write_packed_entry_fn(struct ref_entry *entry, void *cb_data)
  {
-       int *fd = cb_data;
        enum peel_status peel_status = peel_entry(entry, 0);
  
        if (peel_status != PEEL_PEELED && peel_status != PEEL_NON_TAG)
                error("internal error: %s is not a valid packed reference!",
                      entry->name);
-       write_packed_entry(*fd, entry->name, entry->u.value.sha1,
+       write_packed_entry(cb_data, entry->name, entry->u.value.sha1,
                           peel_status == PEEL_PEELED ?
                           entry->u.value.peeled : NULL);
        return 0;
@@@ -2258,15 -2245,22 +2244,22 @@@ int commit_packed_refs(void
                get_packed_ref_cache(&ref_cache);
        int error = 0;
        int save_errno = 0;
+       FILE *out;
  
        if (!packed_ref_cache->lock)
                die("internal error: packed-refs not locked");
-       write_or_die(packed_ref_cache->lock->fd,
-                    PACKED_REFS_HEADER, strlen(PACKED_REFS_HEADER));
  
+       out = fdopen(packed_ref_cache->lock->fd, "w");
+       if (!out)
+               die_errno("unable to fdopen packed-refs descriptor");
+       fprintf_or_die(out, "%s", PACKED_REFS_HEADER);
        do_for_each_entry_in_dir(get_packed_ref_dir(packed_ref_cache),
-                                0, write_packed_entry_fn,
-                                &packed_ref_cache->lock->fd);
+                                0, write_packed_entry_fn, out);
+       if (fclose(out))
+               die_errno("write error");
+       packed_ref_cache->lock->fd = -1;
        if (commit_lock_file(packed_ref_cache->lock)) {
                save_errno = errno;
                error = -1;
@@@ -2386,25 -2380,13 +2379,25 @@@ static void try_remove_empty_parents(ch
  /* make sure nobody touched the ref, and unlink */
  static void prune_ref(struct ref_to_prune *r)
  {
 -      struct ref_lock *lock = lock_ref_sha1(r->name + 5, r->sha1);
 +      struct ref_transaction *transaction;
 +      struct strbuf err = STRBUF_INIT;
  
 -      if (lock) {
 -              unlink_or_warn(git_path("%s", r->name));
 -              unlock_ref(lock);
 -              try_remove_empty_parents(r->name);
 +      if (check_refname_format(r->name, 0))
 +              return;
 +
 +      transaction = ref_transaction_begin(&err);
 +      if (!transaction ||
 +          ref_transaction_delete(transaction, r->name, r->sha1,
 +                                 REF_ISPRUNING, 1, &err) ||
 +          ref_transaction_commit(transaction, NULL, &err)) {
 +              ref_transaction_free(transaction);
 +              error("%s", err.buf);
 +              strbuf_release(&err);
 +              return;
        }
 +      ref_transaction_free(transaction);
 +      strbuf_release(&err);
 +      try_remove_empty_parents(r->name);
  }
  
  static void prune_refs(struct ref_to_prune *r)
@@@ -2547,6 -2529,11 +2540,6 @@@ int repack_without_refs(const char **re
        return ret;
  }
  
 -static int repack_without_ref(const char *refname)
 -{
 -      return repack_without_refs(&refname, 1, NULL);
 -}
 -
  static int delete_ref_loose(struct ref_lock *lock, int flag)
  {
        if (!(flag & REF_ISPACKED) || flag & REF_ISSYMREF) {
  
  int delete_ref(const char *refname, const unsigned char *sha1, int delopt)
  {
 -      struct ref_lock *lock;
 -      int ret = 0, flag = 0;
 -
 -      lock = lock_ref_sha1_basic(refname, sha1, delopt, &flag);
 -      if (!lock)
 +      struct ref_transaction *transaction;
 +      struct strbuf err = STRBUF_INIT;
 +
 +      transaction = ref_transaction_begin(&err);
 +      if (!transaction ||
 +          ref_transaction_delete(transaction, refname, sha1, delopt,
 +                                 sha1 && !is_null_sha1(sha1), &err) ||
 +          ref_transaction_commit(transaction, NULL, &err)) {
 +              error("%s", err.buf);
 +              ref_transaction_free(transaction);
 +              strbuf_release(&err);
                return 1;
 -      ret |= delete_ref_loose(lock, flag);
 -
 -      /* removing the loose one could have resurrected an earlier
 -       * packed one.  Also, if it was not loose we need to repack
 -       * without it.
 -       */
 -      ret |= repack_without_ref(lock->ref_name);
 -
 -      unlink_or_warn(git_path("logs/%s", lock->ref_name));
 -      clear_loose_ref_cache(&ref_cache);
 -      unlock_ref(lock);
 -      return ret;
 +      }
 +      ref_transaction_free(transaction);
 +      strbuf_release(&err);
 +      return 0;
  }
  
  /*
@@@ -3336,6 -3325,43 +3329,6 @@@ int for_each_reflog(each_ref_fn fn, voi
        return retval;
  }
  
 -static struct ref_lock *update_ref_lock(const char *refname,
 -                                      const unsigned char *oldval,
 -                                      int flags, int *type_p,
 -                                      enum action_on_err onerr)
 -{
 -      struct ref_lock *lock;
 -      lock = lock_any_ref_for_update(refname, oldval, flags, type_p);
 -      if (!lock) {
 -              const char *str = "Cannot lock the ref '%s'.";
 -              switch (onerr) {
 -              case UPDATE_REFS_MSG_ON_ERR: error(str, refname); break;
 -              case UPDATE_REFS_DIE_ON_ERR: die(str, refname); break;
 -              case UPDATE_REFS_QUIET_ON_ERR: break;
 -              }
 -      }
 -      return lock;
 -}
 -
 -static int update_ref_write(const char *action, const char *refname,
 -                          const unsigned char *sha1, struct ref_lock *lock,
 -                          struct strbuf *err, enum action_on_err onerr)
 -{
 -      if (write_ref_sha1(lock, sha1, action) < 0) {
 -              const char *str = "Cannot update the ref '%s'.";
 -              if (err)
 -                      strbuf_addf(err, str, refname);
 -
 -              switch (onerr) {
 -              case UPDATE_REFS_MSG_ON_ERR: error(str, refname); break;
 -              case UPDATE_REFS_DIE_ON_ERR: die(str, refname); break;
 -              case UPDATE_REFS_QUIET_ON_ERR: break;
 -              }
 -              return 1;
 -      }
 -      return 0;
 -}
 -
  /**
   * Information needed for a single ref update.  Set new_sha1 to the
   * new value or to zero to delete the ref.  To check the old value
@@@ -3352,21 -3378,6 +3345,21 @@@ struct ref_update 
        const char refname[FLEX_ARRAY];
  };
  
 +/*
 + * Transaction states.
 + * OPEN:   The transaction is in a valid state and can accept new updates.
 + *         An OPEN transaction can be committed.
 + * CLOSED: A closed transaction is no longer active and no other operations
 + *         than free can be used on it in this state.
 + *         A transaction can either become closed by successfully committing
 + *         an active transaction or if there is a failure while building
 + *         the transaction thus rendering it failed/inactive.
 + */
 +enum ref_transaction_state {
 +      REF_TRANSACTION_OPEN   = 0,
 +      REF_TRANSACTION_CLOSED = 1
 +};
 +
  /*
   * Data structure for holding a reference transaction, which can
   * consist of checks and updates to multiple references, carried out
@@@ -3376,10 -3387,9 +3369,10 @@@ struct ref_transaction 
        struct ref_update **updates;
        size_t alloc;
        size_t nr;
 +      enum ref_transaction_state state;
  };
  
 -struct ref_transaction *ref_transaction_begin(void)
 +struct ref_transaction *ref_transaction_begin(struct strbuf *err)
  {
        return xcalloc(1, sizeof(struct ref_transaction));
  }
@@@ -3419,9 -3429,6 +3412,9 @@@ int ref_transaction_update(struct ref_t
  {
        struct ref_update *update;
  
 +      if (transaction->state != REF_TRANSACTION_OPEN)
 +              die("BUG: update called for transaction that is not open");
 +
        if (have_old && !old_sha1)
                die("BUG: have_old is true but old_sha1 is NULL");
  
        return 0;
  }
  
 -void ref_transaction_create(struct ref_transaction *transaction,
 -                          const char *refname,
 -                          const unsigned char *new_sha1,
 -                          int flags)
 +int ref_transaction_create(struct ref_transaction *transaction,
 +                         const char *refname,
 +                         const unsigned char *new_sha1,
 +                         int flags,
 +                         struct strbuf *err)
  {
 -      struct ref_update *update = add_update(transaction, refname);
 +      struct ref_update *update;
 +
 +      if (transaction->state != REF_TRANSACTION_OPEN)
 +              die("BUG: create called for transaction that is not open");
 +
 +      if (!new_sha1 || is_null_sha1(new_sha1))
 +              die("BUG: create ref with null new_sha1");
 +
 +      update = add_update(transaction, refname);
  
 -      assert(!is_null_sha1(new_sha1));
        hashcpy(update->new_sha1, new_sha1);
        hashclr(update->old_sha1);
        update->flags = flags;
        update->have_old = 1;
 +      return 0;
  }
  
 -void ref_transaction_delete(struct ref_transaction *transaction,
 -                          const char *refname,
 -                          const unsigned char *old_sha1,
 -                          int flags, int have_old)
 +int ref_transaction_delete(struct ref_transaction *transaction,
 +                         const char *refname,
 +                         const unsigned char *old_sha1,
 +                         int flags, int have_old,
 +                         struct strbuf *err)
  {
 -      struct ref_update *update = add_update(transaction, refname);
 +      struct ref_update *update;
  
 +      if (transaction->state != REF_TRANSACTION_OPEN)
 +              die("BUG: delete called for transaction that is not open");
 +
 +      if (have_old && !old_sha1)
 +              die("BUG: have_old is true but old_sha1 is NULL");
 +
 +      update = add_update(transaction, refname);
        update->flags = flags;
        update->have_old = have_old;
        if (have_old) {
                assert(!is_null_sha1(old_sha1));
                hashcpy(update->old_sha1, old_sha1);
        }
 +      return 0;
  }
  
  int update_ref(const char *action, const char *refname,
               const unsigned char *sha1, const unsigned char *oldval,
               int flags, enum action_on_err onerr)
  {
 -      struct ref_lock *lock;
 -      lock = update_ref_lock(refname, oldval, flags, NULL, onerr);
 -      if (!lock)
 +      struct ref_transaction *t;
 +      struct strbuf err = STRBUF_INIT;
 +
 +      t = ref_transaction_begin(&err);
 +      if (!t ||
 +          ref_transaction_update(t, refname, sha1, oldval, flags,
 +                                 !!oldval, &err) ||
 +          ref_transaction_commit(t, action, &err)) {
 +              const char *str = "update_ref failed for ref '%s': %s";
 +
 +              ref_transaction_free(t);
 +              switch (onerr) {
 +              case UPDATE_REFS_MSG_ON_ERR:
 +                      error(str, refname, err.buf);
 +                      break;
 +              case UPDATE_REFS_DIE_ON_ERR:
 +                      die(str, refname, err.buf);
 +                      break;
 +              case UPDATE_REFS_QUIET_ON_ERR:
 +                      break;
 +              }
 +              strbuf_release(&err);
                return 1;
 -      return update_ref_write(action, refname, sha1, lock, NULL, onerr);
 +      }
 +      strbuf_release(&err);
 +      ref_transaction_free(t);
 +      return 0;
  }
  
  static int ref_update_compare(const void *r1, const void *r2)
@@@ -3545,13 -3512,8 +3538,13 @@@ int ref_transaction_commit(struct ref_t
        int n = transaction->nr;
        struct ref_update **updates = transaction->updates;
  
 -      if (!n)
 +      if (transaction->state != REF_TRANSACTION_OPEN)
 +              die("BUG: commit called for transaction that is not open");
 +
 +      if (!n) {
 +              transaction->state = REF_TRANSACTION_CLOSED;
                return 0;
 +      }
  
        /* Allocate work space */
        delnames = xmalloc(sizeof(*delnames) * n);
        for (i = 0; i < n; i++) {
                struct ref_update *update = updates[i];
  
 -              update->lock = update_ref_lock(update->refname,
 -                                             (update->have_old ?
 -                                              update->old_sha1 : NULL),
 -                                             update->flags,
 -                                             &update->type,
 -                                             UPDATE_REFS_QUIET_ON_ERR);
 +              update->lock = lock_any_ref_for_update(update->refname,
 +                                                     (update->have_old ?
 +                                                      update->old_sha1 :
 +                                                      NULL),
 +                                                     update->flags,
 +                                                     &update->type);
                if (!update->lock) {
                        if (err)
                                strbuf_addf(err, "Cannot lock the ref '%s'.",
                struct ref_update *update = updates[i];
  
                if (!is_null_sha1(update->new_sha1)) {
 -                      ret = update_ref_write(msg,
 -                                             update->refname,
 -                                             update->new_sha1,
 -                                             update->lock, err,
 -                                             UPDATE_REFS_QUIET_ON_ERR);
 -                      update->lock = NULL; /* freed by update_ref_write */
 -                      if (ret)
 +                      ret = write_ref_sha1(update->lock, update->new_sha1,
 +                                           msg);
 +                      update->lock = NULL; /* freed by write_ref_sha1 */
 +                      if (ret) {
 +                              if (err)
 +                                      strbuf_addf(err, "Cannot update the ref '%s'.",
 +                                                  update->refname);
                                goto cleanup;
 +                      }
                }
        }
  
                struct ref_update *update = updates[i];
  
                if (update->lock) {
 -                      delnames[delnum++] = update->lock->ref_name;
                        ret |= delete_ref_loose(update->lock, update->type);
 +                      if (!(update->flags & REF_ISPRUNING))
 +                              delnames[delnum++] = update->lock->ref_name;
                }
        }
  
        clear_loose_ref_cache(&ref_cache);
  
  cleanup:
 +      transaction->state = REF_TRANSACTION_CLOSED;
 +
        for (i = 0; i < n; i++)
                if (updates[i]->lock)
                        unlock_ref(updates[i]->lock);