Merge branch 'js/maint-1.6.0-path-normalize'
authorJunio C Hamano <gitster@pobox.com>
Wed, 11 Feb 2009 05:30:52 +0000 (21:30 -0800)
committerJunio C Hamano <gitster@pobox.com>
Wed, 11 Feb 2009 05:30:52 +0000 (21:30 -0800)
* js/maint-1.6.0-path-normalize:
Remove unused normalize_absolute_path()
Test and fix normalize_path_copy()
Fix GIT_CEILING_DIRECTORIES on Windows
Move sanitary_path_copy() to path.c and rename it to normalize_path_copy()
Make test-path-utils more robust against incorrect use

1  2 
cache.h
path.c
setup.c
diff --combined cache.h
index d8ba693146640ccac688f9d07f9f5e7c332d3923,39f0d6fea4b04c6c7477c6e874b69f92b11e751c..7f1a6e8bd05ec2998e94f8f1f134ec3759713667
+++ b/cache.h
@@@ -6,22 -6,12 +6,22 @@@
  #include "hash.h"
  
  #include SHA1_HEADER
 -#include <zlib.h>
 +#ifndef git_SHA_CTX
 +#define git_SHA_CTX   SHA_CTX
 +#define git_SHA1_Init SHA1_Init
 +#define git_SHA1_Update       SHA1_Update
 +#define git_SHA1_Final        SHA1_Final
 +#endif
  
 +#include <zlib.h>
  #if defined(NO_DEFLATE_BOUND) || ZLIB_VERNUM < 0x1200
  #define deflateBound(c,s)  ((s) + (((s) + 7) >> 3) + (((s) + 63) >> 6) + 11)
  #endif
  
 +void git_inflate_init(z_streamp strm);
 +void git_inflate_end(z_streamp strm);
 +int git_inflate(z_streamp strm, int flush);
 +
  #if defined(DT_UNKNOWN) && !defined(NO_D_TYPE_IN_DIRENT)
  #define DTYPE(de)     ((de)->d_type)
  #else
@@@ -119,26 -109,6 +119,26 @@@ struct ondisk_cache_entry 
        char name[FLEX_ARRAY]; /* more */
  };
  
 +/*
 + * This struct is used when CE_EXTENDED bit is 1
 + * The struct must match ondisk_cache_entry exactly from
 + * ctime till flags
 + */
 +struct ondisk_cache_entry_extended {
 +      struct cache_time ctime;
 +      struct cache_time mtime;
 +      unsigned int dev;
 +      unsigned int ino;
 +      unsigned int mode;
 +      unsigned int uid;
 +      unsigned int gid;
 +      unsigned int size;
 +      unsigned char sha1[20];
 +      unsigned short flags;
 +      unsigned short flags2;
 +      char name[FLEX_ARRAY]; /* more */
 +};
 +
  struct cache_entry {
        unsigned int ce_ctime;
        unsigned int ce_mtime;
  
  #define CE_NAMEMASK  (0x0fff)
  #define CE_STAGEMASK (0x3000)
 +#define CE_EXTENDED  (0x4000)
  #define CE_VALID     (0x8000)
  #define CE_STAGESHIFT 12
  
 -/* In-memory only */
 +/*
 + * Range 0xFFFF0000 in ce_flags is divided into
 + * two parts: in-memory flags and on-disk ones.
 + * Flags in CE_EXTENDED_FLAGS will get saved on-disk
 + * if you want to save a new flag, add it in
 + * CE_EXTENDED_FLAGS
 + *
 + * In-memory only flags
 + */
  #define CE_UPDATE    (0x10000)
  #define CE_REMOVE    (0x20000)
  #define CE_UPTODATE  (0x40000)
  #define CE_HASHED    (0x100000)
  #define CE_UNHASHED  (0x200000)
  
 +/*
 + * Extended on-disk flags
 + */
 +#define CE_INTENT_TO_ADD 0x20000000
 +/* CE_EXTENDED2 is for future extension */
 +#define CE_EXTENDED2 0x80000000
 +
 +#define CE_EXTENDED_FLAGS (CE_INTENT_TO_ADD)
 +
 +/*
 + * Safeguard to avoid saving wrong flags:
 + *  - CE_EXTENDED2 won't get saved until its semantic is known
 + *  - Bits in 0x0000FFFF have been saved in ce_flags already
 + *  - Bits in 0x003F0000 are currently in-memory flags
 + */
 +#if CE_EXTENDED_FLAGS & 0x803FFFFF
 +#error "CE_EXTENDED_FLAGS out of range"
 +#endif
 +
  /*
   * Copy the sha1 and stat state of a cache entry from one to
   * another. But we never change the name, or the hash state!
@@@ -228,9 -170,7 +228,9 @@@ static inline size_t ce_namelen(const s
  }
  
  #define ce_size(ce) cache_entry_size(ce_namelen(ce))
 -#define ondisk_ce_size(ce) ondisk_cache_entry_size(ce_namelen(ce))
 +#define ondisk_ce_size(ce) (((ce)->ce_flags & CE_EXTENDED) ? \
 +                          ondisk_cache_entry_extended_size(ce_namelen(ce)) : \
 +                          ondisk_cache_entry_size(ce_namelen(ce)))
  #define ce_stage(ce) ((CE_STAGEMASK & (ce)->ce_flags) >> CE_STAGESHIFT)
  #define ce_uptodate(ce) ((ce)->ce_flags & CE_UPTODATE)
  #define ce_mark_uptodate(ce) ((ce)->ce_flags |= CE_UPTODATE)
@@@ -273,10 -213,8 +273,10 @@@ static inline int ce_to_dtype(const str
        (S_ISREG(mode) ? (S_IFREG | ce_permissions(mode)) : \
        S_ISLNK(mode) ? S_IFLNK : S_ISDIR(mode) ? S_IFDIR : S_IFGITLINK)
  
 -#define cache_entry_size(len) ((offsetof(struct cache_entry,name) + (len) + 8) & ~7)
 -#define ondisk_cache_entry_size(len) ((offsetof(struct ondisk_cache_entry,name) + (len) + 8) & ~7)
 +#define flexible_size(STRUCT,len) ((offsetof(struct STRUCT,name) + (len) + 8) & ~7)
 +#define cache_entry_size(len) flexible_size(cache_entry,len)
 +#define ondisk_cache_entry_size(len) flexible_size(ondisk_cache_entry,len)
 +#define ondisk_cache_entry_extended_size(len) flexible_size(ondisk_cache_entry_extended,len)
  
  struct index_state {
        struct cache_entry **cache;
@@@ -317,7 -255,6 +317,7 @@@ static inline void remove_name_hash(str
  
  #define read_cache() read_index(&the_index)
  #define read_cache_from(path) read_index_from(&the_index, (path))
 +#define read_cache_preload(pathspec) read_index_preload(&the_index, (pathspec))
  #define is_cache_unborn() is_index_unborn(&the_index)
  #define read_cache_unmerged() read_index_unmerged(&the_index)
  #define write_cache(newfd, cache, entries) write_index(&the_index, (newfd))
@@@ -371,15 -308,12 +371,15 @@@ static inline enum object_type object_t
  #define GITATTRIBUTES_FILE ".gitattributes"
  #define INFOATTRIBUTES_FILE "info/attributes"
  #define ATTRIBUTE_MACRO_PREFIX "[attr]"
 +#define GIT_NOTES_REF_ENVIRONMENT "GIT_NOTES_REF"
 +#define GIT_NOTES_DEFAULT_REF "refs/notes/commits"
  
  extern int is_bare_repository_cfg;
  extern int is_bare_repository(void);
  extern int is_inside_git_dir(void);
  extern char *git_work_tree_cfg;
  extern int is_inside_work_tree(void);
 +extern int have_git_dir(void);
  extern const char *get_git_dir(void);
  extern char *get_object_directory(void);
  extern char *get_index_file(void);
@@@ -426,7 -360,6 +426,7 @@@ extern int init_db(const char *template
  
  /* Initialize and use the cache information */
  extern int read_index(struct index_state *);
 +extern int read_index_preload(struct index_state *, const char **pathspec);
  extern int read_index_from(struct index_state *, const char *path);
  extern int is_index_unborn(struct index_state *);
  extern int read_index_unmerged(struct index_state *);
@@@ -440,7 -373,6 +440,7 @@@ extern int index_name_pos(const struct 
  #define ADD_CACHE_OK_TO_REPLACE 2     /* Ok to replace file/directory */
  #define ADD_CACHE_SKIP_DFCHECK 4      /* Ok to skip DF conflict checks */
  #define ADD_CACHE_JUST_APPEND 8               /* Append only; tree.c::read_tree() */
 +#define ADD_CACHE_NEW_ONLY 16         /* Do not replace existing ones */
  extern int add_index_entry(struct index_state *, struct cache_entry *ce, int option);
  extern struct cache_entry *refresh_cache_entry(struct cache_entry *ce, int really);
  extern void rename_index_entry_at(struct index_state *, int pos, const char *new_name);
@@@ -449,8 -381,6 +449,8 @@@ extern int remove_file_from_index(struc
  #define ADD_CACHE_VERBOSE 1
  #define ADD_CACHE_PRETEND 2
  #define ADD_CACHE_IGNORE_ERRORS       4
 +#define ADD_CACHE_IGNORE_REMOVAL 8
 +#define ADD_CACHE_INTENT 16
  extern int add_to_index(struct index_state *, const char *path, struct stat *, int flags);
  extern int add_file_to_index(struct index_state *, const char *path, int flags);
  extern struct cache_entry *make_cache_entry(unsigned int mode, const unsigned char *sha1, const char *path, int stage, int refresh);
@@@ -466,6 -396,7 +466,6 @@@ extern int ie_modified(const struct ind
  
  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);
 -extern int index_pipe(unsigned char *sha1, int fd, const char *type, int write_object);
  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);
  
@@@ -517,7 -448,6 +517,7 @@@ extern size_t packed_git_limit
  extern size_t delta_base_cache_limit;
  extern int auto_crlf;
  extern int fsync_object_files;
 +extern int core_preload_index;
  
  enum safe_crlf {
        SAFE_CRLF_FALSE = 0,
  extern enum safe_crlf safe_crlf;
  
  enum branch_track {
 +      BRANCH_TRACK_UNSPECIFIED = -1,
        BRANCH_TRACK_NEVER = 0,
        BRANCH_TRACK_REMOTE,
        BRANCH_TRACK_ALWAYS,
@@@ -544,7 -473,6 +544,7 @@@ enum rebase_setup_type 
  
  extern enum branch_track git_branch_track;
  extern enum rebase_setup_type autorebase;
 +extern char *notes_ref_name;
  
  #define GIT_REPO_VERSION 0
  extern int repository_format_version;
@@@ -589,13 -517,6 +589,13 @@@ static inline void hashclr(unsigned cha
  {
        memset(hash, 0, 20);
  }
 +extern int is_empty_blob_sha1(const unsigned char *sha1);
 +
 +#define EMPTY_TREE_SHA1_HEX \
 +      "4b825dc642cb6eb9a060e54bf8d69288fbee4904"
 +#define EMPTY_TREE_SHA1_BIN \
 +       "\x4b\x82\x5d\xc6\x42\xcb\x6e\xb9\xa0\x60" \
 +       "\xe5\x4b\xf8\xd6\x92\x88\xfb\xee\x49\x04"
  
  int git_mkstemp(char *path, size_t n, const char *template);
  
@@@ -623,11 -544,10 +623,11 @@@ static inline int is_absolute_path(cons
  {
        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);
- int normalize_absolute_path(char *buf, const char *path);
+ int normalize_path_copy(char *dst, const char *src);
  int longest_ancestor_length(const char *path, const char *prefix_list);
  
  /* Read and unpack a sha1 file into memory, write memory to a sha1 file */
@@@ -638,8 -558,8 +638,8 @@@ extern int write_sha1_file(void *buf, u
  extern int pretend_sha1_file(void *, unsigned long, enum object_type, unsigned char *);
  extern int force_object_loose(const unsigned char *sha1, time_t mtime);
  
 -/* just like read_sha1_file(), but non fatal in presence of bad objects */
 -extern void *read_object(const unsigned char *sha1, enum object_type *type, unsigned long *size);
 +/* global flag to enable extra checks when accessing packed objects */
 +extern int do_check_packed_object_crc;
  
  extern int check_sha1_signature(const unsigned char *sha1, void *buf, unsigned long size, const char *type);
  
@@@ -670,7 -590,6 +670,7 @@@ extern int read_ref(const char *filenam
  extern const char *resolve_ref(const char *path, unsigned char *sha1, int, int *);
  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_nth_last_branch(const char *str, struct strbuf *);
  
  extern int refname_match(const char *abbrev_name, const char *full_name, const char **rules);
  extern const char *ref_rev_parse_rules[];
@@@ -725,10 -644,6 +725,10 @@@ struct checkout 
  
  extern int checkout_entry(struct cache_entry *ce, const struct checkout *state, char *topath);
  extern int has_symlink_leading_path(int len, const char *name);
 +extern int has_symlink_or_noent_leading_path(int len, const char *name);
 +extern int has_dirs_only_path(int len, const char *name, int prefix_len);
 +extern void invalidate_lstat_cache(int len, const char *name);
 +extern void clear_lstat_cache(void);
  
  extern struct alternate_object_database {
        struct alternate_object_database *next;
  } *alt_odb_list;
  extern void prepare_alt_odb(void);
  extern void add_to_alternates_file(const char *reference);
 +typedef int alt_odb_fn(struct alternate_object_database *, void *);
 +extern void foreach_alt_odb(alt_odb_fn, void*);
  
  struct pack_window {
        struct pack_window *next;
@@@ -808,11 -721,7 +808,11 @@@ extern struct child_process *git_connec
  extern int finish_connect(struct child_process *conn);
  extern int path_match(const char *path, int nr, char **match);
  extern int get_ack(int fd, unsigned char *result_sha1);
 -extern struct ref **get_remote_heads(int in, struct ref **list, int nr_match, char **match, unsigned int flags);
 +struct extra_have_objects {
 +      int nr, alloc;
 +      unsigned char (*array)[20];
 +};
 +extern struct ref **get_remote_heads(int in, struct ref **list, int nr_match, char **match, unsigned int flags, struct extra_have_objects *);
  extern int server_supports(const char *feature);
  
  extern struct packed_git *parse_pack_index(unsigned char *sha1);
@@@ -830,13 -739,12 +830,13 @@@ extern unsigned char* use_pack(struct p
  extern void close_pack_windows(struct packed_git *);
  extern void unuse_pack(struct pack_window **);
  extern void free_pack_by_name(const char *);
 +extern void clear_delta_base_cache(void);
  extern struct packed_git *add_packed_git(const char *, int, int);
  extern const unsigned char *nth_packed_object_sha1(struct packed_git *, uint32_t);
  extern off_t nth_packed_object_offset(const struct packed_git *, uint32_t);
  extern off_t find_pack_entry_one(const unsigned char *, struct packed_git *);
  extern void *unpack_entry(struct packed_git *, off_t, enum object_type *, unsigned long *);
 -extern unsigned long unpack_object_header_gently(const unsigned char *buf, unsigned long len, enum object_type *type, unsigned long *sizep);
 +extern unsigned long unpack_object_header_buffer(const unsigned char *buf, unsigned long len, enum object_type *type, unsigned long *sizep);
  extern unsigned long get_size_from_delta(struct packed_git *, struct pack_window **, off_t);
  extern const char *packed_object_info_detail(struct packed_git *, off_t, unsigned long *, unsigned long *, unsigned int *, unsigned char *);
  extern int matches_pack_name(struct packed_git *p, const char *name);
@@@ -848,6 -756,7 +848,6 @@@ typedef int (*config_fn_t)(const char *
  extern int git_default_config(const char *, const char *, void *);
  extern int git_config_from_file(config_fn_t fn, const char *, void *);
  extern int git_config(config_fn_t fn, void *);
 -extern int git_parse_long(const char *, long *);
  extern int git_parse_ulong(const char *, unsigned long *);
  extern int git_config_int(const char *, const char *);
  extern unsigned long git_config_ulong(const char *, const char *);
@@@ -946,6 -855,7 +946,6 @@@ extern int ws_fix_copy(char *, const ch
  extern int ws_blank_line(const char *line, int len, unsigned ws_rule);
  
  /* ls-files */
 -int pathspec_match(const char **spec, char *matched, const char *filename, int skiplen);
  int report_path_error(const char *ps_matched, const char **pathspec, int prefix_offset);
  void overlay_tree_on_cache(const char *tree_name, const char *prefix);
  
diff --combined path.c
index 108d9e9599d059a06fd0621188f45a716346ca07,4b9107fed10c1f3551acf1f14d2ba5d1ba8a0b84..dd22370e8e5cb932f9c8ce8756c154073771b35b
--- 1/path.c
--- 2/path.c
+++ b/path.c
@@@ -154,7 -154,7 +154,7 @@@ int validate_headref(const char *path
        /* Make sure it is a "refs/.." symlink */
        if (S_ISLNK(st.st_mode)) {
                len = readlink(path, buffer, sizeof(buffer)-1);
 -              if (len >= 5 && !memcmp("refs/", buffer, 5))
 +              if (len >= 11 && !memcmp("refs/heads/", buffer, 11))
                        return 0;
                return -1;
        }
                len -= 4;
                while (len && isspace(*buf))
                        buf++, len--;
 -              if (len >= 5 && !memcmp("refs/", buf, 5))
 +              if (len >= 11 && !memcmp("refs/heads/", buf, 11))
                        return 0;
        }
  
@@@ -363,56 -363,97 +363,97 @@@ const char *make_relative_path(const ch
  }
  
  /*
-  * path = absolute path
-  * buf = buffer of at least max(2, strlen(path)+1) bytes
-  * It is okay if buf == path, but they should not overlap otherwise.
+  * It is okay if dst == src, but they should not overlap otherwise.
   *
-  * Performs the following normalizations on path, storing the result in buf:
-  * - Removes trailing slashes.
-  * - Removes empty components.
+  * Performs the following normalizations on src, storing the result in dst:
+  * - Ensures that components are separated by '/' (Windows only)
+  * - Squashes sequences of '/'.
   * - Removes "." components.
   * - Removes ".." components, and the components the precede them.
-  * "" and paths that contain only slashes are normalized to "/".
-  * Returns the length of the output.
+  * Returns failure (non-zero) if a ".." component appears as first path
+  * component anytime during the normalization. Otherwise, returns success (0).
   *
   * Note that this function is purely textual.  It does not follow symlinks,
   * verify the existence of the path, or make any system calls.
   */
- int normalize_absolute_path(char *buf, const char *path)
+ int normalize_path_copy(char *dst, const char *src)
  {
-       const char *comp_start = path, *comp_end = path;
-       char *dst = buf;
-       int comp_len;
-       assert(buf);
-       assert(path);
-       while (*comp_start) {
-               assert(*comp_start == '/');
-               while (*++comp_end && *comp_end != '/')
-                       ; /* nothing */
-               comp_len = comp_end - comp_start;
-               if (!strncmp("/",  comp_start, comp_len) ||
-                   !strncmp("/.", comp_start, comp_len))
-                       goto next;
-               if (!strncmp("/..", comp_start, comp_len)) {
-                       while (dst > buf && *--dst != '/')
-                               ; /* nothing */
-                       goto next;
-               }
+       char *dst0;
  
-               memmove(dst, comp_start, comp_len);
-               dst += comp_len;
-       next:
-               comp_start = comp_end;
+       if (has_dos_drive_prefix(src)) {
+               *dst++ = *src++;
+               *dst++ = *src++;
        }
+       dst0 = dst;
  
-       if (dst == buf)
+       if (is_dir_sep(*src)) {
                *dst++ = '/';
+               while (is_dir_sep(*src))
+                       src++;
+       }
+       for (;;) {
+               char c = *src;
+               /*
+                * A path component that begins with . could be
+                * special:
+                * (1) "." and ends   -- ignore and terminate.
+                * (2) "./"           -- ignore them, eat slash and continue.
+                * (3) ".." and ends  -- strip one and terminate.
+                * (4) "../"          -- strip one, eat slash and continue.
+                */
+               if (c == '.') {
+                       if (!src[1]) {
+                               /* (1) */
+                               src++;
+                       } else if (is_dir_sep(src[1])) {
+                               /* (2) */
+                               src += 2;
+                               while (is_dir_sep(*src))
+                                       src++;
+                               continue;
+                       } else if (src[1] == '.') {
+                               if (!src[2]) {
+                                       /* (3) */
+                                       src += 2;
+                                       goto up_one;
+                               } else if (is_dir_sep(src[2])) {
+                                       /* (4) */
+                                       src += 3;
+                                       while (is_dir_sep(*src))
+                                               src++;
+                                       goto up_one;
+                               }
+                       }
+               }
  
+               /* copy up to the next '/', and eat all '/' */
+               while ((c = *src++) != '\0' && !is_dir_sep(c))
+                       *dst++ = c;
+               if (is_dir_sep(c)) {
+                       *dst++ = '/';
+                       while (is_dir_sep(c))
+                               c = *src++;
+                       src--;
+               } else if (!c)
+                       break;
+               continue;
+       up_one:
+               /*
+                * dst0..dst is prefix portion, and dst[-1] is '/';
+                * go up one level.
+                */
+               dst--;  /* go to trailing '/' */
+               if (dst <= dst0)
+                       return -1;
+               /* Windows: dst[-1] cannot be backslash anymore */
+               while (dst0 < dst && dst[-1] != '/')
+                       dst--;
+       }
        *dst = '\0';
-       return dst - buf;
+       return 0;
  }
  
  /*
@@@ -438,15 -479,16 +479,16 @@@ int longest_ancestor_length(const char 
                return -1;
  
        for (colon = ceil = prefix_list; *colon; ceil = colon+1) {
-               for (colon = ceil; *colon && *colon != ':'; colon++);
+               for (colon = ceil; *colon && *colon != PATH_SEP; colon++);
                len = colon - ceil;
                if (len == 0 || len > PATH_MAX || !is_absolute_path(ceil))
                        continue;
                strlcpy(buf, ceil, len+1);
-               len = normalize_absolute_path(buf, buf);
-               /* Strip "trailing slashes" from "/". */
-               if (len == 1)
-                       len = 0;
+               if (normalize_path_copy(buf, buf) < 0)
+                       continue;
+               len = strlen(buf);
+               if (len > 0 && buf[len-1] == '/')
+                       buf[--len] = '\0';
  
                if (!strncmp(path, buf, len) &&
                    path[len] == '/' &&
diff --combined setup.c
index dfda532adc16f5e6d25d7cfc5add3e0e2b6a5209,59735c14506fbf29549628294f98a7d0c7b64b79..6c2deda18492acb5a8597563d6843f9d0dd232c0
+++ b/setup.c
@@@ -4,92 -4,6 +4,6 @@@
  static int inside_git_dir = -1;
  static int inside_work_tree = -1;
  
- static int sanitary_path_copy(char *dst, const char *src)
- {
-       char *dst0;
-       if (has_dos_drive_prefix(src)) {
-               *dst++ = *src++;
-               *dst++ = *src++;
-       }
-       dst0 = dst;
-       if (is_dir_sep(*src)) {
-               *dst++ = '/';
-               while (is_dir_sep(*src))
-                       src++;
-       }
-       for (;;) {
-               char c = *src;
-               /*
-                * A path component that begins with . could be
-                * special:
-                * (1) "." and ends   -- ignore and terminate.
-                * (2) "./"           -- ignore them, eat slash and continue.
-                * (3) ".." and ends  -- strip one and terminate.
-                * (4) "../"          -- strip one, eat slash and continue.
-                */
-               if (c == '.') {
-                       if (!src[1]) {
-                               /* (1) */
-                               src++;
-                       } else if (is_dir_sep(src[1])) {
-                               /* (2) */
-                               src += 2;
-                               while (is_dir_sep(*src))
-                                       src++;
-                               continue;
-                       } else if (src[1] == '.') {
-                               if (!src[2]) {
-                                       /* (3) */
-                                       src += 2;
-                                       goto up_one;
-                               } else if (is_dir_sep(src[2])) {
-                                       /* (4) */
-                                       src += 3;
-                                       while (is_dir_sep(*src))
-                                               src++;
-                                       goto up_one;
-                               }
-                       }
-               }
-               /* copy up to the next '/', and eat all '/' */
-               while ((c = *src++) != '\0' && !is_dir_sep(c))
-                       *dst++ = c;
-               if (is_dir_sep(c)) {
-                       *dst++ = '/';
-                       while (is_dir_sep(c))
-                               c = *src++;
-                       src--;
-               } else if (!c)
-                       break;
-               continue;
-       up_one:
-               /*
-                * dst0..dst is prefix portion, and dst[-1] is '/';
-                * go up one level.
-                */
-               dst -= 2; /* go past trailing '/' if any */
-               if (dst < dst0)
-                       return -1;
-               while (1) {
-                       if (dst <= dst0)
-                               break;
-                       c = *dst--;
-                       if (c == '/') { /* MinGW: cannot be '\\' anymore */
-                               dst += 2;
-                               break;
-                       }
-               }
-       }
-       *dst = '\0';
-       return 0;
- }
  const char *prefix_path(const char *prefix, int len, const char *path)
  {
        const char *orig = path;
                        memcpy(sanitized, prefix, len);
                strcpy(sanitized + len, path);
        }
-       if (sanitary_path_copy(sanitized, sanitized))
+       if (normalize_path_copy(sanitized, sanitized))
                goto error_out;
        if (is_absolute_path(orig)) {
                const char *work_tree = get_git_work_tree();
@@@ -456,11 -370,7 +370,11 @@@ const char *setup_git_directory_gently(
                        inside_git_dir = 1;
                        if (!work_tree_env)
                                inside_work_tree = 0;
 -                      setenv(GIT_DIR_ENVIRONMENT, ".", 1);
 +                      if (offset != len) {
 +                              cwd[offset] = '\0';
 +                              setenv(GIT_DIR_ENVIRONMENT, cwd, 1);
 +                      } else
 +                              setenv(GIT_DIR_ENVIRONMENT, ".", 1);
                        check_repository_format_gently(nongit_ok);
                        return NULL;
                }
                                *nongit_ok = 1;
                                return NULL;
                        }
 -                      die("Not a git repository");
 +                      die("Not a git repository (or any of the parent directories): %s", DEFAULT_GIT_DIR_ENVIRONMENT);
                }
 -              chdir("..");
 +              if (chdir(".."))
 +                      die("Cannot change to %s/..: %s", cwd, strerror(errno));
        }
  
        inside_git_dir = 0;