Merge branch 'jc/add-addremove'
authorJunio C Hamano <gitster@pobox.com>
Wed, 27 Aug 2008 23:39:57 +0000 (16:39 -0700)
committerJunio C Hamano <gitster@pobox.com>
Wed, 27 Aug 2008 23:39:57 +0000 (16:39 -0700)
* jc/add-addremove:
builtin-add.c: optimize -A option and "git add ."
builtin-add.c: restructure the code for maintainability

1  2 
builtin-add.c
cache.h
read-cache.c
diff --combined builtin-add.c
index 81b64d7b9d69f06fff70870e8a55fc4ad506133e,1834e2d7cd50ec7688ec6c5c7d2c79c8513ed132..7c874e31154a4c3f96b3403db1bdcb0b36fdec3e
@@@ -8,10 -8,6 +8,6 @@@
  #include "dir.h"
  #include "exec_cmd.h"
  #include "cache-tree.h"
- #include "diff.h"
- #include "diffcore.h"
- #include "commit.h"
- #include "revision.h"
  #include "run-command.h"
  #include "parse-options.h"
  
@@@ -22,6 -18,27 +18,27 @@@ static const char * const builtin_add_u
  static int patch_interactive = 0, add_interactive = 0;
  static int take_worktree_changes;
  
+ static void fill_pathspec_matches(const char **pathspec, char *seen, int specs)
+ {
+       int num_unmatched = 0, i;
+       /*
+        * Since we are walking the index as if we are warlking the directory,
+        * we have to mark the matched pathspec as seen; otherwise we will
+        * mistakenly think that the user gave a pathspec that did not match
+        * anything.
+        */
+       for (i = 0; i < specs; i++)
+               if (!seen[i])
+                       num_unmatched++;
+       if (!num_unmatched)
+               return;
+       for (i = 0; i < active_nr; i++) {
+               struct cache_entry *ce = active_cache[i];
+               match_pathspec(pathspec, ce->name, ce_namelen(ce), 0, seen);
+       }
+ }
  static void prune_directory(struct dir_struct *dir, const char **pathspec, int prefix)
  {
        char *seen;
@@@ -41,6 -58,7 +58,7 @@@
                        *dst++ = entry;
        }
        dir->nr = dst - dir->entries;
+       fill_pathspec_matches(pathspec, seen, specs);
  
        for (i = 0; i < specs; i++) {
                if (!seen[i] && !file_exists(pathspec[i]))
@@@ -79,59 -97,6 +97,6 @@@ static void fill_directory(struct dir_s
                prune_directory(dir, pathspec, baselen);
  }
  
- struct update_callback_data
- {
-       int flags;
-       int add_errors;
- };
- static void update_callback(struct diff_queue_struct *q,
-                           struct diff_options *opt, void *cbdata)
- {
-       int i;
-       struct update_callback_data *data = cbdata;
-       for (i = 0; i < q->nr; i++) {
-               struct diff_filepair *p = q->queue[i];
-               const char *path = p->one->path;
-               switch (p->status) {
-               default:
-                       die("unexpected diff status %c", p->status);
-               case DIFF_STATUS_UNMERGED:
-               case DIFF_STATUS_MODIFIED:
-               case DIFF_STATUS_TYPE_CHANGED:
-                       if (add_file_to_cache(path, data->flags)) {
-                               if (!(data->flags & ADD_CACHE_IGNORE_ERRORS))
-                                       die("updating files failed");
-                               data->add_errors++;
-                       }
-                       break;
-               case DIFF_STATUS_DELETED:
-                       if (!(data->flags & ADD_CACHE_PRETEND))
-                               remove_file_from_cache(path);
-                       if (data->flags & (ADD_CACHE_PRETEND|ADD_CACHE_VERBOSE))
-                               printf("remove '%s'\n", path);
-                       break;
-               }
-       }
- }
- int add_files_to_cache(const char *prefix, const char **pathspec, int flags)
- {
-       struct update_callback_data data;
-       struct rev_info rev;
-       init_revisions(&rev, prefix);
-       setup_revisions(0, NULL, &rev, NULL);
-       rev.prune_data = pathspec;
-       rev.diffopt.output_format = DIFF_FORMAT_CALLBACK;
-       rev.diffopt.format_callback = update_callback;
-       data.flags = flags;
-       data.add_errors = 0;
-       rev.diffopt.format_callback_data = &data;
-       run_diff_files(&rev, DIFF_RACY_IS_MODIFIED);
-       return !!data.add_errors;
- }
  static void refresh(int verbose, const char **pathspec)
  {
        char *seen;
@@@ -153,16 -118,6 +118,16 @@@ static const char **validate_pathspec(i
  {
        const char **pathspec = get_pathspec(prefix, argv);
  
 +      if (pathspec) {
 +              const char **p;
 +              for (p = pathspec; *p; p++) {
 +                      if (has_symlink_leading_path(strlen(*p), *p)) {
 +                              int len = prefix ? strlen(prefix) : 0;
 +                              die("'%s' is beyond a symbolic link", *p + len);
 +                      }
 +              }
 +      }
 +
        return pathspec;
  }
  
@@@ -268,7 -223,7 +233,7 @@@ int cmd_add(int argc, const char **argv
  
        if (addremove && take_worktree_changes)
                die("-A and -u are mutually incompatible");
-       if (addremove && !argc) {
+       if ((addremove || take_worktree_changes) && !argc) {
                static const char *here[2] = { ".", NULL };
                argc = 1;
                argv = here;
  
        flags = ((verbose ? ADD_CACHE_VERBOSE : 0) |
                 (show_only ? ADD_CACHE_PRETEND : 0) |
-                (ignore_add_errors ? ADD_CACHE_IGNORE_ERRORS : 0));
+                (ignore_add_errors ? ADD_CACHE_IGNORE_ERRORS : 0) |
+                (!(addremove || take_worktree_changes)
+                 ? ADD_CACHE_IGNORE_REMOVAL : 0));
  
        if (require_pathspec && argc == 0) {
                fprintf(stderr, "Nothing specified, nothing added.\n");
                fprintf(stderr, "Maybe you wanted to say 'git add .'?\n");
                return 0;
        }
 -      pathspec = get_pathspec(prefix, argv);
 +      pathspec = validate_pathspec(argc, argv, prefix);
  
-       /*
-        * If we are adding new files, we need to scan the working
-        * tree to find the ones that match pathspecs; this needs
-        * to be done before we read the index.
-        */
-       if (add_new_files)
-               fill_directory(&dir, pathspec, ignored_too);
        if (read_cache() < 0)
                die("index file corrupt");
  
+       if (add_new_files)
+               /* This picks up the paths that are not tracked */
+               fill_directory(&dir, pathspec, ignored_too);
        if (refresh_only) {
                refresh(verbose, pathspec);
                goto finish;
        }
  
-       if (take_worktree_changes || addremove)
-               exit_status |= add_files_to_cache(prefix, pathspec, flags);
+       exit_status |= add_files_to_cache(prefix, pathspec, flags);
  
        if (add_new_files)
                exit_status |= add_files(&dir, flags);
diff --combined cache.h
index ab9f97efc6be1e40a59e03463aa921d339420f6c,6f374add882e421a952304a77e6f4999c2d814c5..de8c2b6266ee96d0958cb2c1430b2b2da99f1e86
+++ b/cache.h
@@@ -126,7 -126,6 +126,7 @@@ struct cache_entry 
  
  #define CE_NAMEMASK  (0x0fff)
  #define CE_STAGEMASK (0x3000)
 +#define CE_EXTENDED  (0x4000)
  #define CE_VALID     (0x8000)
  #define CE_STAGESHIFT 12
  
@@@ -223,8 -222,7 +223,8 @@@ struct index_state 
        struct cache_tree *cache_tree;
        time_t timestamp;
        void *alloc;
 -      unsigned name_hash_initialized : 1;
 +      unsigned name_hash_initialized : 1,
 +               initialized : 1;
        struct hash_table name_hash;
  };
  
@@@ -262,7 -260,6 +262,7 @@@ static inline void remove_name_hash(str
  #define unmerged_cache() unmerged_index(&the_index)
  #define cache_name_pos(name, namelen) index_name_pos(&the_index,(name),(namelen))
  #define add_cache_entry(ce, option) add_index_entry(&the_index, (ce), (option))
 +#define rename_cache_entry_at(pos, new_name) rename_index_entry_at(&the_index, (pos), (new_name))
  #define remove_cache_entry_at(pos) remove_index_entry_at(&the_index, (pos))
  #define remove_file_from_cache(path) remove_file_from_index(&the_index, (path))
  #define add_to_cache(path, st, flags) add_to_index(&the_index, (path), (st), (flags))
@@@ -373,12 -370,12 +373,13 @@@ extern int index_name_pos(const struct 
  #define ADD_CACHE_JUST_APPEND 8               /* Append only; tree.c::read_tree() */
  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);
  extern int remove_index_entry_at(struct index_state *, int pos);
  extern int remove_file_from_index(struct index_state *, const char *path);
  #define ADD_CACHE_VERBOSE 1
  #define ADD_CACHE_PRETEND 2
  #define ADD_CACHE_IGNORE_ERRORS       4
+ #define ADD_CACHE_IGNORE_REMOVAL 8
  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);
@@@ -393,6 -390,7 +394,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);
  
@@@ -424,7 -422,6 +425,7 @@@ extern int delete_ref(const char *, con
  
  /* Environment bits from configuration mechanism */
  extern int trust_executable_bit;
 +extern int trust_ctime;
  extern int quote_path_fully;
  extern int has_symlinks;
  extern int ignore_case;
@@@ -452,7 -449,6 +453,7 @@@ enum safe_crlf 
  extern enum safe_crlf safe_crlf;
  
  enum branch_track {
 +      BRANCH_TRACK_UNSPECIFIED = -1,
        BRANCH_TRACK_NEVER = 0,
        BRANCH_TRACK_REMOTE,
        BRANCH_TRACK_ALWAYS,
diff --combined read-cache.c
index a857c8e12fe4c83e98ccbd772b21fd3e66913ea5,6833af6cf1ab4628849d1d677ccc9aced28aab91..5150c1e14b7384bd443bffb17da2887394237c61
@@@ -8,6 -8,11 +8,11 @@@
  #include "cache-tree.h"
  #include "refs.h"
  #include "dir.h"
+ #include "tree.h"
+ #include "commit.h"
+ #include "diff.h"
+ #include "diffcore.h"
+ #include "revision.h"
  
  /* Index extensions.
   *
@@@ -38,22 -43,6 +43,22 @@@ static void replace_index_entry(struct 
        istate->cache_changed = 1;
  }
  
 +void rename_index_entry_at(struct index_state *istate, int nr, const char *new_name)
 +{
 +      struct cache_entry *old = istate->cache[nr], *new;
 +      int namelen = strlen(new_name);
 +
 +      new = xmalloc(cache_entry_size(namelen));
 +      copy_cache_entry(new, old);
 +      new->ce_flags &= ~(CE_STATE_MASK | CE_NAMEMASK);
 +      new->ce_flags |= (namelen >= CE_NAMEMASK ? CE_NAMEMASK : namelen);
 +      memcpy(new->name, new_name, namelen + 1);
 +
 +      cache_tree_invalidate_path(istate->cache_tree, old->name);
 +      remove_index_entry_at(istate, nr);
 +      add_index_entry(istate, new, ADD_CACHE_OK_TO_ADD|ADD_CACHE_OK_TO_REPLACE);
 +}
 +
  /*
   * This only updates the "non-critical" parts of the directory
   * cache, ie the parts that aren't tracked by GIT, and only used
@@@ -147,7 -136,7 +152,7 @@@ static int ce_modified_check_fs(struct 
                break;
        case S_IFDIR:
                if (S_ISGITLINK(ce->ce_mode))
 -                      return 0;
 +                      return ce_compare_gitlink(ce) ? DATA_CHANGED : 0;
        default:
                return TYPE_CHANGED;
        }
@@@ -187,7 -176,6 +192,7 @@@ static int ce_match_stat_basic(struct c
                        changed |= TYPE_CHANGED;
                break;
        case S_IFGITLINK:
 +              /* We ignore most of the st_xxx fields for gitlinks */
                if (!S_ISDIR(st->st_mode))
                        changed |= TYPE_CHANGED;
                else if (ce_compare_gitlink(ce))
        }
        if (ce->ce_mtime != (unsigned int) st->st_mtime)
                changed |= MTIME_CHANGED;
 -      if (ce->ce_ctime != (unsigned int) st->st_ctime)
 +      if (trust_ctime && ce->ce_ctime != (unsigned int) st->st_ctime)
                changed |= CTIME_CHANGED;
  
        if (ce->ce_uid != (unsigned int) st->st_uid ||
@@@ -294,22 -282,11 +299,22 @@@ int ie_modified(const struct index_stat
        if (changed & (MODE_CHANGED | TYPE_CHANGED))
                return changed;
  
 -      /* Immediately after read-tree or update-index --cacheinfo,
 -       * the length field is zero.  For other cases the ce_size
 -       * should match the SHA1 recorded in the index entry.
 +      /*
 +       * Immediately after read-tree or update-index --cacheinfo,
 +       * the length field is zero, as we have never even read the
 +       * lstat(2) information once, and we cannot trust DATA_CHANGED
 +       * returned by ie_match_stat() which in turn was returned by
 +       * ce_match_stat_basic() to signal that the filesize of the
 +       * blob changed.  We have to actually go to the filesystem to
 +       * see if the contents match, and if so, should answer "unchanged".
 +       *
 +       * The logic does not apply to gitlinks, as ce_match_stat_basic()
 +       * already has checked the actual HEAD from the filesystem in the
 +       * subproject.  If ie_match_stat() already said it is different,
 +       * then we know it is.
         */
 -      if ((changed & DATA_CHANGED) && ce->ce_size != 0)
 +      if ((changed & DATA_CHANGED) &&
 +          (S_ISGITLINK(ce->ce_mode) || ce->ce_size != 0))
                return changed;
  
        changed_fs = ce_modified_check_fs(ce, st);
@@@ -1118,10 -1095,6 +1123,10 @@@ static void convert_from_disk(struct on
        ce->ce_size  = ntohl(ondisk->size);
        /* On-disk flags are just 16 bits */
        ce->ce_flags = ntohs(ondisk->flags);
 +
 +      /* For future extension: we do not understand this entry yet */
 +      if (ce->ce_flags & CE_EXTENDED)
 +              die("Unknown index entry format");
        hashcpy(ce->sha1, ondisk->sha1);
  
        len = ce->ce_flags & CE_NAMEMASK;
@@@ -1159,7 -1132,7 +1164,7 @@@ int read_index_from(struct index_state 
        size_t mmap_size;
  
        errno = EBUSY;
 -      if (istate->alloc)
 +      if (istate->initialized)
                return istate->cache_nr;
  
        errno = ENOENT;
         * index size
         */
        istate->alloc = xmalloc(estimate_cache_size(mmap_size, istate->cache_nr));
 +      istate->initialized = 1;
  
        src_offset = sizeof(*hdr);
        dst_offset = 0;
@@@ -1252,7 -1224,6 +1257,7 @@@ int discard_index(struct index_state *i
        cache_tree_free(&(istate->cache_tree));
        free(istate->alloc);
        istate->alloc = NULL;
 +      istate->initialized = 0;
  
        /* no need to throw away allocated active_cache */
        return 0;
@@@ -1344,11 -1315,6 +1349,11 @@@ static void ce_smudge_racily_clean_entr
         * falsely clean entry due to touch-update-touch race, so we leave
         * everything else as they are.  We are called for entries whose
         * ce_mtime match the index file mtime.
 +       *
 +       * Note that this actually does not do much for gitlinks, for
 +       * which ce_match_stat_basic() always goes to the actual
 +       * contents.  The caller checks with is_racy_timestamp() which
 +       * always says "no" for gitlinks, so we are not called for them ;-)
         */
        struct stat st;
  
@@@ -1483,3 -1449,59 +1488,59 @@@ int read_index_unmerged(struct index_st
        istate->cache_nr = dst - istate->cache;
        return !!last;
  }
+ struct update_callback_data
+ {
+       int flags;
+       int add_errors;
+ };
+ static void update_callback(struct diff_queue_struct *q,
+                           struct diff_options *opt, void *cbdata)
+ {
+       int i;
+       struct update_callback_data *data = cbdata;
+       for (i = 0; i < q->nr; i++) {
+               struct diff_filepair *p = q->queue[i];
+               const char *path = p->one->path;
+               switch (p->status) {
+               default:
+                       die("unexpected diff status %c", p->status);
+               case DIFF_STATUS_UNMERGED:
+               case DIFF_STATUS_MODIFIED:
+               case DIFF_STATUS_TYPE_CHANGED:
+                       if (add_file_to_index(&the_index, path, data->flags)) {
+                               if (!(data->flags & ADD_CACHE_IGNORE_ERRORS))
+                                       die("updating files failed");
+                               data->add_errors++;
+                       }
+                       break;
+               case DIFF_STATUS_DELETED:
+                       if (data->flags & ADD_CACHE_IGNORE_REMOVAL)
+                               break;
+                       if (!(data->flags & ADD_CACHE_PRETEND))
+                               remove_file_from_index(&the_index, path);
+                       if (data->flags & (ADD_CACHE_PRETEND|ADD_CACHE_VERBOSE))
+                               printf("remove '%s'\n", path);
+                       break;
+               }
+       }
+ }
+ int add_files_to_cache(const char *prefix, const char **pathspec, int flags)
+ {
+       struct update_callback_data data;
+       struct rev_info rev;
+       init_revisions(&rev, prefix);
+       setup_revisions(0, NULL, &rev, NULL);
+       rev.prune_data = pathspec;
+       rev.diffopt.output_format = DIFF_FORMAT_CALLBACK;
+       rev.diffopt.format_callback = update_callback;
+       data.flags = flags;
+       data.add_errors = 0;
+       rev.diffopt.format_callback_data = &data;
+       run_diff_files(&rev, DIFF_RACY_IS_MODIFIED);
+       return !!data.add_errors;
+ }