Merge branch 'jx/relative-path-regression-fix'
authorJunio C Hamano <gitster@pobox.com>
Mon, 28 Oct 2013 17:42:29 +0000 (10:42 -0700)
committerJunio C Hamano <gitster@pobox.com>
Mon, 28 Oct 2013 17:42:30 +0000 (10:42 -0700)
* jx/relative-path-regression-fix:
Use simpler relative_path when set_git_dir
relative_path should honor dos-drive-prefix
test: use unambigous leading path (/foo) for MSYS

1  2 
cache.h
path.c
setup.c
t/t0060-path-utils.sh
diff --combined cache.h
index 5e3fc72fd40f49638c24c632121810468fb3acea,94475bd9eeda2edf86d005837d84d405e6836fe6..70ad174dd98c82503d246f8d9e5a253805e44076
+++ b/cache.h
@@@ -101,9 -101,9 +101,9 @@@ unsigned long git_deflate_bound(git_zst
  
  #define CACHE_SIGNATURE 0x44495243    /* "DIRC" */
  struct cache_header {
 -      unsigned int hdr_signature;
 -      unsigned int hdr_version;
 -      unsigned int hdr_entries;
 +      uint32_t hdr_signature;
 +      uint32_t hdr_version;
 +      uint32_t hdr_entries;
  };
  
  #define INDEX_FORMAT_LB 2
   * check it for equality in the 32 bits we save.
   */
  struct cache_time {
 -      unsigned int sec;
 -      unsigned int nsec;
 +      uint32_t sec;
 +      uint32_t nsec;
 +};
 +
 +struct stat_data {
 +      struct cache_time sd_ctime;
 +      struct cache_time sd_mtime;
 +      unsigned int sd_dev;
 +      unsigned int sd_ino;
 +      unsigned int sd_uid;
 +      unsigned int sd_gid;
 +      unsigned int sd_size;
  };
  
  struct cache_entry {
 -      struct cache_time ce_ctime;
 -      struct cache_time ce_mtime;
 -      unsigned int ce_dev;
 -      unsigned int ce_ino;
 +      struct stat_data ce_stat_data;
        unsigned int ce_mode;
 -      unsigned int ce_uid;
 -      unsigned int ce_gid;
 -      unsigned int ce_size;
        unsigned int ce_flags;
        unsigned int ce_namelen;
        unsigned char sha1[20];
  #error "CE_EXTENDED_FLAGS out of range"
  #endif
  
 +struct pathspec;
 +
  /*
   * Copy the sha1 and stat state of a cache entry from one to
   * another. But we never change the name, or the hash state!
   */
  #define CE_STATE_MASK (CE_HASHED | CE_UNHASHED)
 -static inline void copy_cache_entry(struct cache_entry *dst, struct cache_entry *src)
 +static inline void copy_cache_entry(struct cache_entry *dst,
 +                                  const struct cache_entry *src)
  {
        unsigned int state = dst->ce_flags & CE_STATE_MASK;
  
@@@ -229,8 -222,7 +229,8 @@@ static inline unsigned int create_ce_mo
                return S_IFGITLINK;
        return S_IFREG | ce_permissions(mode);
  }
 -static inline unsigned int ce_mode_from_stat(struct cache_entry *ce, unsigned int mode)
 +static inline unsigned int ce_mode_from_stat(const struct cache_entry *ce,
 +                                           unsigned int mode)
  {
        extern int trust_executable_bit, has_symlinks;
        if (!has_symlinks && S_ISREG(mode) &&
@@@ -314,8 -306,6 +314,8 @@@ extern void free_name_hash(struct index
  #define refresh_cache(flags) refresh_index(&the_index, (flags), NULL, NULL, NULL)
  #define ce_match_stat(ce, st, options) ie_match_stat(&the_index, (ce), (st), (options))
  #define ce_modified(ce, st, options) ie_modified(&the_index, (ce), (st), (options))
 +#define cache_dir_exists(name, namelen) index_dir_exists(&the_index, (name), (namelen))
 +#define cache_file_exists(name, namelen, igncase) index_file_exists(&the_index, (name), (namelen), (igncase))
  #define cache_name_exists(name, namelen, igncase) index_name_exists(&the_index, (name), (namelen), (igncase))
  #define cache_name_is_other(name, namelen) index_name_is_other(&the_index, (name), (namelen))
  #define resolve_undo_clear() resolve_undo_clear_index(&the_index)
@@@ -369,9 -359,6 +369,9 @@@ static inline enum object_type object_t
  #define GIT_NOTES_REWRITE_REF_ENVIRONMENT "GIT_NOTES_REWRITE_REF"
  #define GIT_NOTES_REWRITE_MODE_ENVIRONMENT "GIT_NOTES_REWRITE_MODE"
  #define GIT_LITERAL_PATHSPECS_ENVIRONMENT "GIT_LITERAL_PATHSPECS"
 +#define GIT_GLOB_PATHSPECS_ENVIRONMENT "GIT_GLOB_PATHSPECS"
 +#define GIT_NOGLOB_PATHSPECS_ENVIRONMENT "GIT_NOGLOB_PATHSPECS"
 +#define GIT_ICASE_PATHSPECS_ENVIRONMENT "GIT_ICASE_PATHSPECS"
  
  /*
   * This environment variable is expected to contain a boolean indicating
@@@ -419,7 -406,6 +419,7 @@@ extern void setup_work_tree(void)
  extern const char *setup_git_directory_gently(int *);
  extern const char *setup_git_directory(void);
  extern char *prefix_path(const char *prefix, int len, const char *path);
 +extern char *prefix_path_gently(const char *prefix, int len, int *remaining, const char *path);
  extern const char *prefix_filename(const char *prefix, int len, const char *path);
  extern int check_filename(const char *prefix, const char *name);
  extern void verify_filename(const char *prefix,
@@@ -433,8 -419,6 +433,8 @@@ extern int path_inside_repo(const char 
  extern int set_git_dir_init(const char *git_dir, const char *real_git_dir, int);
  extern int init_db(const char *template_dir, unsigned int flags);
  
 +extern void sanitize_stdfds(void);
 +
  #define alloc_nr(x) (((x)+16)*3/2)
  
  /*
  
  /* 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_preload(struct index_state *, const struct pathspec *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 *);
@@@ -465,8 -449,6 +465,8 @@@ extern int write_index(struct index_sta
  extern int discard_index(struct index_state *);
  extern int unmerged_index(const struct index_state *);
  extern int verify_path(const char *path);
 +extern struct cache_entry *index_dir_exists(struct index_state *istate, const char *name, int namelen);
 +extern struct cache_entry *index_file_exists(struct index_state *istate, const char *name, int namelen, int igncase);
  extern struct cache_entry *index_name_exists(struct index_state *istate, const char *name, int namelen, int igncase);
  extern int index_name_pos(const struct index_state *, const char *name, int namelen);
  #define ADD_CACHE_OK_TO_ADD 1         /* Ok to add */
@@@ -488,7 -470,7 +488,7 @@@ extern int remove_file_from_index(struc
  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);
 -extern int ce_same_name(struct cache_entry *a, struct cache_entry *b);
 +extern int ce_same_name(const struct cache_entry *a, const struct cache_entry *b);
  extern int index_name_is_other(const struct index_state *, const char *, int);
  extern void *read_blob_data_from_index(struct index_state *, const char *, unsigned long *);
  
  #define CE_MATCH_RACY_IS_DIRTY                02
  /* do stat comparison even if CE_SKIP_WORKTREE is true */
  #define CE_MATCH_IGNORE_SKIP_WORKTREE 04
 -extern int ie_match_stat(const struct index_state *, struct cache_entry *, struct stat *, unsigned int);
 -extern int ie_modified(const struct index_state *, struct cache_entry *, struct stat *, unsigned int);
 -
 -#define PATHSPEC_ONESTAR 1    /* the pathspec pattern sastisfies GFNM_ONESTAR */
 -
 -struct pathspec {
 -      const char **raw; /* get_pathspec() result, not freed by free_pathspec() */
 -      int nr;
 -      unsigned int has_wildcard:1;
 -      unsigned int recursive:1;
 -      int max_depth;
 -      struct pathspec_item {
 -              const char *match;
 -              int len;
 -              int nowildcard_len;
 -              int flags;
 -      } *items;
 -};
 +extern int ie_match_stat(const struct index_state *, const struct cache_entry *, struct stat *, unsigned int);
 +extern int ie_modified(const struct index_state *, const struct cache_entry *, struct stat *, unsigned int);
  
 -extern int init_pathspec(struct pathspec *, const char **);
 -extern void free_pathspec(struct pathspec *);
  extern int ce_path_match(const struct cache_entry *ce, const struct pathspec *pathspec);
  
 -extern int limit_pathspec_to_literal(void);
 -
  #define HASH_WRITE_OBJECT 1
  #define HASH_FORMAT_CHECK 2
  extern int index_fd(unsigned char *sha1, int fd, struct stat *st, enum object_type type, const char *path, unsigned flags);
  extern int index_path(unsigned char *sha1, const char *path, struct stat *st, unsigned flags);
 +
 +/*
 + * Record to sd the data from st that we use to check whether a file
 + * might have changed.
 + */
 +extern void fill_stat_data(struct stat_data *sd, struct stat *st);
 +
 +/*
 + * Return 0 if st is consistent with a file not having been changed
 + * since sd was filled.  If there are differences, return a
 + * combination of MTIME_CHANGED, CTIME_CHANGED, OWNER_CHANGED,
 + * INODE_CHANGED, and DATA_CHANGED.
 + */
 +extern int match_stat_data(const struct stat_data *sd, struct stat *st);
 +
  extern void fill_stat_cache_info(struct cache_entry *ce, struct stat *st);
  
  #define REFRESH_REALLY                0x0001  /* ignore_valid */
  #define REFRESH_IGNORE_MISSING        0x0008  /* ignore non-existent */
  #define REFRESH_IGNORE_SUBMODULES     0x0010  /* ignore submodules */
  #define REFRESH_IN_PORCELAIN  0x0020  /* user friendly output, not "needs update" */
 -extern int refresh_index(struct index_state *, unsigned int flags, const char **pathspec, char *seen, const char *header_msg);
 +extern int refresh_index(struct index_state *, unsigned int flags, const struct pathspec *pathspec, char *seen, const char *header_msg);
  
  struct lock_file {
        struct lock_file *next;
@@@ -567,7 -554,6 +567,7 @@@ extern int assume_unchanged
  extern int prefer_symlink_refs;
  extern int log_all_ref_updates;
  extern int warn_ambiguous_refs;
 +extern int warn_on_object_refname_ambiguity;
  extern int shared_repository;
  extern const char *apply_default_whitespace;
  extern const char *apply_default_ignorewhitespace;
@@@ -751,8 -737,8 +751,9 @@@ int is_directory(const char *)
  const char *real_path(const char *path);
  const char *real_path_if_valid(const char *path);
  const char *absolute_path(const char *path);
+ const char *remove_leading_path(const char *in, const char *prefix);
  const char *relative_path(const char *in, const char *prefix, struct strbuf *sb);
 +int normalize_path_copy_len(char *dst, const char *src, int *prefix_len);
  int normalize_path_copy(char *dst, const char *src);
  int longest_ancestor_length(const char *path, struct string_list *prefixes);
  char *strip_path_suffix(const char *path, const char *suffix);
@@@ -787,6 -773,9 +788,6 @@@ extern int parse_sha1_header(const cha
  /* global flag to enable extra checks when accessing packed objects */
  extern int do_check_packed_object_crc;
  
 -/* for development: log offset of pack access */
 -extern const char *log_pack_access;
 -
  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);
@@@ -884,7 -873,7 +885,7 @@@ extern char *resolve_refdup(const char 
  
  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, struct strbuf *);
 +extern int interpret_branch_name(const char *str, int len, struct strbuf *);
  extern int get_sha1_mb(const char *str, unsigned char *sha1);
  
  extern int refname_match(const char *abbrev_name, const char *full_name, const char **rules);
@@@ -922,7 -911,6 +923,7 @@@ void show_date_relative(unsigned long t
                        struct strbuf *timebuf);
  int parse_date(const char *date, char *buf, int bufsize);
  int parse_date_basic(const char *date, unsigned long *timestamp, int *offset);
 +int parse_expiry_date(const char *date, unsigned long *timestamp);
  void datestamp(char *buf, int bufsize);
  #define approxidate(s) approxidate_careful((s), NULL)
  unsigned long approxidate_careful(const char *, int *);
@@@ -957,15 -945,6 +958,15 @@@ struct ident_split 
   */
  extern int split_ident_line(struct ident_split *, const char *, int);
  
 +/*
 + * Compare split idents for equality or strict ordering. Note that we
 + * compare only the ident part of the line, ignoring any timestamp.
 + *
 + * Because there are two fields, we must choose one as the primary key; we
 + * currently arbitrarily pick the email.
 + */
 +extern int ident_cmp(const struct ident_split *, const struct ident_split *);
 +
  struct checkout {
        const char *base_dir;
        int base_dir_len;
@@@ -1038,6 -1017,56 +1039,6 @@@ struct pack_entry 
        struct packed_git *p;
  };
  
 -struct ref {
 -      struct ref *next;
 -      unsigned char old_sha1[20];
 -      unsigned char new_sha1[20];
 -      char *symref;
 -      unsigned int
 -              force:1,
 -              forced_update:1,
 -              merge:1,
 -              deletion:1,
 -              matched:1;
 -      enum {
 -              REF_STATUS_NONE = 0,
 -              REF_STATUS_OK,
 -              REF_STATUS_REJECT_NONFASTFORWARD,
 -              REF_STATUS_REJECT_ALREADY_EXISTS,
 -              REF_STATUS_REJECT_NODELETE,
 -              REF_STATUS_REJECT_FETCH_FIRST,
 -              REF_STATUS_REJECT_NEEDS_FORCE,
 -              REF_STATUS_UPTODATE,
 -              REF_STATUS_REMOTE_REJECT,
 -              REF_STATUS_EXPECTING_REPORT
 -      } status;
 -      char *remote_status;
 -      struct ref *peer_ref; /* when renaming */
 -      char name[FLEX_ARRAY]; /* more */
 -};
 -
 -#define REF_NORMAL    (1u << 0)
 -#define REF_HEADS     (1u << 1)
 -#define REF_TAGS      (1u << 2)
 -
 -extern struct ref *find_ref_by_name(const struct ref *list, const char *name);
 -
 -#define CONNECT_VERBOSE       (1u << 0)
 -extern struct child_process *git_connect(int fd[2], const char *url, const char *prog, int flags);
 -extern int finish_connect(struct child_process *conn);
 -extern int git_connection_is_socket(struct child_process *conn);
 -struct extra_have_objects {
 -      int nr, alloc;
 -      unsigned char (*array)[20];
 -};
 -extern struct ref **get_remote_heads(int in, char *src_buf, size_t src_len,
 -                                   struct ref **list, unsigned int flags,
 -                                   struct extra_have_objects *);
 -extern int server_supports(const char *feature);
 -extern int parse_feature_request(const char *features, const char *feature);
 -extern const char *server_feature_value(const char *feature, int *len_ret);
 -extern const char *parse_feature_value(const char *feature_list, const char *feature, int *len_ret);
 -
  extern struct packed_git *parse_pack_index(unsigned char *sha1, const char *idx_path);
  
  /* A hook for count-objects to report invalid files in pack directory */
@@@ -1070,9 -1099,7 +1071,9 @@@ extern int unpack_object_header(struct 
  
  struct object_info {
        /* Request */
 +      enum object_type *typep;
        unsigned long *sizep;
 +      unsigned long *disk_sizep;
  
        /* Response */
        enum {
@@@ -1116,19 -1143,14 +1117,19 @@@ extern int update_server_info(int)
  typedef int (*config_fn_t)(const char *, const char *, void *);
  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_from_buf(config_fn_t fn, const char *name,
 +                             const char *buf, size_t len, void *data);
  extern void git_config_push_parameter(const char *text);
  extern int git_config_from_parameters(config_fn_t fn, void *data);
  extern int git_config(config_fn_t fn, void *);
  extern int git_config_with_options(config_fn_t fn, void *,
 -                                 const char *filename, int respect_includes);
 +                                 const char *filename,
 +                                 const char *blob_ref,
 +                                 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_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 *);
  extern int git_config_bool_or_int(const char *, const char *, int *);
  extern int git_config_bool(const char *, const char *);
@@@ -1244,7 -1266,7 +1245,7 @@@ void packet_trace_identity(const char *
   * return 0 if success, 1 - if addition of a file failed and
   * ADD_FILES_IGNORE_ERRORS was specified in flags
   */
 -int add_files_to_cache(const char *prefix, const char **pathspec, int flags);
 +int add_files_to_cache(const char *prefix, const struct pathspec *pathspec, int flags);
  
  /* diff.c */
  extern int diff_auto_refresh_index;
@@@ -1278,7 -1300,7 +1279,7 @@@ extern int ws_blank_line(const char *li
  #define ws_tab_width(rule)     ((rule) & WS_TAB_WIDTH_MASK)
  
  /* ls-files */
 -int report_path_error(const char *ps_matched, const char **pathspec, const char *prefix);
 +int report_path_error(const char *ps_matched, const struct pathspec *pathspec, const char *prefix);
  void overlay_tree_on_cache(const char *tree_name, const char *prefix);
  
  char *alias_lookup(const char *alias);
@@@ -1305,31 -1327,4 +1306,31 @@@ int checkout_fast_forward(const unsigne
  
  int sane_execvp(const char *file, char *const argv[]);
  
 +/*
 + * A struct to encapsulate the concept of whether a file has changed
 + * since we last checked it. This uses criteria similar to those used
 + * for the index.
 + */
 +struct stat_validity {
 +      struct stat_data *sd;
 +};
 +
 +void stat_validity_clear(struct stat_validity *sv);
 +
 +/*
 + * Returns 1 if the path is a regular file (or a symlink to a regular
 + * file) and matches the saved stat_validity, 0 otherwise.  A missing
 + * or inaccessible file is considered a match if the struct was just
 + * initialized, or if the previous update found an inaccessible file.
 + */
 +int stat_validity_check(struct stat_validity *sv, const char *path);
 +
 +/*
 + * Update the stat_validity from a file opened at descriptor fd. If
 + * the file is missing, inaccessible, or not a regular file, then
 + * future calls to stat_validity_check will match iff one of those
 + * conditions continues to be true.
 + */
 +void stat_validity_update(struct stat_validity *sv, int fd);
 +
  #endif /* CACHE_H */
diff --combined path.c
index 9fd28bcd080ef0a64f0b8d8a5d9d680a741d75c3,fa62da58ed9ad21ef47a679f8522add5a548e4e0..24594c41120bebab1e04f5bc4bd8fc484ea71b97
--- 1/path.c
--- 2/path.c
+++ b/path.c
@@@ -5,7 -5,13 +5,7 @@@
  #include "strbuf.h"
  #include "string-list.h"
  
 -#ifndef get_st_mode_bits
 -/*
 - * The replacement lstat(2) we use on Cygwin is incomplete and
 - * may return wrong permission bits. Most of the time we do not care,
 - * but the callsites of this wrapper do care.
 - */
 -int get_st_mode_bits(const char *path, int *mode)
 +static int get_st_mode_bits(const char *path, int *mode)
  {
        struct stat st;
        if (lstat(path, &st) < 0)
@@@ -13,6 -19,7 +13,6 @@@
        *mode = st.st_mode;
        return 0;
  }
 -#endif
  
  static char bad_path[] = "/bad-path/";
  
@@@ -434,6 -441,16 +434,16 @@@ int adjust_shared_perm(const char *path
        return 0;
  }
  
+ static int have_same_root(const char *path1, const char *path2)
+ {
+       int is_abs1, is_abs2;
+       is_abs1 = is_absolute_path(path1);
+       is_abs2 = is_absolute_path(path2);
+       return (is_abs1 && is_abs2 && tolower(path1[0]) == tolower(path2[0])) ||
+              (!is_abs1 && !is_abs2);
+ }
  /*
   * Give path as relative to prefix.
   *
@@@ -454,6 -471,16 +464,16 @@@ const char *relative_path(const char *i
        else if (!prefix_len)
                return in;
  
+       if (have_same_root(in, prefix)) {
+               /* bypass dos_drive, for "c:" is identical to "C:" */
+               if (has_dos_drive_prefix(in)) {
+                       i = 2;
+                       j = 2;
+               }
+       } else {
+               return in;
+       }
        while (i < prefix_len && j < in_len && prefix[i] == in[j]) {
                if (is_dir_sep(prefix[i])) {
                        while (is_dir_sep(prefix[i]))
        return sb->buf;
  }
  
+ /*
+  * A simpler implementation of relative_path
+  *
+  * Get relative path by removing "prefix" from "in". This function
+  * first appears in v1.5.6-1-g044bbbc, and makes git_dir shorter
+  * to increase performance when traversing the path to work_tree.
+  */
+ const char *remove_leading_path(const char *in, const char *prefix)
+ {
+       static char buf[PATH_MAX + 1];
+       int i = 0, j = 0;
+       if (!prefix || !prefix[0])
+               return in;
+       while (prefix[i]) {
+               if (is_dir_sep(prefix[i])) {
+                       if (!is_dir_sep(in[j]))
+                               return in;
+                       while (is_dir_sep(prefix[i]))
+                               i++;
+                       while (is_dir_sep(in[j]))
+                               j++;
+                       continue;
+               } else if (in[j] != prefix[i]) {
+                       return in;
+               }
+               i++;
+               j++;
+       }
+       if (
+           /* "/foo" is a prefix of "/foo" */
+           in[j] &&
+           /* "/foo" is not a prefix of "/foobar" */
+           !is_dir_sep(prefix[i-1]) && !is_dir_sep(in[j])
+          )
+               return in;
+       while (is_dir_sep(in[j]))
+               j++;
+       if (!in[j])
+               strcpy(buf, ".");
+       else
+               strcpy(buf, in + j);
+       return buf;
+ }
  /*
   * It is okay if dst == src, but they should not overlap otherwise.
   *
   *
   * Note that this function is purely textual.  It does not follow symlinks,
   * verify the existence of the path, or make any system calls.
 + *
 + * prefix_len != NULL is for a specific case of prefix_pathspec():
 + * assume that src == dst and src[0..prefix_len-1] is already
 + * normalized, any time "../" eats up to the prefix_len part,
 + * prefix_len is reduced. In the end prefix_len is the remaining
 + * prefix that has not been overridden by user pathspec.
   */
 -int normalize_path_copy(char *dst, const char *src)
 +int normalize_path_copy_len(char *dst, const char *src, int *prefix_len)
  {
        char *dst0;
  
                /* Windows: dst[-1] cannot be backslash anymore */
                while (dst0 < dst && dst[-1] != '/')
                        dst--;
 +              if (prefix_len && *prefix_len > dst - dst0)
 +                      *prefix_len = dst - dst0;
        }
        *dst = '\0';
        return 0;
  }
  
 +int normalize_path_copy(char *dst, const char *src)
 +{
 +      return normalize_path_copy_len(dst, src, NULL);
 +}
 +
  /*
   * path = Canonical absolute path
   * prefixes = string_list containing normalized, absolute paths without
diff --combined setup.c
index f08dd649761f4d53abef66da052340a3c78d0fe5,dad39c1abaac9a749f899623927722f648259133..dbf41387204766981ebda444bc56a0884f9341f5
+++ b/setup.c
@@@ -5,19 -5,7 +5,19 @@@
  static int inside_git_dir = -1;
  static int inside_work_tree = -1;
  
 -static char *prefix_path_gently(const char *prefix, int len, const char *path)
 +/*
 + * Normalize "path", prepending the "prefix" for relative paths. If
 + * remaining_prefix is not NULL, return the actual prefix still
 + * remains in the path. For example, prefix = sub1/sub2/ and path is
 + *
 + *  foo          -> sub1/sub2/foo  (full prefix)
 + *  ../foo       -> sub1/foo       (remaining prefix is sub1/)
 + *  ../../bar    -> bar            (no remaining prefix)
 + *  ../../sub1/sub2/foo -> sub1/sub2/foo (but no remaining prefix)
 + *  `pwd`/../bar -> sub1/bar       (no remaining prefix)
 + */
 +char *prefix_path_gently(const char *prefix, int len,
 +                       int *remaining_prefix, const char *path)
  {
        const char *orig = path;
        char *sanitized;
                const char *temp = real_path(path);
                sanitized = xmalloc(len + strlen(temp) + 1);
                strcpy(sanitized, temp);
 +              if (remaining_prefix)
 +                      *remaining_prefix = 0;
        } else {
                sanitized = xmalloc(len + strlen(path) + 1);
                if (len)
                        memcpy(sanitized, prefix, len);
                strcpy(sanitized + len, path);
 +              if (remaining_prefix)
 +                      *remaining_prefix = len;
        }
 -      if (normalize_path_copy(sanitized, sanitized))
 +      if (normalize_path_copy_len(sanitized, sanitized, remaining_prefix))
                goto error_out;
        if (is_absolute_path(orig)) {
                size_t root_len, len, total;
@@@ -60,7 -44,7 +60,7 @@@
  
  char *prefix_path(const char *prefix, int len, const char *path)
  {
 -      char *r = prefix_path_gently(prefix, len, path);
 +      char *r = prefix_path_gently(prefix, len, NULL, path);
        if (!r)
                die("'%s' is outside repository", path);
        return r;
@@@ -69,7 -53,7 +69,7 @@@
  int path_inside_repo(const char *prefix, const char *path)
  {
        int len = prefix ? strlen(prefix) : 0;
 -      char *r = prefix_path_gently(prefix, len, path);
 +      char *r = prefix_path_gently(prefix, len, NULL, path);
        if (r) {
                free(r);
                return 1;
@@@ -170,6 -154,155 +170,6 @@@ void verify_non_filename(const char *pr
            "'git <command> [<revision>...] -- [<file>...]'", arg);
  }
  
 -/*
 - * Magic pathspec
 - *
 - * NEEDSWORK: These need to be moved to dir.h or even to a new
 - * pathspec.h when we restructure get_pathspec() users to use the
 - * "struct pathspec" interface.
 - *
 - * Possible future magic semantics include stuff like:
 - *
 - *    { PATHSPEC_NOGLOB, '!', "noglob" },
 - *    { PATHSPEC_ICASE, '\0', "icase" },
 - *    { PATHSPEC_RECURSIVE, '*', "recursive" },
 - *    { PATHSPEC_REGEXP, '\0', "regexp" },
 - *
 - */
 -#define PATHSPEC_FROMTOP    (1<<0)
 -
 -static struct pathspec_magic {
 -      unsigned bit;
 -      char mnemonic; /* this cannot be ':'! */
 -      const char *name;
 -} pathspec_magic[] = {
 -      { PATHSPEC_FROMTOP, '/', "top" },
 -};
 -
 -/*
 - * Take an element of a pathspec and check for magic signatures.
 - * Append the result to the prefix.
 - *
 - * For now, we only parse the syntax and throw out anything other than
 - * "top" magic.
 - *
 - * NEEDSWORK: This needs to be rewritten when we start migrating
 - * get_pathspec() users to use the "struct pathspec" interface.  For
 - * example, a pathspec element may be marked as case-insensitive, but
 - * the prefix part must always match literally, and a single stupid
 - * string cannot express such a case.
 - */
 -static const char *prefix_pathspec(const char *prefix, int prefixlen, const char *elt)
 -{
 -      unsigned magic = 0;
 -      const char *copyfrom = elt;
 -      int i;
 -
 -      if (elt[0] != ':') {
 -              ; /* nothing to do */
 -      } else if (elt[1] == '(') {
 -              /* longhand */
 -              const char *nextat;
 -              for (copyfrom = elt + 2;
 -                   *copyfrom && *copyfrom != ')';
 -                   copyfrom = nextat) {
 -                      size_t len = strcspn(copyfrom, ",)");
 -                      if (copyfrom[len] == ',')
 -                              nextat = copyfrom + len + 1;
 -                      else
 -                              /* handle ')' and '\0' */
 -                              nextat = copyfrom + len;
 -                      if (!len)
 -                              continue;
 -                      for (i = 0; i < ARRAY_SIZE(pathspec_magic); i++)
 -                              if (strlen(pathspec_magic[i].name) == len &&
 -                                  !strncmp(pathspec_magic[i].name, copyfrom, len)) {
 -                                      magic |= pathspec_magic[i].bit;
 -                                      break;
 -                              }
 -                      if (ARRAY_SIZE(pathspec_magic) <= i)
 -                              die("Invalid pathspec magic '%.*s' in '%s'",
 -                                  (int) len, copyfrom, elt);
 -              }
 -              if (*copyfrom != ')')
 -                      die("Missing ')' at the end of pathspec magic in '%s'", elt);
 -              copyfrom++;
 -      } else {
 -              /* shorthand */
 -              for (copyfrom = elt + 1;
 -                   *copyfrom && *copyfrom != ':';
 -                   copyfrom++) {
 -                      char ch = *copyfrom;
 -
 -                      if (!is_pathspec_magic(ch))
 -                              break;
 -                      for (i = 0; i < ARRAY_SIZE(pathspec_magic); i++)
 -                              if (pathspec_magic[i].mnemonic == ch) {
 -                                      magic |= pathspec_magic[i].bit;
 -                                      break;
 -                              }
 -                      if (ARRAY_SIZE(pathspec_magic) <= i)
 -                              die("Unimplemented pathspec magic '%c' in '%s'",
 -                                  ch, elt);
 -              }
 -              if (*copyfrom == ':')
 -                      copyfrom++;
 -      }
 -
 -      if (magic & PATHSPEC_FROMTOP)
 -              return xstrdup(copyfrom);
 -      else
 -              return prefix_path(prefix, prefixlen, copyfrom);
 -}
 -
 -/*
 - * N.B. get_pathspec() is deprecated in favor of the "struct pathspec"
 - * based interface - see pathspec_magic above.
 - *
 - * Arguments:
 - *  - prefix - a path relative to the root of the working tree
 - *  - pathspec - a list of paths underneath the prefix path
 - *
 - * Iterates over pathspec, prepending each path with prefix,
 - * and return the resulting list.
 - *
 - * If pathspec is empty, return a singleton list containing prefix.
 - *
 - * If pathspec and prefix are both empty, return an empty list.
 - *
 - * This is typically used by built-in commands such as add.c, in order
 - * to normalize argv arguments provided to the built-in into a list of
 - * paths to process, all relative to the root of the working tree.
 - */
 -const char **get_pathspec(const char *prefix, const char **pathspec)
 -{
 -      const char *entry = *pathspec;
 -      const char **src, **dst;
 -      int prefixlen;
 -
 -      if (!prefix && !entry)
 -              return NULL;
 -
 -      if (!entry) {
 -              static const char *spec[2];
 -              spec[0] = prefix;
 -              spec[1] = NULL;
 -              return spec;
 -      }
 -
 -      /* Otherwise we have to re-write the entries.. */
 -      src = pathspec;
 -      dst = pathspec;
 -      prefixlen = prefix ? strlen(prefix) : 0;
 -      while (*src) {
 -              *(dst++) = prefix_pathspec(prefix, prefixlen, *src);
 -              src++;
 -      }
 -      *dst = NULL;
 -      if (!*pathspec)
 -              return NULL;
 -      return pathspec;
 -}
  
  /*
   * Test if it looks like we're at a git directory.
@@@ -227,7 -360,6 +227,6 @@@ int is_inside_work_tree(void
  
  void setup_work_tree(void)
  {
-       struct strbuf sb = STRBUF_INIT;
        const char *work_tree, *git_dir;
        static int initialized = 0;
  
        if (getenv(GIT_WORK_TREE_ENVIRONMENT))
                setenv(GIT_WORK_TREE_ENVIRONMENT, ".", 1);
  
-       set_git_dir(relative_path(git_dir, work_tree, &sb));
+       set_git_dir(remove_leading_path(git_dir, work_tree));
        initialized = 1;
-       strbuf_release(&sb);
  }
  
  static int check_repository_format_gently(const char *gitdir, int *nongit_ok)
@@@ -778,15 -908,3 +775,15 @@@ const char *resolve_gitdir(const char *
                return suspect;
        return read_gitfile(suspect);
  }
 +
 +/* if any standard file descriptor is missing open it to /dev/null */
 +void sanitize_stdfds(void)
 +{
 +      int fd = open("/dev/null", O_RDWR, 0);
 +      while (fd != -1 && fd < 2)
 +              fd = dup(fd);
 +      if (fd == -1)
 +              die_errno("open /dev/null or dup failed");
 +      if (fd > 2)
 +              close(fd);
 +}
diff --combined t/t0060-path-utils.sh
index 2bd5e3274549c7a50375e90e58fa5a4dc393491d,40dfa2df6ebaa7c459ad2d440242bc028dc6f59e..07c10c8dca80b9d289431a72d5bf120ca442113d
@@@ -8,13 -8,13 +8,13 @@@ test_description='Test various path uti
  . ./test-lib.sh
  
  norm_path() {
 -      expected=$(test-path-utils mingw_path "$2")
 +      expected=$(test-path-utils print_path "$2")
        test_expect_success $3 "normalize path: $1 => $2" \
        "test \"\$(test-path-utils normalize_path_copy '$1')\" = '$expected'"
  }
  
  relative_path() {
 -      expected=$(test-path-utils mingw_path "$3")
 +      expected=$(test-path-utils print_path "$3")
        test_expect_success $4 "relative path: $1 $2 => $3" \
        "test \"\$(test-path-utils relative_path '$1' '$2')\" = '$expected'"
  }
@@@ -190,33 -190,37 +190,37 @@@ test_expect_success SYMLINKS 'real pat
        test "$sym" = "$(test-path-utils real_path "$dir2/syml")"
  '
  
- relative_path /a/b/c/ /a/b/           c/
- relative_path /a/b/c/ /a/b            c/
- relative_path /a//b//c/       //a/b//         c/      POSIX
- relative_path /a/b    /a/b            ./
- relative_path /a/b/   /a/b            ./
- relative_path /a      /a/b            ../
- relative_path /               /a/b/           ../../
- relative_path /a/c    /a/b/           ../c
- relative_path /a/c    /a/b            ../c
- relative_path /x/y    /a/b/           ../../x/y
- relative_path /a/b    "<empty>"       /a/b
- relative_path /a/b    "<null>"        /a/b
- relative_path a/b/c/  a/b/            c/
- relative_path a/b/c/  a/b             c/
- relative_path a/b//c  a//b            c
- relative_path a/b/    a/b/            ./
- relative_path a/b/    a/b             ./
- relative_path a               a/b             ../
- relative_path x/y     a/b             ../../x/y
- relative_path a/c     a/b             ../c
- relative_path a/b     "<empty>"       a/b
- relative_path a/b     "<null>"        a/b
- relative_path "<empty>"       /a/b            ./
- relative_path "<empty>"       "<empty>"       ./
- relative_path "<empty>"       "<null>"        ./
- relative_path "<null>"        "<empty>"       ./
- relative_path "<null>"        "<null>"        ./
- relative_path "<null>"        /a/b            ./
+ relative_path /foo/a/b/c/     /foo/a/b/       c/
+ relative_path /foo/a/b/c/     /foo/a/b        c/
+ relative_path /foo/a//b//c/   ///foo/a/b//    c/              POSIX
+ relative_path /foo/a/b                /foo/a/b        ./
+ relative_path /foo/a/b/               /foo/a/b        ./
+ relative_path /foo/a          /foo/a/b        ../
+ relative_path /                       /foo/a/b/       ../../../
+ relative_path /foo/a/c                /foo/a/b/       ../c
+ relative_path /foo/a/c                /foo/a/b        ../c
+ relative_path /foo/x/y                /foo/a/b/       ../../x/y
+ relative_path /foo/a/b                "<empty>"       /foo/a/b
+ relative_path /foo/a/b                "<null>"        /foo/a/b
+ relative_path foo/a/b/c/      foo/a/b/        c/
+ relative_path foo/a/b/c/      foo/a/b         c/
+ relative_path foo/a/b//c      foo/a//b        c
+ relative_path foo/a/b/                foo/a/b/        ./
+ relative_path foo/a/b/                foo/a/b         ./
+ relative_path foo/a           foo/a/b         ../
+ relative_path foo/x/y         foo/a/b         ../../x/y
+ relative_path foo/a/c         foo/a/b         ../c
+ relative_path foo/a/b         /foo/x/y        foo/a/b
+ relative_path /foo/a/b                foo/x/y         /foo/a/b
+ relative_path d:/a/b          D:/a/c          ../b            MINGW
+ relative_path C:/a/b          D:/a/c          C:/a/b          MINGW
+ relative_path foo/a/b         "<empty>"       foo/a/b
+ relative_path foo/a/b                 "<null>"        foo/a/b
+ relative_path "<empty>"               /foo/a/b        ./
+ relative_path "<empty>"               "<empty>"       ./
+ relative_path "<empty>"               "<null>"        ./
+ relative_path "<null>"                "<empty>"       ./
+ relative_path "<null>"                "<null>"        ./
+ relative_path "<null>"                /foo/a/b        ./
  
  test_done