Merge branch 'jk/ref-cache-non-repository-optim'
authorJunio C Hamano <gitster@pobox.com>
Wed, 3 Feb 2016 22:16:07 +0000 (14:16 -0800)
committerJunio C Hamano <gitster@pobox.com>
Wed, 3 Feb 2016 22:16:07 +0000 (14:16 -0800)
The underlying machinery used by "ls-files -o" and other commands
have been taught not to create empty submodule ref cache for a
directory that is not a submodule. This removes a ton of wasted
CPU cycles.

* jk/ref-cache-non-repository-optim:
resolve_gitlink_ref: ignore non-repository paths
clean: make is_git_repository a public function

1  2 
builtin/clean.c
cache.h
refs/files-backend.c
diff --combined builtin/clean.c
index cc5f9723dead24b528c676ae06af4ed152c9d2e6,919157bc2fa0d72653b64c24f7c5b78039df191a..7b08237480fde101640ce361b3b95106e7ffa930
@@@ -147,30 -147,6 +147,6 @@@ static int exclude_cb(const struct opti
        return 0;
  }
  
- /*
-  * Return 1 if the given path is the root of a git repository or
-  * submodule else 0. Will not return 1 for bare repositories with the
-  * exception of creating a bare repository in "foo/.git" and calling
-  * is_git_repository("foo").
-  */
- static int is_git_repository(struct strbuf *path)
- {
-       int ret = 0;
-       int gitfile_error;
-       size_t orig_path_len = path->len;
-       assert(orig_path_len != 0);
-       strbuf_complete(path, '/');
-       strbuf_addstr(path, ".git");
-       if (read_gitfile_gently(path->buf, &gitfile_error) || is_git_directory(path->buf))
-               ret = 1;
-       if (gitfile_error == READ_GITFILE_ERR_OPEN_FAILED ||
-           gitfile_error == READ_GITFILE_ERR_READ_FAILED)
-               ret = 1;  /* This could be a real .git file, take the
-                          * safe option and avoid cleaning */
-       strbuf_setlen(path, orig_path_len);
-       return ret;
- }
  static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag,
                int dry_run, int quiet, int *dir_gone)
  {
  
        *dir_gone = 1;
  
-       if ((force_flag & REMOVE_DIR_KEEP_NESTED_GIT) && is_git_repository(path)) {
+       if ((force_flag & REMOVE_DIR_KEEP_NESTED_GIT) && is_nonbare_repository_dir(path)) {
                if (!quiet) {
                        quote_path_relative(path->buf, prefix, &quoted);
                        printf(dry_run ?  _(msg_would_skip_git_dir) : _(msg_skip_git_dir),
@@@ -594,7 -570,7 +570,7 @@@ static int *list_and_choose(struct menu
                               clean_get_color(CLEAN_COLOR_RESET));
                }
  
 -              if (strbuf_getline(&choice, stdin, '\n') != EOF) {
 +              if (strbuf_getline_lf(&choice, stdin) != EOF) {
                        strbuf_trim(&choice);
                } else {
                        eof = 1;
@@@ -676,7 -652,7 +652,7 @@@ static int filter_by_patterns_cmd(void
                clean_print_color(CLEAN_COLOR_PROMPT);
                printf(_("Input ignore patterns>> "));
                clean_print_color(CLEAN_COLOR_RESET);
 -              if (strbuf_getline(&confirm, stdin, '\n') != EOF)
 +              if (strbuf_getline_lf(&confirm, stdin) != EOF)
                        strbuf_trim(&confirm);
                else
                        putchar('\n');
@@@ -774,7 -750,7 +750,7 @@@ static int ask_each_cmd(void
                        qname = quote_path_relative(item->string, NULL, &buf);
                        /* TRANSLATORS: Make sure to keep [y/N] as is */
                        printf(_("Remove %s [y/N]? "), qname);
 -                      if (strbuf_getline(&confirm, stdin, '\n') != EOF) {
 +                      if (strbuf_getline_lf(&confirm, stdin) != EOF) {
                                strbuf_trim(&confirm);
                        } else {
                                putchar('\n');
diff --combined cache.h
index dfc459c0637ff2ff94adba27ec40df84bd82b709,67141a46e432fe3c11b544e493ec0f98fcca9dc9..553b04bfb8f899a4106a800f29225683ecd2e922
+++ b/cache.h
@@@ -9,7 -9,6 +9,7 @@@
  #include "convert.h"
  #include "trace.h"
  #include "string-list.h"
 +#include "pack-revindex.h"
  
  #include SHA1_HEADER
  #ifndef platform_SHA_CTX
@@@ -215,7 -214,7 +215,7 @@@ struct cache_entry 
  #define CE_INTENT_TO_ADD     (1 << 29)
  #define CE_SKIP_WORKTREE     (1 << 30)
  /* CE_EXTENDED2 is for future extension */
 -#define CE_EXTENDED2         (1 << 31)
 +#define CE_EXTENDED2         (1U << 31)
  
  #define CE_EXTENDED_FLAGS (CE_INTENT_TO_ADD | CE_SKIP_WORKTREE)
  
@@@ -260,7 -259,6 +260,7 @@@ static inline unsigned create_ce_flags(
  #define ce_uptodate(ce) ((ce)->ce_flags & CE_UPTODATE)
  #define ce_skip_worktree(ce) ((ce)->ce_flags & CE_SKIP_WORKTREE)
  #define ce_mark_uptodate(ce) ((ce)->ce_flags |= CE_UPTODATE)
 +#define ce_intent_to_add(ce) ((ce)->ce_flags & CE_INTENT_TO_ADD)
  
  #define ce_permissions(mode) (((mode) & 0100) ? 0755 : 0644)
  static inline unsigned int create_ce_mode(unsigned int mode)
@@@ -458,7 -456,6 +458,6 @@@ extern char *git_work_tree_cfg
  extern int is_inside_work_tree(void);
  extern const char *get_git_dir(void);
  extern const char *get_git_common_dir(void);
- extern int is_git_directory(const char *path);
  extern char *get_object_directory(void);
  extern char *get_index_file(void);
  extern char *get_graft_file(void);
@@@ -469,6 -466,25 +468,25 @@@ extern const char *get_git_namespace(vo
  extern const char *strip_namespace(const char *namespaced_ref);
  extern const char *get_git_work_tree(void);
  
+ /*
+  * Return true if the given path is a git directory; note that this _just_
+  * looks at the directory itself. If you want to know whether "foo/.git"
+  * is a repository, you must feed that path, not just "foo".
+  */
+ extern int is_git_directory(const char *path);
+ /*
+  * Return 1 if the given path is the root of a git repository or
+  * submodule, else 0. Will not return 1 for bare repositories with the
+  * exception of creating a bare repository in "foo/.git" and calling
+  * is_git_repository("foo").
+  *
+  * If we run into read errors, we err on the side of saying "yes, it is",
+  * as we usually consider sub-repos precious, and would prefer to err on the
+  * side of not disrupting or deleting them.
+  */
+ extern int is_nonbare_repository_dir(struct strbuf *path);
  #define READ_GITFILE_ERR_STAT_FAILED 1
  #define READ_GITFILE_ERR_NOT_A_FILE 2
  #define READ_GITFILE_ERR_OPEN_FAILED 3
@@@ -1301,7 -1317,6 +1319,7 @@@ extern struct packed_git 
                 freshened:1,
                 do_not_close:1;
        unsigned char sha1[20];
 +      struct revindex_entry *revindex;
        /* something like ".git/objects/pack/xxxxx.pack" */
        char pack_name[FLEX_ARRAY]; /* more */
  } *packed_git;
diff --combined refs/files-backend.c
index 81c92b410eb76cd11dd56f6b82afec6668745dd1,3a27f275859acbe1afe516e4165f600b900f634a..b56976288819c87307c03e4e5abf9b9458d09970
@@@ -933,6 -933,10 +933,10 @@@ static void clear_loose_ref_cache(struc
        }
  }
  
+ /*
+  * Create a new submodule ref cache and add it to the internal
+  * set of caches.
+  */
  static struct ref_cache *create_ref_cache(const char *submodule)
  {
        int len;
        len = strlen(submodule) + 1;
        refs = xcalloc(1, sizeof(struct ref_cache) + len);
        memcpy(refs->name, submodule, len);
+       refs->next = submodule_ref_caches;
+       submodule_ref_caches = refs;
        return refs;
  }
  
- /*
-  * Return a pointer to a ref_cache for the specified submodule. For
-  * the main repository, use submodule==NULL. The returned structure
-  * will be allocated and initialized but not necessarily populated; it
-  * should not be freed.
-  */
- static struct ref_cache *get_ref_cache(const char *submodule)
+ static struct ref_cache *lookup_ref_cache(const char *submodule)
  {
        struct ref_cache *refs;
  
        for (refs = submodule_ref_caches; refs; refs = refs->next)
                if (!strcmp(submodule, refs->name))
                        return refs;
+       return NULL;
+ }
  
-       refs = create_ref_cache(submodule);
-       refs->next = submodule_ref_caches;
-       submodule_ref_caches = refs;
+ /*
+  * Return a pointer to a ref_cache for the specified submodule. For
+  * the main repository, use submodule==NULL. The returned structure
+  * will be allocated and initialized but not necessarily populated; it
+  * should not be freed.
+  */
+ static struct ref_cache *get_ref_cache(const char *submodule)
+ {
+       struct ref_cache *refs = lookup_ref_cache(submodule);
+       if (!refs)
+               refs = create_ref_cache(submodule);
        return refs;
  }
  
@@@ -1336,16 -1346,24 +1346,24 @@@ static int resolve_gitlink_ref_recursiv
  int resolve_gitlink_ref(const char *path, const char *refname, unsigned char *sha1)
  {
        int len = strlen(path), retval;
-       char *submodule;
+       struct strbuf submodule = STRBUF_INIT;
        struct ref_cache *refs;
  
        while (len && path[len-1] == '/')
                len--;
        if (!len)
                return -1;
-       submodule = xstrndup(path, len);
-       refs = get_ref_cache(submodule);
-       free(submodule);
+       strbuf_add(&submodule, path, len);
+       refs = lookup_ref_cache(submodule.buf);
+       if (!refs) {
+               if (!is_nonbare_repository_dir(&submodule)) {
+                       strbuf_release(&submodule);
+                       return -1;
+               }
+               refs = create_ref_cache(submodule.buf);
+       }
+       strbuf_release(&submodule);
  
        retval = resolve_gitlink_ref_recursive(refs, refname, sha1, 0);
        return retval;
@@@ -1840,17 -1858,12 +1858,17 @@@ static int verify_lock(struct ref_lock 
        if (read_ref_full(lock->ref_name,
                          mustexist ? RESOLVE_REF_READING : 0,
                          lock->old_oid.hash, NULL)) {
 -              int save_errno = errno;
 -              strbuf_addf(err, "can't verify ref %s", lock->ref_name);
 -              errno = save_errno;
 -              return -1;
 +              if (old_sha1) {
 +                      int save_errno = errno;
 +                      strbuf_addf(err, "can't verify ref %s", lock->ref_name);
 +                      errno = save_errno;
 +                      return -1;
 +              } else {
 +                      hashclr(lock->old_oid.hash);
 +                      return 0;
 +              }
        }
 -      if (hashcmp(lock->old_oid.hash, old_sha1)) {
 +      if (old_sha1 && hashcmp(lock->old_oid.hash, old_sha1)) {
                strbuf_addf(err, "ref %s is at %s but expected %s",
                            lock->ref_name,
                            sha1_to_hex(lock->old_oid.hash),
@@@ -1887,8 -1900,7 +1905,8 @@@ static struct ref_lock *lock_ref_sha1_b
        const char *orig_refname = refname;
        struct ref_lock *lock;
        int last_errno = 0;
 -      int type, lflags;
 +      int type;
 +      int lflags = 0;
        int mustexist = (old_sha1 && !is_null_sha1(old_sha1));
        int resolve_flags = 0;
        int attempts_remaining = 3;
  
        if (mustexist)
                resolve_flags |= RESOLVE_REF_READING;
 -      if (flags & REF_DELETING) {
 +      if (flags & REF_DELETING)
                resolve_flags |= RESOLVE_REF_ALLOW_BAD_NAME;
 -              if (flags & REF_NODEREF)
 -                      resolve_flags |= RESOLVE_REF_NO_RECURSE;
 +      if (flags & REF_NODEREF) {
 +              resolve_flags |= RESOLVE_REF_NO_RECURSE;
 +              lflags |= LOCK_NO_DEREF;
        }
  
        refname = resolve_ref_unsafe(refname, resolve_flags,
  
                goto error_return;
        }
 +
 +      if (flags & REF_NODEREF)
 +              refname = orig_refname;
 +
        /*
         * If the ref did not exist and we are creating it, make sure
         * there is no existing packed ref whose name begins with our
  
        lock->lk = xcalloc(1, sizeof(struct lock_file));
  
 -      lflags = 0;
 -      if (flags & REF_NODEREF) {
 -              refname = orig_refname;
 -              lflags |= LOCK_NO_DEREF;
 -      }
        lock->ref_name = xstrdup(refname);
        lock->orig_ref_name = xstrdup(orig_refname);
        strbuf_git_path(&ref_file, "%s", refname);
                        goto error_return;
                }
        }
 -      if (old_sha1 && verify_lock(lock, old_sha1, mustexist, err)) {
 +      if (verify_lock(lock, old_sha1, mustexist, err)) {
                last_errno = errno;
                goto error_return;
        }
@@@ -2817,72 -2829,73 +2835,72 @@@ static int commit_ref_update(struct ref
        return 0;
  }
  
 -int create_symref(const char *ref_target, const char *refs_heads_master,
 -                const char *logmsg)
 +static int create_ref_symlink(struct ref_lock *lock, const char *target)
  {
 -      char *lockpath = NULL;
 -      char ref[1000];
 -      int fd, len, written;
 -      char *git_HEAD = git_pathdup("%s", ref_target);
 -      unsigned char old_sha1[20], new_sha1[20];
 -      struct strbuf err = STRBUF_INIT;
 -
 -      if (logmsg && read_ref(ref_target, old_sha1))
 -              hashclr(old_sha1);
 -
 -      if (safe_create_leading_directories(git_HEAD) < 0)
 -              return error("unable to create directory for %s", git_HEAD);
 -
 +      int ret = -1;
  #ifndef NO_SYMLINK_HEAD
 -      if (prefer_symlink_refs) {
 -              unlink(git_HEAD);
 -              if (!symlink(refs_heads_master, git_HEAD))
 -                      goto done;
 +      char *ref_path = get_locked_file_path(lock->lk);
 +      unlink(ref_path);
 +      ret = symlink(target, ref_path);
 +      free(ref_path);
 +
 +      if (ret)
                fprintf(stderr, "no symlink - falling back to symbolic ref\n");
 -      }
  #endif
 +      return ret;
 +}
  
 -      len = snprintf(ref, sizeof(ref), "ref: %s\n", refs_heads_master);
 -      if (sizeof(ref) <= len) {
 -              error("refname too long: %s", refs_heads_master);
 -              goto error_free_return;
 -      }
 -      lockpath = mkpathdup("%s.lock", git_HEAD);
 -      fd = open(lockpath, O_CREAT | O_EXCL | O_WRONLY, 0666);
 -      if (fd < 0) {
 -              error("Unable to open %s for writing", lockpath);
 -              goto error_free_return;
 -      }
 -      written = write_in_full(fd, ref, len);
 -      if (close(fd) != 0 || written != len) {
 -              error("Unable to write to %s", lockpath);
 -              goto error_unlink_return;
 -      }
 -      if (rename(lockpath, git_HEAD) < 0) {
 -              error("Unable to create %s", git_HEAD);
 -              goto error_unlink_return;
 -      }
 -      if (adjust_shared_perm(git_HEAD)) {
 -              error("Unable to fix permissions on %s", lockpath);
 -      error_unlink_return:
 -              unlink_or_warn(lockpath);
 -      error_free_return:
 -              free(lockpath);
 -              free(git_HEAD);
 -              return -1;
 +static void update_symref_reflog(struct ref_lock *lock, const char *refname,
 +                               const char *target, const char *logmsg)
 +{
 +      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)) {
 +              error("%s", err.buf);
 +              strbuf_release(&err);
        }
 -      free(lockpath);
 +}
  
 -#ifndef NO_SYMLINK_HEAD
 -      done:
 -#endif
 -      if (logmsg && !read_ref(refs_heads_master, new_sha1) &&
 -              log_ref_write(ref_target, old_sha1, new_sha1, logmsg, 0, &err)) {
 +static int create_symref_locked(struct ref_lock *lock, const char *refname,
 +                              const char *target, const char *logmsg)
 +{
 +      if (prefer_symlink_refs && !create_ref_symlink(lock, target)) {
 +              update_symref_reflog(lock, refname, target, logmsg);
 +              return 0;
 +      }
 +
 +      if (!fdopen_lock_file(lock->lk, "w"))
 +              return error("unable to fdopen %s: %s",
 +                           lock->lk->tempfile.filename.buf, strerror(errno));
 +
 +      update_symref_reflog(lock, refname, target, logmsg);
 +
 +      /* no error check; commit_ref will check ferror */
 +      fprintf(lock->lk->tempfile.fp, "ref: %s\n", target);
 +      if (commit_ref(lock) < 0)
 +              return error("unable to write symref for %s: %s", refname,
 +                           strerror(errno));
 +      return 0;
 +}
 +
 +int create_symref(const char *refname, const char *target, const char *logmsg)
 +{
 +      struct strbuf err = STRBUF_INIT;
 +      struct ref_lock *lock;
 +      int ret;
 +
 +      lock = lock_ref_sha1_basic(refname, NULL, NULL, NULL, REF_NODEREF, NULL,
 +                                 &err);
 +      if (!lock) {
                error("%s", err.buf);
                strbuf_release(&err);
 +              return -1;
        }
  
 -      free(git_HEAD);
 -      return 0;
 +      ret = create_symref_locked(lock, refname, target, logmsg);
 +      unlock_ref(lock);
 +      return ret;
  }
  
  int reflog_exists(const char *refname)