Merge branch 'ee/clean-remove-dirs'
authorJunio C Hamano <gitster@pobox.com>
Mon, 3 Aug 2015 18:01:13 +0000 (11:01 -0700)
committerJunio C Hamano <gitster@pobox.com>
Mon, 3 Aug 2015 18:01:13 +0000 (11:01 -0700)
Replace "is this subdirectory a separate repository that should not
be touched?" check "git clean" does by checking if it has .git/HEAD
using the submodule-related code with a more optimized check.

* ee/clean-remove-dirs:
read_gitfile_gently: fix use-after-free
clean: improve performance when removing lots of directories
p7300: add performance tests for clean
t7300: add tests to document behavior of clean and nested git
setup: sanity check file size in read_gitfile_gently
setup: add gentle version of read_gitfile

1  2 
cache.h
setup.c
diff --combined cache.h
index 8b621d73520dd757773ad1c32b67af76b6046820,858d9b368801a19ccab9c91c719e3b15680b7bd5..dcc9d3f40b1ebfd3e2a4d959b11b5df9328750f9
+++ b/cache.h
@@@ -397,7 -397,6 +397,7 @@@ static inline enum object_type object_t
  #define EXEC_PATH_ENVIRONMENT "GIT_EXEC_PATH"
  #define CEILING_DIRECTORIES_ENVIRONMENT "GIT_CEILING_DIRECTORIES"
  #define NO_REPLACE_OBJECTS_ENVIRONMENT "GIT_NO_REPLACE_OBJECTS"
 +#define GIT_REPLACE_REF_BASE_ENVIRONMENT "GIT_REPLACE_REF_BASE"
  #define GITATTRIBUTES_FILE ".gitattributes"
  #define INFOATTRIBUTES_FILE "info/attributes"
  #define ATTRIBUTE_MACRO_PREFIX "[attr]"
@@@ -447,7 -446,17 +447,17 @@@ extern int get_common_dir(struct strbu
  extern const char *get_git_namespace(void);
  extern const char *strip_namespace(const char *namespaced_ref);
  extern const char *get_git_work_tree(void);
- extern const char *read_gitfile(const char *path);
+ #define READ_GITFILE_ERR_STAT_FAILED 1
+ #define READ_GITFILE_ERR_NOT_A_FILE 2
+ #define READ_GITFILE_ERR_OPEN_FAILED 3
+ #define READ_GITFILE_ERR_READ_FAILED 4
+ #define READ_GITFILE_ERR_INVALID_FORMAT 5
+ #define READ_GITFILE_ERR_NO_PATH 6
+ #define READ_GITFILE_ERR_NOT_A_REPO 7
+ #define READ_GITFILE_ERR_TOO_LARGE 8
+ 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 void set_git_work_tree(const char *tree);
  
@@@ -623,7 -632,6 +633,7 @@@ extern unsigned long pack_size_limit_cf
   * been sought but there were none.
   */
  extern int check_replace_refs;
 +extern char *git_replace_ref_base;
  
  extern int fsync_object_files;
  extern int core_preload_index;
@@@ -945,17 -953,8 +955,17 @@@ extern int has_sha1_pack(const unsigne
   * Return true iff we have an object named sha1, whether local or in
   * an alternate object database, and whether packed or loose.  This
   * function does not respect replace references.
 + *
 + * If the QUICK flag is set, do not re-check the pack directory
 + * when we cannot find the object (this means we may give a false
 + * negative answer if another process is simultaneously repacking).
   */
 -extern int has_sha1_file(const unsigned char *sha1);
 +#define HAS_SHA1_QUICK 0x1
 +extern int has_sha1_file_with_flags(const unsigned char *sha1, int flags);
 +static inline int has_sha1_file(const unsigned char *sha1)
 +{
 +      return has_sha1_file_with_flags(sha1, 0);
 +}
  
  /*
   * Return true iff an alternate object database has a loose object
@@@ -1709,6 -1708,5 +1719,6 @@@ int stat_validity_check(struct stat_val
  void stat_validity_update(struct stat_validity *sv, int fd);
  
  int versioncmp(const char *s1, const char *s2);
 +void sleep_millisec(int millisec);
  
  #endif /* CACHE_H */
diff --combined setup.c
index 82c0cc2a13bfeae3ec364dce2438fb4c67e46a35,97bb5e3b93e8e678832a6d9f057eaa0ec48ed9ac..5f9f07dcdb059ad6aeac8d90ee18aedf56a8f85e
+++ b/setup.c
@@@ -4,7 -4,6 +4,7 @@@
  
  static int inside_git_dir = -1;
  static int inside_work_tree = -1;
 +static int work_tree_config_is_bogus;
  
  /*
   * The input parameter must contain an absolute path, and it must already be
@@@ -328,10 -327,6 +328,10 @@@ void setup_work_tree(void
  
        if (initialized)
                return;
 +
 +      if (work_tree_config_is_bogus)
 +              die("unable to set up work tree using invalid config");
 +
        work_tree = get_git_work_tree();
        git_dir = get_git_dir();
        if (!is_absolute_path(git_dir))
@@@ -411,35 -406,58 +411,58 @@@ static void update_linked_gitdir(const 
  /*
   * Try to read the location of the git directory from the .git file,
   * return path to git directory if found.
+  *
+  * On failure, if return_error_code is not NULL, return_error_code
+  * will be set to an error code and NULL will be returned. If
+  * return_error_code is NULL the function will die instead (for most
+  * cases).
   */
- const char *read_gitfile(const char *path)
+ const char *read_gitfile_gently(const char *path, int *return_error_code)
  {
-       char *buf;
-       char *dir;
+       const int max_file_size = 1 << 20;  /* 1MB */
+       int error_code = 0;
+       char *buf = NULL;
+       char *dir = NULL;
        const char *slash;
        struct stat st;
        int fd;
        ssize_t len;
  
-       if (stat(path, &st))
-               return NULL;
-       if (!S_ISREG(st.st_mode))
-               return NULL;
+       if (stat(path, &st)) {
+               error_code = READ_GITFILE_ERR_STAT_FAILED;
+               goto cleanup_return;
+       }
+       if (!S_ISREG(st.st_mode)) {
+               error_code = READ_GITFILE_ERR_NOT_A_FILE;
+               goto cleanup_return;
+       }
+       if (st.st_size > max_file_size) {
+               error_code = READ_GITFILE_ERR_TOO_LARGE;
+               goto cleanup_return;
+       }
        fd = open(path, O_RDONLY);
-       if (fd < 0)
-               die_errno("Error opening '%s'", path);
+       if (fd < 0) {
+               error_code = READ_GITFILE_ERR_OPEN_FAILED;
+               goto cleanup_return;
+       }
        buf = xmalloc(st.st_size + 1);
        len = read_in_full(fd, buf, st.st_size);
        close(fd);
-       if (len != st.st_size)
-               die("Error reading %s", path);
+       if (len != st.st_size) {
+               error_code = READ_GITFILE_ERR_READ_FAILED;
+               goto cleanup_return;
+       }
        buf[len] = '\0';
-       if (!starts_with(buf, "gitdir: "))
-               die("Invalid gitfile format: %s", path);
+       if (!starts_with(buf, "gitdir: ")) {
+               error_code = READ_GITFILE_ERR_INVALID_FORMAT;
+               goto cleanup_return;
+       }
        while (buf[len - 1] == '\n' || buf[len - 1] == '\r')
                len--;
-       if (len < 9)
-               die("No path in gitfile: %s", path);
+       if (len < 9) {
+               error_code = READ_GITFILE_ERR_NO_PATH;
+               goto cleanup_return;
+       }
        buf[len] = '\0';
        dir = buf + 8;
  
                free(buf);
                buf = dir;
        }
-       if (!is_git_directory(dir))
-               die("Not a git repository: %s", dir);
+       if (!is_git_directory(dir)) {
+               error_code = READ_GITFILE_ERR_NOT_A_REPO;
+               goto cleanup_return;
+       }
        update_linked_gitdir(path, dir);
        path = real_path(dir);
  
+ cleanup_return:
+       if (return_error_code)
+               *return_error_code = error_code;
+       else if (error_code) {
+               switch (error_code) {
+               case READ_GITFILE_ERR_STAT_FAILED:
+               case READ_GITFILE_ERR_NOT_A_FILE:
+                       /* non-fatal; follow return path */
+                       break;
+               case READ_GITFILE_ERR_OPEN_FAILED:
+                       die_errno("Error opening '%s'", path);
+               case READ_GITFILE_ERR_TOO_LARGE:
+                       die("Too large to be a .git file: '%s'", path);
+               case READ_GITFILE_ERR_READ_FAILED:
+                       die("Error reading %s", path);
+               case READ_GITFILE_ERR_INVALID_FORMAT:
+                       die("Invalid gitfile format: %s", path);
+               case READ_GITFILE_ERR_NO_PATH:
+                       die("No path in gitfile: %s", path);
+               case READ_GITFILE_ERR_NOT_A_REPO:
+                       die("Not a git repository: %s", dir);
+               default:
+                       assert(0);
+               }
+       }
        free(buf);
-       return path;
+       return error_code ? NULL : path;
  }
  
  static const char *setup_explicit_git_dir(const char *gitdirenv,
        if (work_tree_env)
                set_git_work_tree(work_tree_env);
        else if (is_bare_repository_cfg > 0) {
 -              if (git_work_tree_cfg) /* #22.2, #30 */
 -                      die("core.bare and core.worktree do not make sense");
 +              if (git_work_tree_cfg) {
 +                      /* #22.2, #30 */
 +                      warning("core.bare and core.worktree do not make sense");
 +                      work_tree_config_is_bogus = 1;
 +              }
  
                /* #18, #26 */
                set_git_dir(gitdirenv);