Merge branch 'jk/repository-extension' into maint
authorJunio C Hamano <gitster@pobox.com>
Tue, 3 Nov 2015 23:32:25 +0000 (15:32 -0800)
committerJunio C Hamano <gitster@pobox.com>
Tue, 3 Nov 2015 23:32:25 +0000 (15:32 -0800)
Prepare for Git on-disk repository representation to undergo
backward incompatible changes by introducing a new repository
format version "1", with an extension mechanism.

* jk/repository-extension:
introduce "preciousObjects" repository extension
introduce "extensions" form of core.repositoryformatversion

1  2 
builtin/gc.c
builtin/prune.c
builtin/repack.c
cache.h
environment.c
setup.c
diff --combined builtin/gc.c
index 0ad8d30b56f89a9ea6ac3a9ee5ae4593ae9754a0,8b8dc6b6100814e5e3169b4befaecea366bc92ad..b757d9ae4fdff0d0834cc1c94f9c3595541d54c1
@@@ -11,7 -11,6 +11,7 @@@
   */
  
  #include "builtin.h"
 +#include "tempfile.h"
  #include "lockfile.h"
  #include "parse-options.h"
  #include "run-command.h"
@@@ -43,7 -42,20 +43,7 @@@ static struct argv_array prune = ARGV_A
  static struct argv_array prune_worktrees = ARGV_ARRAY_INIT;
  static struct argv_array rerere = ARGV_ARRAY_INIT;
  
 -static char *pidfile;
 -
 -static void remove_pidfile(void)
 -{
 -      if (pidfile)
 -              unlink(pidfile);
 -}
 -
 -static void remove_pidfile_on_signal(int signo)
 -{
 -      remove_pidfile();
 -      sigchain_pop(signo);
 -      raise(signo);
 -}
 +static struct tempfile pidfile;
  
  static void git_config_date_string(const char *key, const char **output)
  {
@@@ -73,7 -85,7 +73,7 @@@ static void gc_config(void
        git_config_get_int("gc.autopacklimit", &gc_auto_pack_limit);
        git_config_get_bool("gc.autodetach", &detach_auto);
        git_config_date_string("gc.pruneexpire", &prune_expire);
 -      git_config_date_string("gc.pruneworktreesexpire", &prune_worktrees_expire);
 +      git_config_date_string("gc.worktreepruneexpire", &prune_worktrees_expire);
        git_config(git_default_config, NULL);
  }
  
@@@ -187,22 -199,20 +187,22 @@@ static const char *lock_repo_for_gc(in
        uintmax_t pid;
        FILE *fp;
        int fd;
 +      char *pidfile_path;
  
 -      if (pidfile)
 +      if (is_tempfile_active(&pidfile))
                /* already locked */
                return NULL;
  
        if (gethostname(my_host, sizeof(my_host)))
                strcpy(my_host, "unknown");
  
 -      fd = hold_lock_file_for_update(&lock, git_path("gc.pid"),
 +      pidfile_path = git_pathdup("gc.pid");
 +      fd = hold_lock_file_for_update(&lock, pidfile_path,
                                       LOCK_DIE_ON_ERROR);
        if (!force) {
                static char locking_host[128];
                int should_exit;
 -              fp = fopen(git_path("gc.pid"), "r");
 +              fp = fopen(pidfile_path, "r");
                memset(locking_host, 0, sizeof(locking_host));
                should_exit =
                        fp != NULL &&
                        if (fd >= 0)
                                rollback_lock_file(&lock);
                        *ret_pid = pid;
 +                      free(pidfile_path);
                        return locking_host;
                }
        }
        write_in_full(fd, sb.buf, sb.len);
        strbuf_release(&sb);
        commit_lock_file(&lock);
 -
 -      pidfile = git_pathdup("gc.pid");
 -      sigchain_push_common(remove_pidfile_on_signal);
 -      atexit(remove_pidfile);
 -
 +      register_tempfile(&pidfile, pidfile_path);
 +      free(pidfile_path);
        return NULL;
  }
  
@@@ -281,7 -293,7 +281,7 @@@ int cmd_gc(int argc, const char **argv
        argv_array_pushl(&reflog, "reflog", "expire", "--all", NULL);
        argv_array_pushl(&repack, "repack", "-d", "-l", NULL);
        argv_array_pushl(&prune, "prune", "--expire", NULL);
 -      argv_array_pushl(&prune_worktrees, "prune", "--worktrees", "--expire", NULL);
 +      argv_array_pushl(&prune_worktrees, "worktree", "prune", "--expire", NULL);
        argv_array_pushl(&rerere, "rerere", "gc", NULL);
  
        gc_config();
        if (gc_before_repack())
                return -1;
  
-       if (run_command_v_opt(repack.argv, RUN_GIT_CMD))
-               return error(FAILED_RUN, repack.argv[0]);
-       if (prune_expire) {
-               argv_array_push(&prune, prune_expire);
-               if (quiet)
-                       argv_array_push(&prune, "--no-progress");
-               if (run_command_v_opt(prune.argv, RUN_GIT_CMD))
-                       return error(FAILED_RUN, prune.argv[0]);
+       if (!repository_format_precious_objects) {
+               if (run_command_v_opt(repack.argv, RUN_GIT_CMD))
+                       return error(FAILED_RUN, repack.argv[0]);
+               if (prune_expire) {
+                       argv_array_push(&prune, prune_expire);
+                       if (quiet)
+                               argv_array_push(&prune, "--no-progress");
+                       if (run_command_v_opt(prune.argv, RUN_GIT_CMD))
+                               return error(FAILED_RUN, prune.argv[0]);
+               }
        }
  
        if (prune_worktrees_expire) {
diff --combined builtin/prune.c
index 10b03d3e4cb5ced78118251ac390dd6a33f9a71b,6a58e75108c2f5dd9d6777fee92f6d948c89a13f..8f4f0522856b988a8798fd65c6d81d8826709aa2
@@@ -6,6 -6,7 +6,6 @@@
  #include "reachable.h"
  #include "parse-options.h"
  #include "progress.h"
 -#include "dir.h"
  
  static const char * const prune_usage[] = {
        N_("git prune [-n] [-v] [--expire <time>] [--] [<head>...]"),
@@@ -75,6 -76,95 +75,6 @@@ static int prune_subdir(int nr, const c
        return 0;
  }
  
 -static int prune_worktree(const char *id, struct strbuf *reason)
 -{
 -      struct stat st;
 -      char *path;
 -      int fd, len;
 -
 -      if (!is_directory(git_path("worktrees/%s", id))) {
 -              strbuf_addf(reason, _("Removing worktrees/%s: not a valid directory"), id);
 -              return 1;
 -      }
 -      if (file_exists(git_path("worktrees/%s/locked", id)))
 -              return 0;
 -      if (stat(git_path("worktrees/%s/gitdir", id), &st)) {
 -              strbuf_addf(reason, _("Removing worktrees/%s: gitdir file does not exist"), id);
 -              return 1;
 -      }
 -      fd = open(git_path("worktrees/%s/gitdir", id), O_RDONLY);
 -      if (fd < 0) {
 -              strbuf_addf(reason, _("Removing worktrees/%s: unable to read gitdir file (%s)"),
 -                          id, strerror(errno));
 -              return 1;
 -      }
 -      len = st.st_size;
 -      path = xmalloc(len + 1);
 -      read_in_full(fd, path, len);
 -      close(fd);
 -      while (len && (path[len - 1] == '\n' || path[len - 1] == '\r'))
 -              len--;
 -      if (!len) {
 -              strbuf_addf(reason, _("Removing worktrees/%s: invalid gitdir file"), id);
 -              free(path);
 -              return 1;
 -      }
 -      path[len] = '\0';
 -      if (!file_exists(path)) {
 -              struct stat st_link;
 -              free(path);
 -              /*
 -               * the repo is moved manually and has not been
 -               * accessed since?
 -               */
 -              if (!stat(git_path("worktrees/%s/link", id), &st_link) &&
 -                  st_link.st_nlink > 1)
 -                      return 0;
 -              if (st.st_mtime <= expire) {
 -                      strbuf_addf(reason, _("Removing worktrees/%s: gitdir file points to non-existent location"), id);
 -                      return 1;
 -              } else {
 -                      return 0;
 -              }
 -      }
 -      free(path);
 -      return 0;
 -}
 -
 -static void prune_worktrees(void)
 -{
 -      struct strbuf reason = STRBUF_INIT;
 -      struct strbuf path = STRBUF_INIT;
 -      DIR *dir = opendir(git_path("worktrees"));
 -      struct dirent *d;
 -      int ret;
 -      if (!dir)
 -              return;
 -      while ((d = readdir(dir)) != NULL) {
 -              if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, ".."))
 -                      continue;
 -              strbuf_reset(&reason);
 -              if (!prune_worktree(d->d_name, &reason))
 -                      continue;
 -              if (show_only || verbose)
 -                      printf("%s\n", reason.buf);
 -              if (show_only)
 -                      continue;
 -              strbuf_reset(&path);
 -              strbuf_addstr(&path, git_path("worktrees/%s", d->d_name));
 -              ret = remove_dir_recursively(&path, 0);
 -              if (ret < 0 && errno == ENOTDIR)
 -                      ret = unlink(path.buf);
 -              if (ret)
 -                      error(_("failed to remove: %s"), strerror(errno));
 -      }
 -      closedir(dir);
 -      if (!show_only)
 -              rmdir(git_path("worktrees"));
 -      strbuf_release(&reason);
 -      strbuf_release(&path);
 -}
 -
  /*
   * Write errors (particularly out of space) can result in
   * failed temporary packs (and more rarely indexes and other
@@@ -101,10 -191,12 +101,10 @@@ int cmd_prune(int argc, const char **ar
  {
        struct rev_info revs;
        struct progress *progress = NULL;
 -      int do_prune_worktrees = 0;
        const struct option options[] = {
                OPT__DRY_RUN(&show_only, N_("do not remove, show only")),
                OPT__VERBOSE(&verbose, N_("report pruned objects")),
                OPT_BOOL(0, "progress", &show_progress, N_("show progress")),
 -              OPT_BOOL(0, "worktrees", &do_prune_worktrees, N_("prune .git/worktrees")),
                OPT_EXPIRY_DATE(0, "expire", &expire,
                                N_("expire objects older than <time>")),
                OPT_END()
  
        argc = parse_options(argc, argv, prefix, options, prune_usage, 0);
  
 -      if (do_prune_worktrees) {
 -              if (argc)
 -                      die(_("--worktrees does not take extra arguments"));
 -              prune_worktrees();
 -              return 0;
 -      }
 -
+       if (repository_format_precious_objects)
+               die(_("cannot prune in a precious-objects repo"));
        while (argc--) {
                unsigned char sha1[20];
                const char *name = *argv++;
diff --combined builtin/repack.c
index 70b9b1eaf17f5447021f7174f31e5c19d9754486,3beda2c65ab15f7ca5b6b2f6637249234a4e6c41..945611006a4ddcad1b834d87fe62698549ebb837
@@@ -193,6 -193,9 +193,9 @@@ int cmd_repack(int argc, const char **a
        argc = parse_options(argc, argv, prefix, builtin_repack_options,
                                git_repack_usage, 0);
  
+       if (delete_redundant && repository_format_precious_objects)
+               die(_("cannot delete packs in a precious-objects repo"));
        if (pack_kept_objects < 0)
                pack_kept_objects = write_bitmaps;
  
        failed = 0;
        for_each_string_list_item(item, &names) {
                for (ext = 0; ext < ARRAY_SIZE(exts); ext++) {
 -                      const char *fname_old;
 -                      char *fname;
 +                      char *fname, *fname_old;
                        fname = mkpathdup("%s/pack-%s%s", packdir,
                                                item->string, exts[ext].name);
                        if (!file_exists(fname)) {
                                continue;
                        }
  
 -                      fname_old = mkpath("%s/old-%s%s", packdir,
 +                      fname_old = mkpathdup("%s/old-%s%s", packdir,
                                                item->string, exts[ext].name);
                        if (file_exists(fname_old))
                                if (unlink(fname_old))
  
                        if (!failed && rename(fname, fname_old)) {
                                free(fname);
 +                              free(fname_old);
                                failed = 1;
                                break;
                        } else {
                                string_list_append(&rollback, fname);
 +                              free(fname_old);
                        }
                }
                if (failed)
        if (failed) {
                struct string_list rollback_failure = STRING_LIST_INIT_DUP;
                for_each_string_list_item(item, &rollback) {
 -                      const char *fname_old;
 -                      char *fname;
 +                      char *fname, *fname_old;
                        fname = mkpathdup("%s/%s", packdir, item->string);
 -                      fname_old = mkpath("%s/old-%s", packdir, item->string);
 +                      fname_old = mkpathdup("%s/old-%s", packdir, item->string);
                        if (rename(fname_old, fname))
                                string_list_append(&rollback_failure, fname);
                        free(fname);
 +                      free(fname_old);
                }
  
                if (rollback_failure.nr) {
        /* Remove the "old-" files */
        for_each_string_list_item(item, &names) {
                for (ext = 0; ext < ARRAY_SIZE(exts); ext++) {
 -                      const char *fname;
 -                      fname = mkpath("%s/old-%s%s",
 -                                      packdir,
 -                                      item->string,
 -                                      exts[ext].name);
 +                      char *fname;
 +                      fname = mkpathdup("%s/old-%s%s",
 +                                        packdir,
 +                                        item->string,
 +                                        exts[ext].name);
                        if (remove_path(fname))
                                warning(_("removing '%s' failed"), fname);
 +                      free(fname);
                }
        }
  
diff --combined cache.h
index 79066e57dc806d118366d9807e0af338b72e2fb2,b1bc40105507089836af9efba108fd3ca80eb0e3..d801429e0c34e085719b442ebc114f3fcb519f80
+++ 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,17 -446,7 +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);
  
@@@ -596,6 -585,8 +596,6 @@@ extern void update_index_if_able(struc
  extern int hold_locked_index(struct lock_file *, int);
  extern void set_alternate_index_output(const char *);
  
 -extern int delete_ref(const char *, const unsigned char *sha1, unsigned int flags);
 -
  /* Environment bits from configuration mechanism */
  extern int trust_executable_bit;
  extern int trust_ctime;
@@@ -631,7 -622,6 +631,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;
@@@ -696,8 -686,15 +696,15 @@@ extern char *notes_ref_name
  
  extern int grafts_replace_parents;
  
+ /*
+  * GIT_REPO_VERSION is the version we write by default. The
+  * _READ variant is the highest number we know how to
+  * handle.
+  */
  #define GIT_REPO_VERSION 0
+ #define GIT_REPO_VERSION_READ 1
  extern int repository_format_version;
+ extern int repository_format_precious_objects;
  extern int check_repository_format(void);
  
  #define MTIME_CHANGED 0x0001
  #define DATA_CHANGED    0x0020
  #define TYPE_CHANGED    0x0040
  
 +/*
 + * Return a statically allocated filename, either generically (mkpath), in
 + * the repository directory (git_path), or in a submodule's repository
 + * directory (git_path_submodule). In all cases, note that the result
 + * may be overwritten by another call to _any_ of the functions. Consider
 + * using the safer "dup" or "strbuf" formats below (in some cases, the
 + * unsafe versions have already been removed).
 + */
 +extern const char *mkpath(const char *fmt, ...) __attribute__((format (printf, 1, 2)));
 +extern const char *git_path(const char *fmt, ...) __attribute__((format (printf, 1, 2)));
 +
  extern char *mksnpath(char *buf, size_t n, const char *fmt, ...)
        __attribute__((format (printf, 3, 4)));
  extern void strbuf_git_path(struct strbuf *sb, const char *fmt, ...)
        __attribute__((format (printf, 2, 3)));
 +extern void strbuf_git_path_submodule(struct strbuf *sb, const char *path,
 +                                    const char *fmt, ...)
 +      __attribute__((format (printf, 3, 4)));
  extern char *git_pathdup(const char *fmt, ...)
        __attribute__((format (printf, 1, 2)));
  extern char *mkpathdup(const char *fmt, ...)
        __attribute__((format (printf, 1, 2)));
 -
 -/* Return a statically allocated filename matching the sha1 signature */
 -extern const char *mkpath(const char *fmt, ...) __attribute__((format (printf, 1, 2)));
 -extern const char *git_path(const char *fmt, ...) __attribute__((format (printf, 1, 2)));
 -extern const char *git_path_submodule(const char *path, const char *fmt, ...)
 +extern char *git_pathdup_submodule(const char *path, const char *fmt, ...)
        __attribute__((format (printf, 2, 3)));
 +
  extern void report_linked_checkout_garbage(void);
  
 +/*
 + * You can define a static memoized git path like:
 + *
 + *    static GIT_PATH_FUNC(git_path_foo, "FOO");
 + *
 + * or use one of the global ones below.
 + */
 +#define GIT_PATH_FUNC(func, filename) \
 +      const char *func(void) \
 +      { \
 +              static char *ret; \
 +              if (!ret) \
 +                      ret = git_pathdup(filename); \
 +              return ret; \
 +      }
 +
 +const char *git_path_cherry_pick_head(void);
 +const char *git_path_revert_head(void);
 +const char *git_path_squash_msg(void);
 +const char *git_path_merge_msg(void);
 +const char *git_path_merge_rr(void);
 +const char *git_path_merge_mode(void);
 +const char *git_path_merge_head(void);
 +const char *git_path_fetch_head(void);
 +const char *git_path_shallow(void);
 +
  /*
   * Return the name of the file in the local object database that would
   * be used to store a loose object with the specified sha1.  The
@@@ -982,7 -942,7 +989,7 @@@ extern int do_check_packed_object_crc
  
  extern int check_sha1_signature(const unsigned char *sha1, void *buf, unsigned long size, const char *type);
  
 -extern int move_temp_to_file(const char *tmpfile, const char *filename);
 +extern int finalize_object_file(const char *tmpfile, const char *filename);
  
  extern int has_sha1_pack(const unsigned char *sha1);
  
@@@ -1067,10 -1027,76 +1074,10 @@@ extern int get_oid_hex(const char *hex
  
  extern char *sha1_to_hex(const unsigned char *sha1);  /* static buffer result! */
  extern char *oid_to_hex(const struct object_id *oid); /* same static buffer as sha1_to_hex */
 -extern int read_ref_full(const char *refname, int resolve_flags,
 -                       unsigned char *sha1, int *flags);
 -extern int read_ref(const char *refname, unsigned char *sha1);
  
 -/*
 - * Resolve a reference, recursively following symbolic refererences.
 - *
 - * Store the referred-to object's name in sha1 and return the name of
 - * the non-symbolic reference that ultimately pointed at it.  The
 - * return value, if not NULL, is a pointer into either a static buffer
 - * or the input ref.
 - *
 - * If the reference cannot be resolved to an object, the behavior
 - * depends on the RESOLVE_REF_READING flag:
 - *
 - * - If RESOLVE_REF_READING is set, return NULL.
 - *
 - * - If RESOLVE_REF_READING is not set, clear sha1 and return the name of
 - *   the last reference name in the chain, which will either be a non-symbolic
 - *   reference or an undefined reference.  If this is a prelude to
 - *   "writing" to the ref, the return value is the name of the ref
 - *   that will actually be created or changed.
 - *
 - * If the RESOLVE_REF_NO_RECURSE flag is passed, only resolves one
 - * level of symbolic reference.  The value stored in sha1 for a symbolic
 - * reference will always be null_sha1 in this case, and the return
 - * value is the reference that the symref refers to directly.
 - *
 - * If flags is non-NULL, set the value that it points to the
 - * combination of REF_ISPACKED (if the reference was found among the
 - * packed references), REF_ISSYMREF (if the initial reference was a
 - * symbolic reference), REF_BAD_NAME (if the reference name is ill
 - * formed --- see RESOLVE_REF_ALLOW_BAD_NAME below), and REF_ISBROKEN
 - * (if the ref is malformed or has a bad name). See refs.h for more detail
 - * on each flag.
 - *
 - * If ref is not a properly-formatted, normalized reference, return
 - * NULL.  If more than MAXDEPTH recursive symbolic lookups are needed,
 - * give up and return NULL.
 - *
 - * RESOLVE_REF_ALLOW_BAD_NAME allows resolving refs even when their
 - * name is invalid according to git-check-ref-format(1).  If the name
 - * is bad then the value stored in sha1 will be null_sha1 and the two
 - * flags REF_ISBROKEN and REF_BAD_NAME will be set.
 - *
 - * Even with RESOLVE_REF_ALLOW_BAD_NAME, names that escape the refs/
 - * directory and do not consist of all caps and underscores cannot be
 - * resolved. The function returns NULL for such ref names.
 - * Caps and underscores refers to the special refs, such as HEAD,
 - * FETCH_HEAD and friends, that all live outside of the refs/ directory.
 - */
 -#define RESOLVE_REF_READING 0x01
 -#define RESOLVE_REF_NO_RECURSE 0x02
 -#define RESOLVE_REF_ALLOW_BAD_NAME 0x04
 -extern const char *resolve_ref_unsafe(const char *ref, int resolve_flags, unsigned char *sha1, int *flags);
 -extern char *resolve_refdup(const char *ref, int resolve_flags, unsigned char *sha1, int *flags);
 -
 -extern int dwim_ref(const char *str, int len, unsigned char *sha1, char **ref);
 -extern int dwim_log(const char *str, int len, unsigned char *sha1, char **ref);
  extern int interpret_branch_name(const char *str, int len, struct strbuf *);
  extern int get_sha1_mb(const char *str, unsigned char *sha1);
  
 -/*
 - * Return true iff abbrev_name is a possible abbreviation for
 - * full_name according to the rules defined by ref_rev_parse_rules in
 - * refs.c.
 - */
 -extern int refname_match(const char *abbrev_name, const char *full_name);
 -
 -extern int create_symref(const char *ref, const char *refs_heads_master, const char *logmsg);
  extern int validate_headref(const char *ref);
  
  extern int base_name_compare(const char *name1, int len1, int mode1, const char *name2, int len2, int mode2);
@@@ -1086,30 -1112,18 +1093,30 @@@ extern void *read_object_with_reference
  extern struct object *peel_to_type(const char *name, int namelen,
                                   struct object *o, enum object_type);
  
 -enum date_mode {
 -      DATE_NORMAL = 0,
 -      DATE_RELATIVE,
 -      DATE_SHORT,
 -      DATE_LOCAL,
 -      DATE_ISO8601,
 -      DATE_ISO8601_STRICT,
 -      DATE_RFC2822,
 -      DATE_RAW
 +struct date_mode {
 +      enum date_mode_type {
 +              DATE_NORMAL = 0,
 +              DATE_RELATIVE,
 +              DATE_SHORT,
 +              DATE_LOCAL,
 +              DATE_ISO8601,
 +              DATE_ISO8601_STRICT,
 +              DATE_RFC2822,
 +              DATE_STRFTIME,
 +              DATE_RAW
 +      } type;
 +      const char *strftime_fmt;
  };
  
 -const char *show_date(unsigned long time, int timezone, enum date_mode mode);
 +/*
 + * Convenience helper for passing a constant type, like:
 + *
 + *   show_date(t, tz, DATE_MODE(NORMAL));
 + */
 +#define DATE_MODE(t) date_mode_from_type(DATE_##t)
 +struct date_mode *date_mode_from_type(enum date_mode_type type);
 +
 +const char *show_date(unsigned long time, int timezone, const struct 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, struct strbuf *out);
@@@ -1119,7 -1133,7 +1126,7 @@@ 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);
 -enum date_mode parse_date_format(const char *format);
 +void parse_date_format(const char *format, struct date_mode *mode);
  int date_overflows(unsigned long date);
  
  #define IDENT_STRICT         1
@@@ -1156,8 -1170,7 +1163,8 @@@ extern int split_ident_line(struct iden
   * the ident_split. It will also sanity-check the values and produce
   * a well-known sentinel date if they appear bogus.
   */
 -const char *show_ident_date(const struct ident_split *id, enum date_mode mode);
 +const char *show_ident_date(const struct ident_split *id,
 +                          const struct date_mode *mode);
  
  /*
   * Compare split idents for equality or strict ordering. Note that we
@@@ -1429,7 -1442,6 +1436,7 @@@ extern int git_config_with_options(conf
                                   int respect_includes);
  extern int git_config_early(config_fn_t fn, void *, const char *repo_config);
  extern int git_parse_ulong(const char *, unsigned long *);
 +extern int git_parse_maybe_bool(const char *);
  extern int git_config_int(const char *, const char *);
  extern int64_t git_config_int64(const char *, const char *);
  extern unsigned long git_config_ulong(const char *, const char *);
@@@ -1441,7 -1453,6 +1448,7 @@@ extern int git_config_pathname(const ch
  extern int git_config_set_in_file(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_key_is_valid(const char *key);
  extern int git_config_set_multivar(const char *, const char *, const char *, int);
  extern int git_config_set_multivar_in_file(const char *, const char *, const char *, const char *, int);
  extern int git_config_rename_section(const char *, const char *);
@@@ -1578,9 -1589,8 +1585,9 @@@ static inline ssize_t write_str_in_full
  {
        return write_in_full(fd, str, strlen(str));
  }
 -__attribute__((format (printf, 3, 4)))
 -extern int write_file(const char *path, int fatal, const char *fmt, ...);
 +
 +extern int write_file(const char *path, const char *fmt, ...);
 +extern int write_file_gently(const char *path, const char *fmt, ...);
  
  /* pager.c */
  extern void setup_pager(void);
diff --combined environment.c
index a533aed630c20a5e0718bcea4375875a44896416,da66e829d173142c3849d15306a9772b40aafff4..23a38e4b14f61f078bec3d7c2a5e5bcc5d505d9d
@@@ -26,6 -26,7 +26,7 @@@ int warn_ambiguous_refs = 1
  int warn_on_object_refname_ambiguity = 1;
  int ref_paranoia = -1;
  int repository_format_version;
+ int repository_format_precious_objects;
  const char *git_commit_encoding;
  const char *git_log_output_encoding;
  int shared_repository = PERM_UMASK;
@@@ -47,7 -48,6 +48,7 @@@ const char *askpass_program
  const char *excludes_file;
  enum auto_crlf auto_crlf = AUTO_CRLF_FALSE;
  int check_replace_refs = 1;
 +char *git_replace_ref_base;
  enum eol core_eol = EOL_UNSET;
  enum safe_crlf safe_crlf = SAFE_CRLF_WARN;
  unsigned whitespace_rule_cfg = WS_DEFAULT_RULE;
@@@ -111,7 -111,6 +112,7 @@@ const char * const local_repo_env[] = 
        GRAFT_ENVIRONMENT,
        INDEX_ENVIRONMENT,
        NO_REPLACE_OBJECTS_ENVIRONMENT,
 +      GIT_REPLACE_REF_BASE_ENVIRONMENT,
        GIT_PREFIX_ENVIRONMENT,
        GIT_SHALLOW_FILE_ENVIRONMENT,
        GIT_COMMON_DIR_ENVIRONMENT,
@@@ -158,7 -157,6 +159,7 @@@ static void setup_git_env(void
        struct strbuf sb = STRBUF_INIT;
        const char *gitfile;
        const char *shallow_file;
 +      const char *replace_ref_base;
  
        git_dir = getenv(GIT_DIR_ENVIRONMENT);
        if (!git_dir)
                                           "info/grafts", &git_graft_env);
        if (getenv(NO_REPLACE_OBJECTS_ENVIRONMENT))
                check_replace_refs = 0;
 +      replace_ref_base = getenv(GIT_REPLACE_REF_BASE_ENVIRONMENT);
 +      git_replace_ref_base = xstrdup(replace_ref_base ? replace_ref_base
 +                                                        : "refs/replace/");
        namespace = expand_namespace(getenv(GIT_NAMESPACE_ENVIRONMENT));
        namespace_len = strlen(namespace);
        shallow_file = getenv(GIT_SHALLOW_FILE_ENVIRONMENT);
@@@ -237,8 -232,6 +238,8 @@@ void set_git_work_tree(const char *new_
        }
        git_work_tree_initialized = 1;
        work_tree = xstrdup(real_path(new_work_tree));
 +      if (setenv(GIT_WORK_TREE_ENVIRONMENT, work_tree, 1))
 +              die("could not set GIT_WORK_TREE to '%s'", work_tree);
  }
  
  const char *get_git_work_tree(void)
diff --combined setup.c
index a17c51e61d75ac8280bf04d95c50d7bdfd6d7a0e,8b8dca9fd21570c8109d17ccfb848ca0e32f65b2..3324014cb7ac825fa9703cc6568190460e9cd87d
+++ b/setup.c
@@@ -5,6 -5,7 +5,7 @@@
  static int inside_git_dir = -1;
  static int inside_work_tree = -1;
  static int work_tree_config_is_bogus;
+ static struct string_list unknown_extensions = STRING_LIST_INIT_DUP;
  
  /*
   * The input parameter must contain an absolute path, and it must already be
@@@ -352,10 -353,25 +353,25 @@@ void setup_work_tree(void
  
  static int check_repo_format(const char *var, const char *value, void *cb)
  {
+       const char *ext;
        if (strcmp(var, "core.repositoryformatversion") == 0)
                repository_format_version = git_config_int(var, value);
        else if (strcmp(var, "core.sharedrepository") == 0)
                shared_repository = git_config_perm(var, value);
+       else if (skip_prefix(var, "extensions.", &ext)) {
+               /*
+                * record any known extensions here; otherwise,
+                * we fall through to recording it as unknown, and
+                * check_repository_format will complain
+                */
+               if (!strcmp(ext, "noop"))
+                       ;
+               else if (!strcmp(ext, "preciousobjects"))
+                       repository_format_precious_objects = git_config_bool(var, value);
+               else
+                       string_list_append(&unknown_extensions, ext);
+       }
        return 0;
  }
  
@@@ -366,6 -382,8 +382,8 @@@ static int check_repository_format_gent
        config_fn_t fn;
        int ret = 0;
  
+       string_list_clear(&unknown_extensions, 0);
        if (get_common_dir(&sb, gitdir))
                fn = check_repo_format;
        else
         * is a good one.
         */
        git_config_early(fn, NULL, repo_config);
-       if (GIT_REPO_VERSION < repository_format_version) {
+       if (GIT_REPO_VERSION_READ < repository_format_version) {
                if (!nongit_ok)
                        die ("Expected git repo version <= %d, found %d",
-                            GIT_REPO_VERSION, repository_format_version);
+                            GIT_REPO_VERSION_READ, repository_format_version);
                warning("Expected git repo version <= %d, found %d",
-                       GIT_REPO_VERSION, repository_format_version);
+                       GIT_REPO_VERSION_READ, repository_format_version);
                warning("Please upgrade Git");
                *nongit_ok = -1;
                ret = -1;
        }
+       if (repository_format_version >= 1 && unknown_extensions.nr) {
+               int i;
+               if (!nongit_ok)
+                       die("unknown repository extension: %s",
+                           unknown_extensions.items[0].string);
+               for (i = 0; i < unknown_extensions.nr; i++)
+                       warning("unknown repository extension: %s",
+                               unknown_extensions.items[i].string);
+               *nongit_ok = -1;
+               ret = -1;
+       }
        strbuf_release(&sb);
        return ret;
  }
@@@ -402,67 -435,44 +435,67 @@@ static void update_linked_gitdir(const 
        struct strbuf path = STRBUF_INIT;
        struct stat st;
  
 -      strbuf_addf(&path, "%s/gitfile", gitdir);
 +      strbuf_addf(&path, "%s/gitdir", gitdir);
        if (stat(path.buf, &st) || st.st_mtime + 24 * 3600 < time(NULL))
 -              write_file(path.buf, 0, "%s\n", gitfile);
 +              write_file(path.buf, "%s", gitfile);
        strbuf_release(&path);
  }
  
  /*
   * 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,