Merge branch 'jk/relative-directory-fix'
authorJunio C Hamano <gitster@pobox.com>
Wed, 25 Apr 2018 04:28:52 +0000 (13:28 +0900)
committerJunio C Hamano <gitster@pobox.com>
Wed, 25 Apr 2018 04:28:52 +0000 (13:28 +0900)
Some codepaths, including the refs API, get and keep relative
paths, that go out of sync when the process does chdir(2). The
chdir-notify API is introduced to let these codepaths adjust these
cached paths to the new current directory.

* jk/relative-directory-fix:
refs: use chdir_notify to update cached relative paths
set_work_tree: use chdir_notify
add chdir-notify API
trace.c: export trace_setup_key
set_git_dir: die when setenv() fails

1  2 
Makefile
cache.h
environment.c
setup.c
t/t1501-work-tree.sh
diff --combined Makefile
index f181687250d1d7cf9e1d8b5013980751a34d527b,0da98b9274dfff98847a8812cb9344a46dd4acd6..2e198c41dcebc7efbae92678f4de63b228f0622b
+++ b/Makefile
@@@ -29,10 -29,10 +29,10 @@@ all:
  # Perl-compatible regular expressions instead of standard or extended
  # POSIX regular expressions.
  #
 -# Currently USE_LIBPCRE is a synonym for USE_LIBPCRE1, define
 -# USE_LIBPCRE2 instead if you'd like to use version 2 of the PCRE
 -# library. The USE_LIBPCRE flag will likely be changed to mean v2 by
 -# default in future releases.
 +# USE_LIBPCRE is a synonym for USE_LIBPCRE2, define USE_LIBPCRE1
 +# instead if you'd like to use the legacy version 1 of the PCRE
 +# library. Support for version 1 will likely be removed in some future
 +# release of Git, as upstream has all but abandoned it.
  #
  # When using USE_LIBPCRE1, define NO_LIBPCRE1_JIT if the PCRE v1
  # library is compiled without --enable-jit. We will auto-detect
  # when hardlinking a file to another name and unlinking the original file right
  # away (some NTFS drivers seem to zero the contents in that scenario).
  #
 +# Define INSTALL_SYMLINKS if you prefer to have everything that can be
 +# symlinked between bin/ and libexec/ to use relative symlinks between
 +# the two. This option overrides NO_CROSS_DIRECTORY_HARDLINKS and
 +# NO_INSTALL_HARDLINKS which will also use symlinking by indirection
 +# within the same directory in some cases, INSTALL_SYMLINKS will
 +# always symlink to the final target directly.
 +#
  # Define NO_CROSS_DIRECTORY_HARDLINKS if you plan to distribute the installed
  # programs as a tar, where bin/ and libexec/ might be on different file systems.
  #
@@@ -481,7 -474,8 +481,7 @@@ ARFLAGS = rc
  # This can help installing the suite in a relocatable way.
  
  prefix = $(HOME)
 -bindir_relative = bin
 -bindir = $(prefix)/$(bindir_relative)
 +bindir = $(prefix)/bin
  mandir = $(prefix)/share/man
  infodir = $(prefix)/share/info
  gitexecdir = libexec/git-core
@@@ -498,10 -492,8 +498,10 @@@ lib = li
  # DESTDIR =
  pathsep = :
  
 +bindir_relative = $(patsubst $(prefix)/%,%,$(bindir))
  mandir_relative = $(patsubst $(prefix)/%,%,$(mandir))
  infodir_relative = $(patsubst $(prefix)/%,%,$(infodir))
 +gitexecdir_relative = $(patsubst $(prefix)/%,%,$(gitexecdir))
  htmldir_relative = $(patsubst $(prefix)/%,%,$(htmldir))
  
  export prefix bindir sharedir sysconfdir gitwebdir perllibdir localedir
@@@ -554,7 -546,6 +554,7 @@@ SCRIPT_PERL 
  SCRIPT_PYTHON =
  SCRIPT_SH =
  SCRIPT_LIB =
 +TEST_BUILTINS_OBJS =
  TEST_PROGRAMS_NEED_X =
  
  # Having this variable in your environment would break pipelines because
@@@ -660,49 -651,47 +660,49 @@@ X 
  
  PROGRAMS += $(patsubst %.o,git-%$X,$(PROGRAM_OBJS))
  
 -TEST_PROGRAMS_NEED_X += test-chmtime
 -TEST_PROGRAMS_NEED_X += test-ctype
 -TEST_PROGRAMS_NEED_X += test-config
 -TEST_PROGRAMS_NEED_X += test-date
 -TEST_PROGRAMS_NEED_X += test-delta
 -TEST_PROGRAMS_NEED_X += test-drop-caches
 -TEST_PROGRAMS_NEED_X += test-dump-cache-tree
 +TEST_BUILTINS_OBJS += test-chmtime.o
 +TEST_BUILTINS_OBJS += test-config.o
 +TEST_BUILTINS_OBJS += test-ctype.o
 +TEST_BUILTINS_OBJS += test-date.o
 +TEST_BUILTINS_OBJS += test-delta.o
 +TEST_BUILTINS_OBJS += test-drop-caches.o
 +TEST_BUILTINS_OBJS += test-dump-cache-tree.o
 +TEST_BUILTINS_OBJS += test-dump-split-index.o
 +TEST_BUILTINS_OBJS += test-example-decorate.o
 +TEST_BUILTINS_OBJS += test-genrandom.o
 +TEST_BUILTINS_OBJS += test-hashmap.o
 +TEST_BUILTINS_OBJS += test-index-version.o
 +TEST_BUILTINS_OBJS += test-lazy-init-name-hash.o
 +TEST_BUILTINS_OBJS += test-match-trees.o
 +TEST_BUILTINS_OBJS += test-mergesort.o
 +TEST_BUILTINS_OBJS += test-mktemp.o
 +TEST_BUILTINS_OBJS += test-online-cpus.o
 +TEST_BUILTINS_OBJS += test-path-utils.o
 +TEST_BUILTINS_OBJS += test-prio-queue.o
 +TEST_BUILTINS_OBJS += test-read-cache.o
 +TEST_BUILTINS_OBJS += test-ref-store.o
 +TEST_BUILTINS_OBJS += test-regex.o
 +TEST_BUILTINS_OBJS += test-revision-walking.o
 +TEST_BUILTINS_OBJS += test-run-command.o
 +TEST_BUILTINS_OBJS += test-scrap-cache-tree.o
 +TEST_BUILTINS_OBJS += test-sha1-array.o
 +TEST_BUILTINS_OBJS += test-sha1.o
 +TEST_BUILTINS_OBJS += test-sigchain.o
 +TEST_BUILTINS_OBJS += test-strcmp-offset.o
 +TEST_BUILTINS_OBJS += test-string-list.o
 +TEST_BUILTINS_OBJS += test-submodule-config.o
 +TEST_BUILTINS_OBJS += test-subprocess.o
 +TEST_BUILTINS_OBJS += test-urlmatch-normalization.o
 +TEST_BUILTINS_OBJS += test-wildmatch.o
 +TEST_BUILTINS_OBJS += test-write-cache.o
 +
  TEST_PROGRAMS_NEED_X += test-dump-fsmonitor
 -TEST_PROGRAMS_NEED_X += test-dump-split-index
  TEST_PROGRAMS_NEED_X += test-dump-untracked-cache
 -TEST_PROGRAMS_NEED_X += test-example-decorate
  TEST_PROGRAMS_NEED_X += test-fake-ssh
 -TEST_PROGRAMS_NEED_X += test-genrandom
 -TEST_PROGRAMS_NEED_X += test-hashmap
 -TEST_PROGRAMS_NEED_X += test-index-version
 -TEST_PROGRAMS_NEED_X += test-lazy-init-name-hash
  TEST_PROGRAMS_NEED_X += test-line-buffer
 -TEST_PROGRAMS_NEED_X += test-match-trees
 -TEST_PROGRAMS_NEED_X += test-mergesort
 -TEST_PROGRAMS_NEED_X += test-mktemp
 -TEST_PROGRAMS_NEED_X += test-online-cpus
  TEST_PROGRAMS_NEED_X += test-parse-options
 -TEST_PROGRAMS_NEED_X += test-path-utils
 -TEST_PROGRAMS_NEED_X += test-prio-queue
 -TEST_PROGRAMS_NEED_X += test-read-cache
 -TEST_PROGRAMS_NEED_X += test-write-cache
 -TEST_PROGRAMS_NEED_X += test-ref-store
 -TEST_PROGRAMS_NEED_X += test-regex
 -TEST_PROGRAMS_NEED_X += test-revision-walking
 -TEST_PROGRAMS_NEED_X += test-run-command
 -TEST_PROGRAMS_NEED_X += test-scrap-cache-tree
 -TEST_PROGRAMS_NEED_X += test-sha1
 -TEST_PROGRAMS_NEED_X += test-sha1-array
 -TEST_PROGRAMS_NEED_X += test-sigchain
 -TEST_PROGRAMS_NEED_X += test-strcmp-offset
 -TEST_PROGRAMS_NEED_X += test-string-list
 -TEST_PROGRAMS_NEED_X += test-submodule-config
 -TEST_PROGRAMS_NEED_X += test-subprocess
  TEST_PROGRAMS_NEED_X += test-svn-fe
 -TEST_PROGRAMS_NEED_X += test-urlmatch-normalization
 -TEST_PROGRAMS_NEED_X += test-wildmatch
 +TEST_PROGRAMS_NEED_X += test-tool
  
  TEST_PROGRAMS = $(patsubst %,t/helper/%$X,$(TEST_PROGRAMS_NEED_X))
  
@@@ -783,6 -772,7 +783,7 @@@ LIB_OBJS += branch.
  LIB_OBJS += bulk-checkin.o
  LIB_OBJS += bundle.o
  LIB_OBJS += cache-tree.o
+ LIB_OBJS += chdir-notify.o
  LIB_OBJS += checkout.o
  LIB_OBJS += color.o
  LIB_OBJS += column.o
@@@ -1181,18 -1171,13 +1182,18 @@@ ifdef NO_LIBGEN_
        COMPAT_OBJS += compat/basename.o
  endif
  
 -USE_LIBPCRE1 ?= $(USE_LIBPCRE)
 +USE_LIBPCRE2 ?= $(USE_LIBPCRE)
  
 -ifneq (,$(USE_LIBPCRE1))
 -      ifdef USE_LIBPCRE2
 -$(error Only set USE_LIBPCRE1 (or its alias USE_LIBPCRE) or USE_LIBPCRE2, not both!)
 +ifneq (,$(USE_LIBPCRE2))
 +      ifdef USE_LIBPCRE1
 +$(error Only set USE_LIBPCRE2 (or its alias USE_LIBPCRE) or USE_LIBPCRE1, not both!)
        endif
  
 +      BASIC_CFLAGS += -DUSE_LIBPCRE2
 +      EXTLIBS += -lpcre2-8
 +endif
 +
 +ifdef USE_LIBPCRE1
        BASIC_CFLAGS += -DUSE_LIBPCRE1
        EXTLIBS += -lpcre
  
@@@ -1201,6 -1186,11 +1202,6 @@@ ifdef NO_LIBPCRE1_JI
  endif
  endif
  
 -ifdef USE_LIBPCRE2
 -      BASIC_CFLAGS += -DUSE_LIBPCRE2
 -      EXTLIBS += -lpcre2-8
 -endif
 -
  ifdef LIBPCREDIR
        BASIC_CFLAGS += -I$(LIBPCREDIR)/include
        EXTLIBS += -L$(LIBPCREDIR)/$(lib) $(CC_LD_DYNPATH)$(LIBPCREDIR)/$(lib)
@@@ -1752,7 -1742,6 +1753,7 @@@ infodir_relative_SQ = $(subst ','\'',$(
  perllibdir_SQ = $(subst ','\'',$(perllibdir))
  localedir_SQ = $(subst ','\'',$(localedir))
  gitexecdir_SQ = $(subst ','\'',$(gitexecdir))
 +gitexecdir_relative_SQ = $(subst ','\'',$(gitexecdir_relative))
  template_dir_SQ = $(subst ','\'',$(template_dir))
  htmldir_relative_SQ = $(subst ','\'',$(htmldir_relative))
  prefix_SQ = $(subst ','\'',$(prefix))
@@@ -2095,7 -2084,7 +2096,7 @@@ VCSSVN_OBJS += vcs-svn/fast_export.
  VCSSVN_OBJS += vcs-svn/svndiff.o
  VCSSVN_OBJS += vcs-svn/svndump.o
  
 -TEST_OBJS := $(patsubst %$X,%.o,$(TEST_PROGRAMS))
 +TEST_OBJS := $(patsubst %$X,%.o,$(TEST_PROGRAMS)) $(patsubst %,t/helper/%,$(TEST_BUILTINS_OBJS))
  OBJECTS := $(LIB_OBJS) $(BUILTIN_OBJS) $(PROGRAM_OBJS) $(TEST_OBJS) \
        $(XDIFF_OBJS) \
        $(VCSSVN_OBJS) \
@@@ -2506,12 -2495,10 +2507,12 @@@ t/helper/test-svn-fe$X: $(VCSSVN_LIB
  
  .PRECIOUS: $(TEST_OBJS)
  
 +t/helper/test-tool$X: $(patsubst %,t/helper/%,$(TEST_BUILTINS_OBJS))
 +
  t/helper/test-%$X: t/helper/test-%.o GIT-LDFLAGS $(GITLIBS)
        $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(filter %.a,$^) $(LIBS)
  
 -check-sha1:: t/helper/test-sha1$X
 +check-sha1:: t/helper/test-tool$X
        t/helper/test-sha1.sh
  
  SP_OBJ = $(patsubst %.o,%.sp,$(C_OBJ))
@@@ -2620,44 -2607,35 +2621,44 @@@ endi
  
        bindir=$$(cd '$(DESTDIR_SQ)$(bindir_SQ)' && pwd) && \
        execdir=$$(cd '$(DESTDIR_SQ)$(gitexec_instdir_SQ)' && pwd) && \
 +      destdir_from_execdir_SQ=$$(echo '$(gitexecdir_relative_SQ)' | sed -e 's|[^/][^/]*|..|g') && \
        { test "$$bindir/" = "$$execdir/" || \
          for p in git$X $(filter $(install_bindir_programs),$(ALL_PROGRAMS)); do \
                $(RM) "$$execdir/$$p" && \
 -              test -z "$(NO_INSTALL_HARDLINKS)$(NO_CROSS_DIRECTORY_HARDLINKS)" && \
 -              ln "$$bindir/$$p" "$$execdir/$$p" 2>/dev/null || \
 -              cp "$$bindir/$$p" "$$execdir/$$p" || exit; \
 +              test -n "$(INSTALL_SYMLINKS)" && \
 +              ln -s "$$destdir_from_execdir_SQ/$(bindir_relative_SQ)/$$p" "$$execdir/$$p" || \
 +              { test -z "$(NO_INSTALL_HARDLINKS)$(NO_CROSS_DIRECTORY_HARDLINKS)" && \
 +                ln "$$bindir/$$p" "$$execdir/$$p" 2>/dev/null || \
 +                cp "$$bindir/$$p" "$$execdir/$$p" || exit; } \
          done; \
        } && \
        for p in $(filter $(install_bindir_programs),$(BUILT_INS)); do \
                $(RM) "$$bindir/$$p" && \
 -              test -z "$(NO_INSTALL_HARDLINKS)" && \
 -              ln "$$bindir/git$X" "$$bindir/$$p" 2>/dev/null || \
 -              ln -s "git$X" "$$bindir/$$p" 2>/dev/null || \
 -              cp "$$bindir/git$X" "$$bindir/$$p" || exit; \
 +              test -n "$(INSTALL_SYMLINKS)" && \
 +              ln -s "git$X" "$$bindir/$$p" || \
 +              { test -z "$(NO_INSTALL_HARDLINKS)" && \
 +                ln "$$bindir/git$X" "$$bindir/$$p" 2>/dev/null || \
 +                ln -s "git$X" "$$bindir/$$p" 2>/dev/null || \
 +                cp "$$bindir/git$X" "$$bindir/$$p" || exit; } \
        done && \
        for p in $(BUILT_INS); do \
                $(RM) "$$execdir/$$p" && \
 -              test -z "$(NO_INSTALL_HARDLINKS)" && \
 -              ln "$$execdir/git$X" "$$execdir/$$p" 2>/dev/null || \
 -              ln -s "git$X" "$$execdir/$$p" 2>/dev/null || \
 -              cp "$$execdir/git$X" "$$execdir/$$p" || exit; \
 +              test -n "$(INSTALL_SYMLINKS)" && \
 +              ln -s "$$destdir_from_execdir_SQ/$(bindir_relative_SQ)/git$X" "$$execdir/$$p" || \
 +              { test -z "$(NO_INSTALL_HARDLINKS)" && \
 +                ln "$$execdir/git$X" "$$execdir/$$p" 2>/dev/null || \
 +                ln -s "git$X" "$$execdir/$$p" 2>/dev/null || \
 +                cp "$$execdir/git$X" "$$execdir/$$p" || exit; } \
        done && \
        remote_curl_aliases="$(REMOTE_CURL_ALIASES)" && \
        for p in $$remote_curl_aliases; do \
                $(RM) "$$execdir/$$p" && \
 -              test -z "$(NO_INSTALL_HARDLINKS)" && \
 -              ln "$$execdir/git-remote-http$X" "$$execdir/$$p" 2>/dev/null || \
 -              ln -s "git-remote-http$X" "$$execdir/$$p" 2>/dev/null || \
 -              cp "$$execdir/git-remote-http$X" "$$execdir/$$p" || exit; \
 +              test -n "$(INSTALL_SYMLINKS)" && \
 +              ln -s "git-remote-http$X" "$$execdir/$$p" || \
 +              { test -z "$(NO_INSTALL_HARDLINKS)" && \
 +                ln "$$execdir/git-remote-http$X" "$$execdir/$$p" 2>/dev/null || \
 +                ln -s "git-remote-http$X" "$$execdir/$$p" 2>/dev/null || \
 +                cp "$$execdir/git-remote-http$X" "$$execdir/$$p" || exit; } \
        done && \
        ./check_bindir "z$$bindir" "z$$execdir" "$$bindir/git-add$X"
  
diff --combined cache.h
index bbaf5c349ab893a6f0f4549b61bcc70eff50745f,5c24394d84426da27161db4925c226343a295969..77b7acebb6f73b6681fdd6bc4111e3b325409fdb
+++ b/cache.h
@@@ -459,7 -459,7 +459,7 @@@ static inline enum object_type object_t
   */
  extern const char * const local_repo_env[];
  
 -extern void setup_git_env(void);
 +extern void setup_git_env(const char *git_dir);
  
  /*
   * Returns true iff we have a configured git repository (either via
@@@ -477,7 -477,7 +477,7 @@@ extern const char *get_git_common_dir(v
  extern char *get_object_directory(void);
  extern char *get_index_file(void);
  extern char *get_graft_file(void);
- extern int set_git_dir(const char *path);
+ extern void set_git_dir(const char *path);
  extern int get_common_dir_noenv(struct strbuf *sb, const char *gitdir);
  extern int get_common_dir(struct strbuf *sb, const char *gitdir);
  extern const char *get_git_namespace(void);
@@@ -940,6 -940,12 +940,6 @@@ extern void check_repository_format(voi
  #define DATA_CHANGED    0x0020
  #define TYPE_CHANGED    0x0040
  
 -/*
 - * Put in `buf` the name of the file in the local object database that
 - * would be used to store a loose object with the specified sha1.
 - */
 -extern void sha1_file_name(struct strbuf *buf, const unsigned char *sha1);
 -
  /*
   * Return an abbreviated sha1 unique within this repository's object database.
   * The result will be at least `len` characters long, and will be NUL
   * more calls to find_unique_abbrev are made.
   *
   * The `_r` variant writes to a buffer supplied by the caller, which must be at
 - * least `GIT_SHA1_HEXSZ + 1` bytes. The return value is the number of bytes
 + * least `GIT_MAX_HEXSZ + 1` bytes. The return value is the number of bytes
   * written (excluding the NUL terminator).
   *
   * Note that while this version avoids the static buffer, it is not fully
   * reentrant, as it calls into other non-reentrant git code.
   */
 -extern const char *find_unique_abbrev(const unsigned char *sha1, int len);
 -extern int find_unique_abbrev_r(char *hex, const unsigned char *sha1, int len);
 +extern const char *find_unique_abbrev(const struct object_id *oid, int len);
 +extern int find_unique_abbrev_r(char *hex, const struct object_id *oid, int len);
  
  extern const unsigned char null_sha1[GIT_MAX_RAWSZ];
  extern const struct object_id null_oid;
@@@ -1183,19 -1189,19 +1183,19 @@@ extern char *xdg_config_home(const cha
   */
  extern char *xdg_cache_home(const char *filename);
  
 -extern void *read_sha1_file_extended(const unsigned char *sha1,
 -                                   enum object_type *type,
 -                                   unsigned long *size, int lookup_replace);
 -static inline void *read_sha1_file(const unsigned char *sha1, enum object_type *type, unsigned long *size)
 +extern void *read_object_file_extended(const struct object_id *oid,
 +                                     enum object_type *type,
 +                                     unsigned long *size, int lookup_replace);
 +static inline void *read_object_file(const struct object_id *oid, enum object_type *type, unsigned long *size)
  {
 -      return read_sha1_file_extended(sha1, type, size, 1);
 +      return read_object_file_extended(oid, type, size, 1);
  }
  
  /*
   * This internal function is only declared here for the benefit of
   * lookup_replace_object().  Please do not call it directly.
   */
 -extern const unsigned char *do_lookup_replace_object(const unsigned char *sha1);
 +extern const struct object_id *do_lookup_replace_object(const struct object_id *oid);
  
  /*
   * If object sha1 should be replaced, return the replacement object's
   * either sha1 or a pointer to a permanently-allocated value.  When
   * object replacement is suppressed, always return sha1.
   */
 -static inline const unsigned char *lookup_replace_object(const unsigned char *sha1)
 +static inline const struct object_id *lookup_replace_object(const struct object_id *oid)
  {
        if (!check_replace_refs)
 -              return sha1;
 -      return do_lookup_replace_object(sha1);
 +              return oid;
 +      return do_lookup_replace_object(oid);
  }
  
 -/* Read and unpack a sha1 file into memory, write memory to a sha1 file */
 -extern int sha1_object_info(const unsigned char *, unsigned long *);
 +/* Read and unpack an object file into memory, write memory to an object file */
 +extern int oid_object_info(const struct object_id *, unsigned long *);
  
  extern int hash_object_file(const void *buf, unsigned long len,
                            const char *type, struct object_id *oid);
@@@ -1230,22 -1236,23 +1230,22 @@@ extern int force_object_loose(const str
  
  extern int git_open_cloexec(const char *name, int flags);
  #define git_open(name) git_open_cloexec(name, O_RDONLY)
 -extern void *map_sha1_file(const unsigned char *sha1, unsigned long *size);
  extern int unpack_sha1_header(git_zstream *stream, unsigned char *map, unsigned long mapsize, void *buffer, unsigned long bufsiz);
  extern int parse_sha1_header(const char *hdr, unsigned long *sizep);
  
 -extern int check_sha1_signature(const unsigned char *sha1, void *buf, unsigned long size, const char *type);
 +extern int check_object_signature(const struct object_id *oid, void *buf, unsigned long size, const char *type);
  
  extern int finalize_object_file(const char *tmpfile, const char *filename);
  
  /*
 - * Open the loose object at path, check its sha1, and return the contents,
 + * Open the loose object at path, check its hash, and return the contents,
   * type, and size. If the object is a blob, then "contents" may return NULL,
   * to allow streaming of large blobs.
   *
   * Returns 0 on success, negative on error (details may be written to stderr).
   */
  int read_loose_object(const char *path,
 -                    const unsigned char *expected_sha1,
 +                    const struct object_id *expected_oid,
                      enum object_type *type,
                      unsigned long *size,
                      void **contents);
@@@ -1272,7 -1279,7 +1272,7 @@@ extern int has_object_file_with_flags(c
   */
  extern int has_loose_object_nonlocal(const unsigned char *sha1);
  
 -extern void assert_sha1_type(const unsigned char *sha1, enum object_type expect);
 +extern void assert_oid_type(const struct object_id *oid, enum object_type expect);
  
  /* Helper to check and "touch" a file */
  extern int check_and_freshen_file(const char *fn, int freshen);
@@@ -1428,10 -1435,10 +1428,10 @@@ extern int df_name_compare(const char *
  extern int name_compare(const char *name1, size_t len1, const char *name2, size_t len2);
  extern int cache_name_stage_compare(const char *name1, int len1, int stage1, const char *name2, int len2, int stage2);
  
 -extern void *read_object_with_reference(const unsigned char *sha1,
 +extern void *read_object_with_reference(const struct object_id *oid,
                                        const char *required_type,
                                        unsigned long *size,
 -                                      unsigned char *sha1_ret);
 +                                      struct object_id *oid_ret);
  
  extern struct object *peel_to_type(const char *name, int namelen,
                                   struct object *o, enum object_type);
@@@ -1557,6 -1564,57 +1557,6 @@@ extern int has_dirs_only_path(const cha
  extern void schedule_dir_for_removal(const char *name, int len);
  extern void remove_scheduled_dirs(void);
  
 -extern struct alternate_object_database {
 -      struct alternate_object_database *next;
 -
 -      /* see alt_scratch_buf() */
 -      struct strbuf scratch;
 -      size_t base_len;
 -
 -      /*
 -       * Used to store the results of readdir(3) calls when searching
 -       * for unique abbreviated hashes.  This cache is never
 -       * invalidated, thus it's racy and not necessarily accurate.
 -       * That's fine for its purpose; don't use it for tasks requiring
 -       * greater accuracy!
 -       */
 -      char loose_objects_subdir_seen[256];
 -      struct oid_array loose_objects_cache;
 -
 -      char path[FLEX_ARRAY];
 -} *alt_odb_list;
 -extern void prepare_alt_odb(void);
 -extern char *compute_alternate_path(const char *path, struct strbuf *err);
 -typedef int alt_odb_fn(struct alternate_object_database *, void *);
 -extern int foreach_alt_odb(alt_odb_fn, void*);
 -
 -/*
 - * Allocate a "struct alternate_object_database" but do _not_ actually
 - * add it to the list of alternates.
 - */
 -struct alternate_object_database *alloc_alt_odb(const char *dir);
 -
 -/*
 - * Add the directory to the on-disk alternates file; the new entry will also
 - * take effect in the current process.
 - */
 -extern void add_to_alternates_file(const char *dir);
 -
 -/*
 - * Add the directory to the in-memory list of alternates (along with any
 - * recursive alternates it points to), but do not modify the on-disk alternates
 - * file.
 - */
 -extern void add_to_alternates_memory(const char *dir);
 -
 -/*
 - * Returns a scratch strbuf pre-filled with the alternate object directory,
 - * including a trailing slash, which can be used to access paths in the
 - * alternate. Always use this over direct access to alt->scratch, as it
 - * cleans up any previous use of the scratch buffer.
 - */
 -extern struct strbuf *alt_scratch_buf(struct alternate_object_database *alt);
 -
  struct pack_window {
        struct pack_window *next;
        unsigned char *base;
        unsigned int inuse_cnt;
  };
  
 -extern struct packed_git {
 -      struct packed_git *next;
 -      struct list_head mru;
 -      struct pack_window *windows;
 -      off_t pack_size;
 -      const void *index_data;
 -      size_t index_size;
 -      uint32_t num_objects;
 -      uint32_t num_bad_objects;
 -      unsigned char *bad_object_sha1;
 -      int index_version;
 -      time_t mtime;
 -      int pack_fd;
 -      unsigned pack_local:1,
 -               pack_keep:1,
 -               freshened:1,
 -               do_not_close:1,
 -               pack_promisor:1;
 -      unsigned char sha1[20];
 -      struct revindex_entry *revindex;
 -      /* something like ".git/objects/pack/xxxxx.pack" */
 -      char pack_name[FLEX_ARRAY]; /* more */
 -} *packed_git;
 -
 -/*
 - * A most-recently-used ordered version of the packed_git list.
 - */
 -extern struct list_head packed_git_mru;
 -
  struct pack_entry {
        off_t offset;
        unsigned char sha1[20];
@@@ -1690,9 -1777,7 +1690,9 @@@ struct object_info 
  #define OBJECT_INFO_SKIP_CACHED 4
  /* Do not retry packed storage after checking packed and loose storage */
  #define OBJECT_INFO_QUICK 8
 -extern int sha1_object_info_extended(const unsigned char *, struct object_info *, unsigned flags);
 +/* Do not check loose object */
 +#define OBJECT_INFO_IGNORE_LOOSE 16
 +extern int oid_object_info_extended(const struct object_id *, struct object_info *, unsigned flags);
  
  /*
   * Set this to 0 to prevent sha1_object_info_extended() from fetching missing
diff --combined environment.c
index 39b3d906c89048cd094f352fa2ea81d873deb31a,903a6c9df7be3242484678c6b74b2658989657dd..fd970b81bdb682763fcb21528b9739171734bdba
@@@ -13,8 -13,7 +13,9 @@@
  #include "refs.h"
  #include "fmt-merge-msg.h"
  #include "commit.h"
 +#include "argv-array.h"
 +#include "object-store.h"
+ #include "chdir-notify.h"
  
  int trust_executable_bit = 1;
  int trust_ctime = 1;
@@@ -149,35 -148,10 +150,35 @@@ static char *expand_namespace(const cha
        return strbuf_detach(&buf, NULL);
  }
  
 -void setup_git_env(void)
 +/*
 + * Wrapper of getenv() that returns a strdup value. This value is kept
 + * in argv to be freed later.
 + */
 +static const char *getenv_safe(struct argv_array *argv, const char *name)
 +{
 +      const char *value = getenv(name);
 +
 +      if (!value)
 +              return NULL;
 +
 +      argv_array_push(argv, value);
 +      return argv->argv[argv->argc - 1];
 +}
 +
 +void setup_git_env(const char *git_dir)
  {
        const char *shallow_file;
        const char *replace_ref_base;
 +      struct set_gitdir_args args = { NULL };
 +      struct argv_array to_free = ARGV_ARRAY_INIT;
 +
 +      args.commondir = getenv_safe(&to_free, GIT_COMMON_DIR_ENVIRONMENT);
 +      args.object_dir = getenv_safe(&to_free, DB_ENVIRONMENT);
 +      args.graft_file = getenv_safe(&to_free, GRAFT_ENVIRONMENT);
 +      args.index_file = getenv_safe(&to_free, INDEX_ENVIRONMENT);
 +      args.alternate_db = getenv_safe(&to_free, ALTERNATE_DB_ENVIRONMENT);
 +      repo_set_gitdir(the_repository, git_dir, &args);
 +      argv_array_clear(&to_free);
  
        if (getenv(NO_REPLACE_OBJECTS_ENVIRONMENT))
                check_replace_refs = 0;
@@@ -271,9 -245,9 +272,9 @@@ const char *get_git_work_tree(void
  
  char *get_object_directory(void)
  {
 -      if (!the_repository->objectdir)
 +      if (!the_repository->objects->objectdir)
                BUG("git environment hasn't been setup");
 -      return the_repository->objectdir;
 +      return the_repository->objects->objectdir;
  }
  
  int odb_mkstemp(struct strbuf *temp_filename, const char *pattern)
@@@ -323,12 -297,32 +324,31 @@@ char *get_graft_file(void
        return the_repository->graft_file;
  }
  
int set_git_dir(const char *path)
static void set_git_dir_1(const char *path)
  {
        if (setenv(GIT_DIR_ENVIRONMENT, path, 1))
-               return error("Could not set GIT_DIR to '%s'", path);
+               die("could not set GIT_DIR to '%s'", path);
 -      repo_set_gitdir(the_repository, path);
 -      setup_git_env();
 +      setup_git_env(path);
-       return 0;
+ }
+ static void update_relative_gitdir(const char *name,
+                                  const char *old_cwd,
+                                  const char *new_cwd,
+                                  void *data)
+ {
+       char *path = reparent_relative_path(old_cwd, new_cwd, get_git_dir());
+       trace_printf_key(&trace_setup_key,
+                        "setup: move $GIT_DIR to '%s'",
+                        path);
+       set_git_dir_1(path);
+       free(path);
+ }
+ void set_git_dir(const char *path)
+ {
+       set_git_dir_1(path);
+       if (!is_absolute_path(path))
+               chdir_notify_register(NULL, update_relative_gitdir, NULL);
  }
  
  const char *get_log_output_encoding(void)
diff --combined setup.c
index 664453fcef7f3b75f56d000dc52f42a0aff42fb6,9eb2e808e193dc609e9b79f4abf7f23c268b6f86..3e03d442b6fad10c1b11fb8e8626f3c4fe444298
+++ b/setup.c
@@@ -3,6 -3,7 +3,7 @@@
  #include "config.h"
  #include "dir.h"
  #include "string-list.h"
+ #include "chdir-notify.h"
  
  static int inside_git_dir = -1;
  static int inside_work_tree = -1;
@@@ -378,7 -379,7 +379,7 @@@ int is_inside_work_tree(void
  
  void setup_work_tree(void)
  {
-       const char *work_tree, *git_dir;
+       const char *work_tree;
        static int initialized = 0;
  
        if (initialized)
                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))
-               git_dir = real_path(get_git_dir());
-       if (!work_tree || chdir(work_tree))
+       if (!work_tree || chdir_notify(work_tree))
                die(_("this operation must be run in a work tree"));
  
        /*
        if (getenv(GIT_WORK_TREE_ENVIRONMENT))
                setenv(GIT_WORK_TREE_ENVIRONMENT, ".", 1);
  
-       set_git_dir(remove_leading_path(git_dir, work_tree));
        initialized = 1;
  }
  
@@@ -1116,7 -1113,8 +1113,7 @@@ const char *setup_git_directory_gently(
                        const char *gitdir = getenv(GIT_DIR_ENVIRONMENT);
                        if (!gitdir)
                                gitdir = DEFAULT_GIT_DIR_ENVIRONMENT;
 -                      repo_set_gitdir(the_repository, gitdir);
 -                      setup_git_env();
 +                      setup_git_env(gitdir);
                }
                if (startup_info->have_repository)
                        repo_set_hash_algo(the_repository, repo_fmt.hash_algo);
diff --combined t/t1501-work-tree.sh
index 02cf2013fc6f68cdb62739e9dff94fa08f5607be,a5db53337baee2db00ec8ac7b476026796665594..9c0bc6525034021a1947d07faec84570d0dd629a
@@@ -341,7 -341,7 +341,7 @@@ test_expect_success 'make_relative_pat
  
  test_expect_success 'relative $GIT_WORK_TREE and git subprocesses' '
        GIT_DIR=repo.git GIT_WORK_TREE=repo.git/work \
 -      test-subprocess --setup-work-tree rev-parse --show-toplevel >actual &&
 +      test-tool subprocess --setup-work-tree rev-parse --show-toplevel >actual &&
        echo "$(pwd)/repo.git/work" >expected &&
        test_cmp expected actual
  '
@@@ -360,7 -360,7 +360,7 @@@ test_expect_success 'GIT_DIR set (1)' 
        (
                cd work &&
                GIT_DIR=../gitfile git rev-parse --git-common-dir >actual &&
 -              test-path-utils real_path "$TRASH_DIRECTORY/repo.git" >expect &&
 +              test-tool path-utils real_path "$TRASH_DIRECTORY/repo.git" >expect &&
                test_cmp expect actual
        )
  '
@@@ -371,7 -371,7 +371,7 @@@ test_expect_success 'GIT_DIR set (2)' 
        (
                cd work &&
                GIT_DIR=../gitfile git rev-parse --git-common-dir >actual &&
 -              test-path-utils real_path "$TRASH_DIRECTORY/repo.git" >expect &&
 +              test-tool path-utils real_path "$TRASH_DIRECTORY/repo.git" >expect &&
                test_cmp expect actual
        )
  '
@@@ -382,7 -382,7 +382,7 @@@ test_expect_success 'Auto discovery' 
        (
                cd work &&
                git rev-parse --git-common-dir >actual &&
 -              test-path-utils real_path "$TRASH_DIRECTORY/repo.git" >expect &&
 +              test-tool path-utils real_path "$TRASH_DIRECTORY/repo.git" >expect &&
                test_cmp expect actual &&
                echo haha >data1 &&
                git add data1 &&
@@@ -400,7 -400,7 +400,7 @@@ test_expect_success '$GIT_DIR/common ov
        (
                cd work &&
                git rev-parse --git-common-dir >actual &&
 -              test-path-utils real_path "$TRASH_DIRECTORY/repo.git" >expect &&
 +              test-tool path-utils real_path "$TRASH_DIRECTORY/repo.git" >expect &&
                test_cmp expect actual &&
                echo haha >data2 &&
                git add data2 &&
@@@ -431,4 -431,16 +431,16 @@@ test_expect_success 'error out graceful
        )
  '
  
+ test_expect_success 'refs work with relative gitdir and work tree' '
+       git init relative &&
+       git -C relative commit --allow-empty -m one &&
+       git -C relative commit --allow-empty -m two &&
+       GIT_DIR=relative/.git GIT_WORK_TREE=relative git reset HEAD^ &&
+       git -C relative log -1 --format=%s >actual &&
+       echo one >expect &&
+       test_cmp expect actual
+ '
  test_done