Merge branch 'jc/pack-objects-bigfile'
authorJunio C Hamano <gitster@pobox.com>
Wed, 27 Apr 2011 18:36:41 +0000 (11:36 -0700)
committerJunio C Hamano <gitster@pobox.com>
Wed, 27 Apr 2011 18:36:41 +0000 (11:36 -0700)
* jc/pack-objects-bigfile:
Teach core.bigfilethreashold to pack-objects

1  2 
Documentation/config.txt
cache.h
config.c
environment.c
fast-import.c
diff --combined Documentation/config.txt
index 750c86d4f51b256faac1b1b5eb26275384d87886,c5598f4af96a6d24f1b9375d737a4471a846cd16..0906499e7d26ac1e808f3a598675a32a7ad72739
@@@ -62,7 -62,7 +62,7 @@@ Internal whitespace within a variable v
  
  The values following the equals sign in variable assign are all either
  a string, an integer, or a boolean.  Boolean values may be given as yes/no,
 -0/1, true/false or on/off.  Case is not significant in boolean values, when
 +1/0, true/false or on/off.  Case is not significant in boolean values, when
  converting value to the canonical form using '--bool' type specifier;
  'git config' will ensure that the output is "true" or "false".
  
@@@ -320,7 -320,7 +320,7 @@@ core.worktree:
        Set the path to the root of the working tree.
        This can be overridden by the GIT_WORK_TREE environment
        variable and the '--work-tree' command line option.
 -      The value can an absolute path or relative to the path to
 +      The value can be an absolute path or relative to the path to
        the .git directory, which is either specified by --git-dir
        or GIT_DIR, or automatically discovered.
        If --git-dir or GIT_DIR is specified but none of
@@@ -376,6 -376,15 +376,6 @@@ core.warnAmbiguousRefs:
        If true, git will warn you if the ref name you passed it is ambiguous
        and might match multiple refs in the .git/refs/ tree. True by default.
  
 -core.abbrevguard::
 -      Even though git makes sure that it uses enough hexdigits to show
 -      an abbreviated object name unambiguously, as more objects are
 -      added to the repository over time, a short name that used to be
 -      unique will stop being unique.  Git uses this many extra hexdigits
 -      that are more than necessary to make the object name currently
 -      unique, in the hope that its output will stay unique a bit longer.
 -      Defaults to 0.
 -
  core.compression::
        An integer -1..9, indicating a default compression level.
        -1 is the zlib default. 0 means no compression,
@@@ -442,8 -451,6 +442,6 @@@ for most projects as source code and ot
  be delta compressed, but larger binary media files won't be.
  +
  Common unit suffixes of 'k', 'm', or 'g' are supported.
- +
- Currently only linkgit:git-fast-import[1] honors this setting.
  
  core.excludesfile::
        In addition to '.gitignore' (per-directory) and
@@@ -558,12 -565,6 +556,12 @@@ core.sparseCheckout:
        Enable "sparse checkout" feature. See section "Sparse checkout" in
        linkgit:git-read-tree[1] for more information.
  
 +core.abbrev::
 +      Set the length object names are abbreviated to.  If unspecified,
 +      many commands abbreviate to 7 hexdigits, which may not be enough
 +      for abbreviated object names to stay unique for sufficiently long
 +      time.
 +
  add.ignore-errors::
  add.ignoreErrors::
        Tells 'git add' to continue adding files when some files cannot be
@@@ -897,13 -898,9 +895,13 @@@ diff.wordRegex:
        characters are *ignorable* whitespace.
  
  fetch.recurseSubmodules::
 -      A boolean value which changes the behavior for fetch and pull, the
 -      default is to not recursively fetch populated submodules unless
 -      configured otherwise.
 +      This option can be either set to a boolean value or to 'on-demand'.
 +      Setting it to a boolean changes the behavior of fetch and pull to
 +      unconditionally recurse into submodules when set to true or to not
 +      recurse at all when set to false. When set to 'on-demand' (the default
 +      value), fetch and pull will only recurse into a populated submodule
 +      when its superproject retrieves a commit that updates the submodule's
 +      reference.
  
  fetch.unpackLimit::
        If the number of objects fetched over the git native
@@@ -1102,12 -1099,6 +1100,12 @@@ All gitcvs variables except for 'gitcvs
  is one of "ext" and "pserver") to make them apply only for the given
  access method.
  
 +grep.lineNumber::
 +      If set to true, enable '-n' option by default.
 +
 +grep.extendedRegexp::
 +      If set to true, enable '--extended-regexp' option by default.
 +
  gui.commitmsgwidth::
        Defines how wide the commit message window is in the
        linkgit:git-gui[1]. "75" is the default.
@@@ -1598,8 -1589,7 +1596,8 @@@ push.default:
  * `matching` - push all matching branches.
    All branches having the same name in both ends are considered to be
    matching. This is the default.
 -* `tracking` - push the current branch to its upstream branch.
 +* `upstream` - push the current branch to its upstream branch.
 +* `tracking` - deprecated synonym for `upstream`.
  * `current` - push the current branch to a branch of the same name.
  
  rebase.stat::
@@@ -1827,7 -1817,7 +1825,7 @@@ submodule.<name>.update:
        linkgit:git-submodule[1] and linkgit:gitmodules[5] for details.
  
  submodule.<name>.fetchRecurseSubmodules::
 -      This option can be used to enable/disable recursive fetching of this
 +      This option can be used to control recursive fetching of this
        submodule. It can be overridden by using the --[no-]recurse-submodules
        command line option to "git fetch" and "git pull".
        This setting will override that from in the linkgit:gitmodules[5]
diff --combined cache.h
index 2674f4cf5a74aaa8e6137985d552d7b6123a432c,baec5edf031a72204fd4347586d7da0d0bb3d7ba..28899b7b7881474eed4a6f3b6fda8db79d5cbc9c
+++ b/cache.h
@@@ -5,7 -5,6 +5,7 @@@
  #include "strbuf.h"
  #include "hash.h"
  #include "advice.h"
 +#include "gettext.h"
  
  #include SHA1_HEADER
  #ifndef git_SHA_CTX
@@@ -437,7 -436,6 +437,7 @@@ extern void verify_non_filename(const c
  
  #define INIT_DB_QUIET 0x0001
  
 +extern int set_git_dir_init(const char *git_dir, const char *real_git_dir, int);
  extern int init_db(const char *template_dir, unsigned int flags);
  
  #define alloc_nr(x) (((x)+16)*3/2)
@@@ -502,23 -500,8 +502,23 @@@ extern int index_name_is_other(const st
  extern int ie_match_stat(const struct index_state *, struct cache_entry *, struct stat *, unsigned int);
  extern int ie_modified(const struct index_state *, struct cache_entry *, struct stat *, unsigned int);
  
 -extern int ce_path_match(const struct cache_entry *ce, const char **pathspec);
 -extern int index_fd(unsigned char *sha1, int fd, struct stat *st, int write_object, enum object_type type, const char *path);
 +struct pathspec {
 +      const char **raw; /* get_pathspec() result, not freed by free_pathspec() */
 +      int nr;
 +      unsigned int has_wildcard:1;
 +      unsigned int recursive:1;
 +      int max_depth;
 +      struct pathspec_item {
 +              const char *match;
 +              int len;
 +              unsigned int has_wildcard:1;
 +      } *items;
 +};
 +
 +extern int init_pathspec(struct pathspec *, const char **);
 +extern void free_pathspec(struct pathspec *);
 +extern int ce_path_match(const struct cache_entry *ce, const struct pathspec *pathspec);
 +extern int index_fd(unsigned char *sha1, int fd, struct stat *st, int write_object, enum object_type type, const char *path, int format_check);
  extern int index_path(unsigned char *sha1, const char *path, struct stat *st, int write_object);
  extern void fill_stat_cache_info(struct cache_entry *ce, struct stat *st);
  
  #define REFRESH_IGNORE_MISSING        0x0008  /* ignore non-existent */
  #define REFRESH_IGNORE_SUBMODULES     0x0010  /* ignore submodules */
  #define REFRESH_IN_PORCELAIN  0x0020  /* user friendly output, not "needs update" */
 -extern int refresh_index(struct index_state *, unsigned int flags, const char **pathspec, char *seen, char *header_msg);
 +extern int refresh_index(struct index_state *, unsigned int flags, const char **pathspec, char *seen, const char *header_msg);
  
  struct lock_file {
        struct lock_file *next;
@@@ -544,7 -527,6 +544,7 @@@ extern NORETURN void unable_to_lock_ind
  extern int hold_lock_file_for_update(struct lock_file *, const char *path, int);
  extern int hold_lock_file_for_append(struct lock_file *, const char *path, int);
  extern int commit_lock_file(struct lock_file *);
 +extern void update_index_if_able(struct index_state *, struct lock_file *);
  
  extern int hold_locked_index(struct lock_file *, int);
  extern int commit_locked_index(struct lock_file *);
@@@ -558,12 -540,12 +558,12 @@@ extern int trust_executable_bit
  extern int trust_ctime;
  extern int quote_path_fully;
  extern int has_symlinks;
 +extern int minimum_abbrev, default_abbrev;
  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 unique_abbrev_extra_length;
  extern int shared_repository;
  extern const char *apply_default_whitespace;
  extern const char *apply_default_ignorewhitespace;
@@@ -573,6 -555,7 +573,7 @@@ extern int core_compression_seen
  extern size_t packed_git_window_size;
  extern size_t packed_git_limit;
  extern size_t delta_base_cache_limit;
+ extern unsigned long big_file_threshold;
  extern int read_replace_refs;
  extern int fsync_object_files;
  extern int core_preload_index;
@@@ -589,7 -572,7 +590,7 @@@ extern enum safe_crlf safe_crlf
  enum auto_crlf {
        AUTO_CRLF_FALSE = 0,
        AUTO_CRLF_TRUE = 1,
 -      AUTO_CRLF_INPUT = -1,
 +      AUTO_CRLF_INPUT = -1
  };
  
  extern enum auto_crlf auto_crlf;
@@@ -626,7 -609,7 +627,7 @@@ enum rebase_setup_type 
  enum push_default_type {
        PUSH_DEFAULT_NOTHING = 0,
        PUSH_DEFAULT_MATCHING,
 -      PUSH_DEFAULT_TRACKING,
 +      PUSH_DEFAULT_UPSTREAM,
        PUSH_DEFAULT_CURRENT
  };
  
@@@ -694,11 -677,9 +695,11 @@@ static inline void hashclr(unsigned cha
  
  #define EMPTY_TREE_SHA1_HEX \
        "4b825dc642cb6eb9a060e54bf8d69288fbee4904"
 -#define EMPTY_TREE_SHA1_BIN \
 +#define EMPTY_TREE_SHA1_BIN_LITERAL \
         "\x4b\x82\x5d\xc6\x42\xcb\x6e\xb9\xa0\x60" \
         "\xe5\x4b\xf8\xd6\x92\x88\xfb\xee\x49\x04"
 +#define EMPTY_TREE_SHA1_BIN \
 +       ((const unsigned char *) EMPTY_TREE_SHA1_BIN_LITERAL)
  
  int git_mkstemp(char *path, size_t n, const char *template);
  
@@@ -728,7 -709,6 +729,7 @@@ int set_shared_perm(const char *path, i
  #define adjust_shared_perm(path) set_shared_perm((path), 0)
  int safe_create_leading_directories(char *path);
  int safe_create_leading_directories_const(const char *path);
 +int mkdir_in_gitdir(const char *path);
  extern char *expand_user_path(const char *path);
  char *enter_repo(char *path, int strict);
  static inline int is_absolute_path(const char *path)
        return path[0] == '/' || has_dos_drive_prefix(path);
  }
  int is_directory(const char *);
 -const char *make_absolute_path(const char *path);
 -const char *make_nonrelative_path(const char *path);
 -const char *make_relative_path(const char *abs, const char *base);
 +const char *real_path(const char *path);
 +const char *absolute_path(const char *path);
 +const char *relative_path(const char *abs, const char *base);
  int normalize_path_copy(char *dst, const char *src);
  int longest_ancestor_length(const char *path, const char *prefix_list);
  char *strip_path_suffix(const char *path, const char *suffix);
@@@ -779,8 -759,8 +780,8 @@@ static inline unsigned int hexval(unsig
  }
  
  /* Convert to/from hex/sha1 representation */
 -#define MINIMUM_ABBREV 4
 -#define DEFAULT_ABBREV 7
 +#define MINIMUM_ABBREV minimum_abbrev
 +#define DEFAULT_ABBREV default_abbrev
  
  struct object_context {
        unsigned char tree[20];
@@@ -918,8 -898,7 +919,8 @@@ extern struct packed_git 
        time_t mtime;
        int pack_fd;
        unsigned pack_local:1,
 -               pack_keep:1;
 +               pack_keep:1,
 +               do_not_close:1;
        unsigned char sha1[20];
        /* something like ".git/objects/pack/xxxxx.pack" */
        char pack_name[FLEX_ARRAY]; /* more */
@@@ -1019,13 -998,13 +1020,13 @@@ extern int git_config_maybe_bool(const 
  extern int git_config_string(const char **, const char *, const char *);
  extern int git_config_pathname(const char **, const char *, const char *);
  extern int git_config_set(const char *, const char *);
 +extern int git_config_parse_key(const char *, char **, int *);
  extern int git_config_set_multivar(const char *, const char *, const char *, int);
  extern int git_config_rename_section(const char *, const char *);
  extern const char *git_etc_gitconfig(void);
  extern int check_repository_format_version(const char *var, const char *value, void *cb);
  extern int git_env_bool(const char *, int);
  extern int git_config_system(void);
 -extern int git_config_global(void);
  extern int config_error_nonbool(const char *);
  extern const char *get_log_output_encoding(void);
  extern const char *get_commit_output_encoding(void);
@@@ -1087,14 -1066,9 +1088,14 @@@ extern void alloc_report(void)
  /* trace.c */
  __attribute__((format (printf, 1, 2)))
  extern void trace_printf(const char *format, ...);
 +extern void trace_vprintf(const char *key, const char *format, va_list ap);
  __attribute__((format (printf, 2, 3)))
  extern void trace_argv_printf(const char **argv, const char *format, ...);
  extern void trace_repo_setup(const char *prefix);
 +extern int trace_want(const char *key);
 +extern void trace_strbuf(const char *key, const struct strbuf *buf);
 +
 +void packet_trace_identity(const char *prog);
  
  /* convert.c */
  /* returns 1 if *dst was used */
diff --combined config.c
index 0abcada9381a3e6638f94bcf41bf9ee7b96f4de8,85f956058c59e05b4aafb71d05c54e47cd07f39f..d06fb19d511c29e92aa840c664618ca4a6f73fe6
+++ b/config.c
@@@ -20,7 -20,8 +20,7 @@@ static int zlib_compression_seen
  
  const char *config_exclusive_filename = NULL;
  
 -struct config_item
 -{
 +struct config_item {
        struct config_item *next;
        char *name;
        char *value;
@@@ -498,6 -499,13 +498,6 @@@ static int git_default_core_config(cons
                return 0;
        }
  
 -      if (!strcmp(var, "core.abbrevguard")) {
 -              unique_abbrev_extra_length = git_config_int(var, value);
 -              if (unique_abbrev_extra_length < 0)
 -                      unique_abbrev_extra_length = 0;
 -              return 0;
 -      }
 -
        if (!strcmp(var, "core.bare")) {
                is_bare_repository_cfg = git_config_bool(var, value);
                return 0;
                return 0;
        }
  
 +      if (!strcmp(var, "core.abbrev")) {
 +              int abbrev = git_config_int(var, value);
 +              if (abbrev < minimum_abbrev || abbrev > 40)
 +                      return -1;
 +              default_abbrev = abbrev;
 +              return 0;
 +      }
 +
        if (!strcmp(var, "core.loosecompression")) {
                int level = git_config_int(var, value);
                if (level == -1)
                return 0;
        }
  
+       if (!strcmp(var, "core.bigfilethreshold")) {
+               long n = git_config_int(var, value);
+               big_file_threshold = 0 < n ? n : 0;
+               return 0;
+       }
        if (!strcmp(var, "core.packedgitlimit")) {
                packed_git_limit = git_config_int(var, value);
                return 0;
@@@ -737,10 -743,8 +743,10 @@@ static int git_default_push_config(cons
                        push_default = PUSH_DEFAULT_NOTHING;
                else if (!strcmp(value, "matching"))
                        push_default = PUSH_DEFAULT_MATCHING;
 -              else if (!strcmp(value, "tracking"))
 -                      push_default = PUSH_DEFAULT_TRACKING;
 +              else if (!strcmp(value, "upstream"))
 +                      push_default = PUSH_DEFAULT_UPSTREAM;
 +              else if (!strcmp(value, "tracking")) /* deprecated */
 +                      push_default = PUSH_DEFAULT_UPSTREAM;
                else if (!strcmp(value, "current"))
                        push_default = PUSH_DEFAULT_CURRENT;
                else {
@@@ -833,6 -837,11 +839,6 @@@ int git_config_system(void
        return !git_env_bool("GIT_CONFIG_NOSYSTEM", 0);
  }
  
 -int git_config_global(void)
 -{
 -      return !git_env_bool("GIT_CONFIG_NOGLOBAL", 0);
 -}
 -
  int git_config_from_parameters(config_fn_t fn, void *data)
  {
        static int loaded_environment;
@@@ -864,7 -873,7 +870,7 @@@ int git_config_early(config_fn_t fn, vo
        }
  
        home = getenv("HOME");
 -      if (git_config_global() && home) {
 +      if (home) {
                char *user_config = xstrdup(mkpath("%s/.gitconfig", home));
                if (!access(user_config, R_OK)) {
                        ret += git_config_from_file(fn, user_config, data);
@@@ -1095,75 -1104,6 +1101,75 @@@ int git_config_set(const char *key, con
        return git_config_set_multivar(key, value, NULL, 0);
  }
  
 +/*
 + * Auxiliary function to sanity-check and split the key into the section
 + * identifier and variable name.
 + *
 + * Returns 0 on success, -1 when there is an invalid character in the key and
 + * -2 if there is no section name in the key.
 + *
 + * store_key - pointer to char* which will hold a copy of the key with
 + *             lowercase section and variable name
 + * baselen - pointer to int which will hold the length of the
 + *           section + subsection part, can be NULL
 + */
 +int git_config_parse_key(const char *key, char **store_key, int *baselen_)
 +{
 +      int i, dot, baselen;
 +      const char *last_dot = strrchr(key, '.');
 +
 +      /*
 +       * Since "key" actually contains the section name and the real
 +       * key name separated by a dot, we have to know where the dot is.
 +       */
 +
 +      if (last_dot == NULL || last_dot == key) {
 +              error("key does not contain a section: %s", key);
 +              return -2;
 +      }
 +
 +      if (!last_dot[1]) {
 +              error("key does not contain variable name: %s", key);
 +              return -2;
 +      }
 +
 +      baselen = last_dot - key;
 +      if (baselen_)
 +              *baselen_ = baselen;
 +
 +      /*
 +       * Validate the key and while at it, lower case it for matching.
 +       */
 +      *store_key = xmalloc(strlen(key) + 1);
 +
 +      dot = 0;
 +      for (i = 0; key[i]; i++) {
 +              unsigned char c = key[i];
 +              if (c == '.')
 +                      dot = 1;
 +              /* Leave the extended basename untouched.. */
 +              if (!dot || i > baselen) {
 +                      if (!iskeychar(c) ||
 +                          (i == baselen + 1 && !isalpha(c))) {
 +                              error("invalid key: %s", key);
 +                              goto out_free_ret_1;
 +                      }
 +                      c = tolower(c);
 +              } else if (c == '\n') {
 +                      error("invalid key (newline): %s", key);
 +                      goto out_free_ret_1;
 +              }
 +              (*store_key)[i] = c;
 +      }
 +      (*store_key)[i] = 0;
 +
 +      return 0;
 +
 +out_free_ret_1:
 +      free(*store_key);
 +      return -1;
 +}
 +
  /*
   * If value==NULL, unset in (remove from) config,
   * if value_regex!=NULL, disregard key/value pairs where value does not match.
  int git_config_set_multivar(const char *key, const char *value,
        const char *value_regex, int multi_replace)
  {
 -      int i, dot;
        int fd = -1, in_fd;
        int ret;
        char *config_filename;
        struct lock_file *lock = NULL;
 -      const char *last_dot = strrchr(key, '.');
  
        if (config_exclusive_filename)
                config_filename = xstrdup(config_exclusive_filename);
        else
                config_filename = git_pathdup("config");
  
 -      /*
 -       * Since "key" actually contains the section name and the real
 -       * key name separated by a dot, we have to know where the dot is.
 -       */
 -
 -      if (last_dot == NULL) {
 -              error("key does not contain a section: %s", key);
 -              ret = 2;
 +      /* parse-key returns negative; flip the sign to feed exit(3) */
 +      ret = 0 - git_config_parse_key(key, &store.key, &store.baselen);
 +      if (ret)
                goto out_free;
 -      }
 -      store.baselen = last_dot - key;
  
        store.multi_replace = multi_replace;
  
 -      /*
 -       * Validate the key and while at it, lower case it for matching.
 -       */
 -      store.key = xmalloc(strlen(key) + 1);
 -      dot = 0;
 -      for (i = 0; key[i]; i++) {
 -              unsigned char c = key[i];
 -              if (c == '.')
 -                      dot = 1;
 -              /* Leave the extended basename untouched.. */
 -              if (!dot || i > store.baselen) {
 -                      if (!iskeychar(c) || (i == store.baselen+1 && !isalpha(c))) {
 -                              error("invalid key: %s", key);
 -                              free(store.key);
 -                              ret = 1;
 -                              goto out_free;
 -                      }
 -                      c = tolower(c);
 -              } else if (c == '\n') {
 -                      error("invalid key (newline): %s", key);
 -                      free(store.key);
 -                      ret = 1;
 -                      goto out_free;
 -              }
 -              store.key[i] = c;
 -      }
 -      store.key[i] = 0;
  
        /*
         * The lock serves a purpose in addition to locking: the new
diff --combined environment.c
index f4549d3f7b0367b31b0fa0042fb12ff183beb081,8557e84ff6870b2736ec0a2db11f6f75a28906d3..40185bc854ea2c5b8d2e3deb800dd6f3f44482a9
@@@ -15,13 -15,13 +15,13 @@@ int user_ident_explicitly_given
  int trust_executable_bit = 1;
  int trust_ctime = 1;
  int has_symlinks = 1;
 +int minimum_abbrev = 4, default_abbrev = 7;
  int ignore_case;
  int assume_unchanged;
  int prefer_symlink_refs;
  int is_bare_repository_cfg = -1; /* unspecified */
  int log_all_ref_updates = -1; /* unspecified */
  int warn_ambiguous_refs = 1;
 -int unique_abbrev_extra_length;
  int repository_format_version;
  const char *git_commit_encoding;
  const char *git_log_output_encoding;
@@@ -35,6 -35,7 +35,7 @@@ int fsync_object_files
  size_t packed_git_window_size = DEFAULT_PACKED_GIT_WINDOW_SIZE;
  size_t packed_git_limit = DEFAULT_PACKED_GIT_LIMIT;
  size_t delta_base_cache_limit = 16 * 1024 * 1024;
+ unsigned long big_file_threshold = 512 * 1024 * 1024;
  const char *pager_program;
  int pager_use_color = 1;
  const char *editor_program;
@@@ -140,7 -141,7 +141,7 @@@ static int git_work_tree_initialized
  void set_git_work_tree(const char *new_work_tree)
  {
        if (git_work_tree_initialized) {
 -              new_work_tree = make_absolute_path(new_work_tree);
 +              new_work_tree = real_path(new_work_tree);
                if (strcmp(new_work_tree, work_tree))
                        die("internal error: work tree has already been set\n"
                            "Current worktree: %s\nNew worktree: %s",
                return;
        }
        git_work_tree_initialized = 1;
 -      work_tree = xstrdup(make_absolute_path(new_work_tree));
 +      work_tree = xstrdup(real_path(new_work_tree));
  }
  
  const char *get_git_work_tree(void)
diff --combined fast-import.c
index 65d65bf8f91b7a584c7dc77f84a5dec263c3d11c,1421dcd6eb5db6e617fca4bac7471cd83d0cd592..3e4e655bb989076e4bb6311fa4fe41320c344194
@@@ -24,12 -24,10 +24,12 @@@ Format of STDIN stream
      commit_msg
      ('from' sp committish lf)?
      ('merge' sp committish lf)*
 -    file_change*
 +    (file_change | ls)*
      lf?;
    commit_msg ::= data;
  
 +  ls ::= 'ls' sp '"' quoted(path) '"' lf;
 +
    file_change ::= file_clr
      | file_del
      | file_rnm
    ts    ::= # time since the epoch in seconds, ascii base10 notation;
    tz    ::= # GIT style timezone;
  
 -     # note: comments and cat requests may appear anywhere
 +     # note: comments, ls and cat requests may appear anywhere
       # in the input, except within a data command.  Any form
       # of the data command always escapes the related input
       # from comment processing.
       # must be the first character on that line (an lf
       # preceded it).
       #
 +
    cat_blob ::= 'cat-blob' sp (hexsha1 | idnum) lf;
 +  ls_tree  ::= 'ls' sp (hexsha1 | idnum) sp path_str lf;
  
    comment ::= '#' not_lf* lf;
    not_lf  ::= # Any byte that is not ASCII newline (LF);
  #define DEPTH_BITS 13
  #define MAX_DEPTH ((1<<DEPTH_BITS)-1)
  
 -struct object_entry
 -{
 +struct object_entry {
        struct pack_idx_entry idx;
        struct object_entry *next;
        uint32_t type : TYPE_BITS,
                depth : DEPTH_BITS;
  };
  
 -struct object_entry_pool
 -{
 +struct object_entry_pool {
        struct object_entry_pool *next_pool;
        struct object_entry *next_free;
        struct object_entry *end;
        struct object_entry entries[FLEX_ARRAY]; /* more */
  };
  
 -struct mark_set
 -{
 +struct mark_set {
        union {
                struct object_entry *marked[1024];
                struct mark_set *sets[1024];
        unsigned int shift;
  };
  
 -struct last_object
 -{
 +struct last_object {
        struct strbuf data;
        off_t offset;
        unsigned int depth;
        unsigned no_swap : 1;
  };
  
 -struct mem_pool
 -{
 +struct mem_pool {
        struct mem_pool *next_pool;
        char *next_free;
        char *end;
        uintmax_t space[FLEX_ARRAY]; /* more */
  };
  
 -struct atom_str
 -{
 +struct atom_str {
        struct atom_str *next_atom;
        unsigned short str_len;
        char str_dat[FLEX_ARRAY]; /* more */
  };
  
  struct tree_content;
 -struct tree_entry
 -{
 +struct tree_entry {
        struct tree_content *tree;
        struct atom_str *name;
 -      struct tree_entry_ms
 -      {
 +      struct tree_entry_ms {
                uint16_t mode;
                unsigned char sha1[20];
        } versions[2];
  };
  
 -struct tree_content
 -{
 +struct tree_content {
        unsigned int entry_capacity; /* must match avail_tree_content */
        unsigned int entry_count;
        unsigned int delta_depth;
        struct tree_entry *entries[FLEX_ARRAY]; /* more */
  };
  
 -struct avail_tree_content
 -{
 +struct avail_tree_content {
        unsigned int entry_capacity; /* must match tree_content */
        struct avail_tree_content *next_avail;
  };
  
 -struct branch
 -{
 +struct branch {
        struct branch *table_next_branch;
        struct branch *active_next_branch;
        const char *name;
        unsigned char sha1[20];
  };
  
 -struct tag
 -{
 +struct tag {
        struct tag *next_tag;
        const char *name;
        unsigned int pack_id;
        unsigned char sha1[20];
  };
  
 -struct hash_list
 -{
 +struct hash_list {
        struct hash_list *next;
        unsigned char sha1[20];
  };
@@@ -265,7 -274,8 +265,7 @@@ typedef enum 
        WHENSPEC_NOW
  } whenspec_type;
  
 -struct recent_command
 -{
 +struct recent_command {
        struct recent_command *prev;
        struct recent_command *next;
        char *buf;
  /* Configured limits on output */
  static unsigned long max_depth = 10;
  static off_t max_packsize;
- static uintmax_t big_file_threshold = 512 * 1024 * 1024;
  static int force_update;
  static int pack_compression_level = Z_DEFAULT_COMPRESSION;
  static int pack_compression_seen;
@@@ -319,7 -328,6 +318,7 @@@ static struct mark_set *marks
  static const char *export_marks_file;
  static const char *import_marks_file;
  static int import_marks_file_from_stream;
 +static int import_marks_file_ignore_missing;
  static int relative_marks_paths;
  
  /* Our last blob */
@@@ -364,7 -372,6 +363,7 @@@ static int cat_blob_fd = STDOUT_FILENO
  
  static void parse_argv(void);
  static void parse_cat_blob(void);
 +static void parse_ls(struct branch *b);
  
  static void write_branch_report(FILE *rpt, struct branch *b)
  {
@@@ -863,7 -870,6 +862,7 @@@ static void start_packfile(void
        p = xcalloc(1, sizeof(*p) + strlen(tmpfile) + 2);
        strcpy(p->pack_name, tmpfile);
        p->pack_fd = pack_fd;
 +      p->do_not_close = 1;
        pack_file = sha1fd(pack_fd, p->pack_name);
  
        hdr.hdr_signature = htonl(PACK_SIGNATURE);
@@@ -1788,11 -1794,7 +1787,11 @@@ static void read_marks(void
  {
        char line[512];
        FILE *f = fopen(import_marks_file, "r");
 -      if (!f)
 +      if (f)
 +              ;
 +      else if (import_marks_file_ignore_missing && errno == ENOENT)
 +              return; /* Marks file does not exist */
 +      else
                die_errno("cannot read '%s'", import_marks_file);
        while (fgets(line, sizeof(line), f)) {
                uintmax_t mark;
@@@ -2606,8 -2608,6 +2605,8 @@@ static void parse_new_commit(void
                        note_change_n(b, prev_fanout);
                else if (!strcmp("deleteall", command_buf.buf))
                        file_change_deleteall(b);
 +              else if (!prefixcmp(command_buf.buf, "ls "))
 +                      parse_ls(b);
                else {
                        unread_command_buf = 1;
                        break;
@@@ -2831,153 -2831,6 +2830,153 @@@ static void parse_cat_blob(void
        cat_blob(oe, sha1);
  }
  
 +static struct object_entry *dereference(struct object_entry *oe,
 +                                      unsigned char sha1[20])
 +{
 +      unsigned long size;
 +      char *buf = NULL;
 +      if (!oe) {
 +              enum object_type type = sha1_object_info(sha1, NULL);
 +              if (type < 0)
 +                      die("object not found: %s", sha1_to_hex(sha1));
 +              /* cache it! */
 +              oe = insert_object(sha1);
 +              oe->type = type;
 +              oe->pack_id = MAX_PACK_ID;
 +              oe->idx.offset = 1;
 +      }
 +      switch (oe->type) {
 +      case OBJ_TREE:  /* easy case. */
 +              return oe;
 +      case OBJ_COMMIT:
 +      case OBJ_TAG:
 +              break;
 +      default:
 +              die("Not a treeish: %s", command_buf.buf);
 +      }
 +
 +      if (oe->pack_id != MAX_PACK_ID) {       /* in a pack being written */
 +              buf = gfi_unpack_entry(oe, &size);
 +      } else {
 +              enum object_type unused;
 +              buf = read_sha1_file(sha1, &unused, &size);
 +      }
 +      if (!buf)
 +              die("Can't load object %s", sha1_to_hex(sha1));
 +
 +      /* Peel one layer. */
 +      switch (oe->type) {
 +      case OBJ_TAG:
 +              if (size < 40 + strlen("object ") ||
 +                  get_sha1_hex(buf + strlen("object "), sha1))
 +                      die("Invalid SHA1 in tag: %s", command_buf.buf);
 +              break;
 +      case OBJ_COMMIT:
 +              if (size < 40 + strlen("tree ") ||
 +                  get_sha1_hex(buf + strlen("tree "), sha1))
 +                      die("Invalid SHA1 in commit: %s", command_buf.buf);
 +      }
 +
 +      free(buf);
 +      return find_object(sha1);
 +}
 +
 +static struct object_entry *parse_treeish_dataref(const char **p)
 +{
 +      unsigned char sha1[20];
 +      struct object_entry *e;
 +
 +      if (**p == ':') {       /* <mark> */
 +              char *endptr;
 +              e = find_mark(strtoumax(*p + 1, &endptr, 10));
 +              if (endptr == *p + 1)
 +                      die("Invalid mark: %s", command_buf.buf);
 +              if (!e)
 +                      die("Unknown mark: %s", command_buf.buf);
 +              *p = endptr;
 +              hashcpy(sha1, e->idx.sha1);
 +      } else {        /* <sha1> */
 +              if (get_sha1_hex(*p, sha1))
 +                      die("Invalid SHA1: %s", command_buf.buf);
 +              e = find_object(sha1);
 +              *p += 40;
 +      }
 +
 +      while (!e || e->type != OBJ_TREE)
 +              e = dereference(e, sha1);
 +      return e;
 +}
 +
 +static void print_ls(int mode, const unsigned char *sha1, const char *path)
 +{
 +      static struct strbuf line = STRBUF_INIT;
 +
 +      /* See show_tree(). */
 +      const char *type =
 +              S_ISGITLINK(mode) ? commit_type :
 +              S_ISDIR(mode) ? tree_type :
 +              blob_type;
 +
 +      if (!mode) {
 +              /* missing SP path LF */
 +              strbuf_reset(&line);
 +              strbuf_addstr(&line, "missing ");
 +              quote_c_style(path, &line, NULL, 0);
 +              strbuf_addch(&line, '\n');
 +      } else {
 +              /* mode SP type SP object_name TAB path LF */
 +              strbuf_reset(&line);
 +              strbuf_addf(&line, "%06o %s %s\t",
 +                              mode, type, sha1_to_hex(sha1));
 +              quote_c_style(path, &line, NULL, 0);
 +              strbuf_addch(&line, '\n');
 +      }
 +      cat_blob_write(line.buf, line.len);
 +}
 +
 +static void parse_ls(struct branch *b)
 +{
 +      const char *p;
 +      struct tree_entry *root = NULL;
 +      struct tree_entry leaf = {NULL};
 +
 +      /* ls SP (<treeish> SP)? <path> */
 +      p = command_buf.buf + strlen("ls ");
 +      if (*p == '"') {
 +              if (!b)
 +                      die("Not in a commit: %s", command_buf.buf);
 +              root = &b->branch_tree;
 +      } else {
 +              struct object_entry *e = parse_treeish_dataref(&p);
 +              root = new_tree_entry();
 +              hashcpy(root->versions[1].sha1, e->idx.sha1);
 +              load_tree(root);
 +              if (*p++ != ' ')
 +                      die("Missing space after tree-ish: %s", command_buf.buf);
 +      }
 +      if (*p == '"') {
 +              static struct strbuf uq = STRBUF_INIT;
 +              const char *endp;
 +              strbuf_reset(&uq);
 +              if (unquote_c_style(&uq, p, &endp))
 +                      die("Invalid path: %s", command_buf.buf);
 +              if (*endp)
 +                      die("Garbage after path in: %s", command_buf.buf);
 +              p = uq.buf;
 +      }
 +      tree_content_get(root, p, &leaf);
 +      /*
 +       * A directory in preparation would have a sha1 of zero
 +       * until it is saved.  Save, for simplicity.
 +       */
 +      if (S_ISDIR(leaf.versions[1].mode))
 +              store_tree(&leaf);
 +
 +      print_ls(leaf.versions[1].mode, leaf.versions[1].sha1, p);
 +      if (!b || root != &b->branch_tree)
 +              release_tree_entry(root);
 +}
 +
  static void checkpoint(void)
  {
        checkpoint_requested = 0;
@@@ -3013,8 -2866,7 +3012,8 @@@ static char* make_fast_import_path(cons
        return strbuf_detach(&abs_path, NULL);
  }
  
 -static void option_import_marks(const char *marks, int from_stream)
 +static void option_import_marks(const char *marks,
 +                                      int from_stream, int ignore_missing)
  {
        if (import_marks_file) {
                if (from_stream)
        import_marks_file = make_fast_import_path(marks);
        safe_create_leading_directories_const(import_marks_file);
        import_marks_file_from_stream = from_stream;
 +      import_marks_file_ignore_missing = ignore_missing;
  }
  
  static void option_date_format(const char *fmt)
@@@ -3128,10 -2979,7 +3127,10 @@@ static int parse_one_feature(const cha
        if (!prefixcmp(feature, "date-format=")) {
                option_date_format(feature + 12);
        } else if (!prefixcmp(feature, "import-marks=")) {
 -              option_import_marks(feature + 13, from_stream);
 +              option_import_marks(feature + 13, from_stream, 0);
 +      } else if (!prefixcmp(feature, "import-marks-if-exists=")) {
 +              option_import_marks(feature + strlen("import-marks-if-exists="),
 +                                      from_stream, 1);
        } else if (!prefixcmp(feature, "export-marks=")) {
                option_export_marks(feature + 13);
        } else if (!strcmp(feature, "cat-blob")) {
                relative_marks_paths = 0;
        } else if (!prefixcmp(feature, "force")) {
                force_update = 1;
 +      } else if (!strcmp(feature, "notes") || !strcmp(feature, "ls")) {
 +              ; /* do nothing; we have the feature */
        } else {
                return 0;
        }
@@@ -3206,10 -3052,6 +3205,6 @@@ static int git_pack_config(const char *
                max_packsize = git_config_ulong(k, v);
                return 0;
        }
-       if (!strcmp(k, "core.bigfilethreshold")) {
-               long n = git_config_int(k, v);
-               big_file_threshold = 0 < n ? n : 0;
-       }
        return git_default_config(k, v, cb);
  }
  
@@@ -3283,8 -3125,6 +3278,8 @@@ int main(int argc, const char **argv
        while (read_next_command() != EOF) {
                if (!strcmp("blob", command_buf.buf))
                        parse_new_blob();
 +              else if (!prefixcmp(command_buf.buf, "ls "))
 +                      parse_ls(NULL);
                else if (!prefixcmp(command_buf.buf, "commit "))
                        parse_new_commit();
                else if (!prefixcmp(command_buf.buf, "tag "))