Merge branch 'sb/maint-1.6.0-add-config-fix'
authorJunio C Hamano <gitster@pobox.com>
Sun, 21 Jun 2009 04:46:38 +0000 (21:46 -0700)
committerJunio C Hamano <gitster@pobox.com>
Sun, 21 Jun 2009 04:46:38 +0000 (21:46 -0700)
* sb/maint-1.6.0-add-config-fix:
add: allow configurations to be overriden by command line
use xstrdup, not strdup in ll-merge.c

Conflicts:
builtin-add.c

1  2 
builtin-add.c
ll-merge.c
t/t3700-add.sh
diff --combined builtin-add.c
index c1b229a9d8a026ec88cdbf0492856021cacc8097,13db4a6455e17a0f960e53ddcbc6e2db898f55d0..56e522127c3e73fad59d8dc96bdffdb87a3b7a44
@@@ -8,39 -8,20 +8,39 @@@
  #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"
 +#include "diff.h"
 +#include "revision.h"
  
  static const char * const builtin_add_usage[] = {
        "git add [options] [--] <filepattern>...",
        NULL
  };
 -static int patch_interactive = 0, add_interactive = 0;
 +static int patch_interactive, add_interactive, edit_interactive;
  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 were walking 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;
                        *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]))
 +              if (!seen[i] && pathspec[i][0] && !file_exists(pathspec[i]))
                        die("pathspec '%s' did not match any files",
                                        pathspec[i]);
        }
          free(seen);
  }
  
 +static void treat_gitlinks(const char **pathspec)
 +{
 +      int i;
 +
 +      if (!pathspec || !*pathspec)
 +              return;
 +
 +      for (i = 0; i < active_nr; i++) {
 +              struct cache_entry *ce = active_cache[i];
 +              if (S_ISGITLINK(ce->ce_mode)) {
 +                      int len = ce_namelen(ce), j;
 +                      for (j = 0; pathspec[j]; j++) {
 +                              int len2 = strlen(pathspec[j]);
 +                              if (len2 <= len || pathspec[j][len] != '/' ||
 +                                  memcmp(ce->name, pathspec[j], len))
 +                                      continue;
 +                              if (len2 == len + 1)
 +                                      /* strip trailing slash */
 +                                      pathspec[j] = xstrndup(ce->name, len);
 +                              else
 +                                      die ("Path '%s' is in submodule '%.*s'",
 +                                              pathspec[j], len, ce->name);
 +                      }
 +              }
 +      }
 +}
 +
  static void fill_directory(struct dir_struct *dir, const char **pathspec,
                int ignored_too)
  {
        /* Set up the default git porcelain excludes */
        memset(dir, 0, sizeof(*dir));
        if (!ignored_too) {
 -              dir->collect_ignored = 1;
 +              dir->flags |= DIR_COLLECT_IGNORED;
                setup_standard_excludes(dir);
        }
  
                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;
@@@ -147,16 -153,6 +147,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(*p, strlen(*p))) {
 +                              int len = prefix ? strlen(prefix) : 0;
 +                              die("'%s' is beyond a symbolic link", *p + len);
 +                      }
 +              }
 +      }
 +
        return pathspec;
  }
  
@@@ -189,58 -185,13 +189,58 @@@ int interactive_add(int argc, const cha
        return status;
  }
  
 +int edit_patch(int argc, const char **argv, const char *prefix)
 +{
 +      char *file = xstrdup(git_path("ADD_EDIT.patch"));
 +      const char *apply_argv[] = { "apply", "--recount", "--cached",
 +              file, NULL };
 +      struct child_process child;
 +      struct rev_info rev;
 +      int out;
 +      struct stat st;
 +
 +      git_config(git_diff_basic_config, NULL); /* no "diff" UI options */
 +
 +      if (read_cache() < 0)
 +              die ("Could not read the index");
 +
 +      init_revisions(&rev, prefix);
 +      rev.diffopt.context = 7;
 +
 +      argc = setup_revisions(argc, argv, &rev, NULL);
 +      rev.diffopt.output_format = DIFF_FORMAT_PATCH;
 +      out = open(file, O_CREAT | O_WRONLY, 0644);
 +      if (out < 0)
 +              die ("Could not open '%s' for writing.", file);
 +      rev.diffopt.file = fdopen(out, "w");
 +      rev.diffopt.close_file = 1;
 +      if (run_diff_files(&rev, 0))
 +              die ("Could not write patch");
 +
 +      launch_editor(file, NULL, NULL);
 +
 +      if (stat(file, &st))
 +              die("Could not stat '%s'", file);
 +      if (!st.st_size)
 +              die("Empty patch. Aborted.");
 +
 +      memset(&child, 0, sizeof(child));
 +      child.git_cmd = 1;
 +      child.argv = apply_argv;
 +      if (run_command(&child))
 +              die ("Could not apply '%s'", file);
 +
 +      unlink(file);
 +      return 0;
 +}
 +
  static struct lock_file lock_file;
  
  static const char ignore_error[] =
  "The following paths are ignored by one of your .gitignore files:\n";
  
  static int verbose = 0, show_only = 0, ignored_too = 0, refresh_only = 0;
 -static int ignore_add_errors, addremove;
 +static int ignore_add_errors, addremove, intent_to_add;
  
  static struct option builtin_add_options[] = {
        OPT__DRY_RUN(&show_only),
        OPT_GROUP(""),
        OPT_BOOLEAN('i', "interactive", &add_interactive, "interactive picking"),
        OPT_BOOLEAN('p', "patch", &patch_interactive, "interactive patching"),
 +      OPT_BOOLEAN('e', "edit", &edit_interactive, "edit current diff and apply"),
        OPT_BOOLEAN('f', "force", &ignored_too, "allow adding otherwise ignored files"),
        OPT_BOOLEAN('u', "update", &take_worktree_changes, "update tracked files"),
 +      OPT_BOOLEAN('N', "intent-to-add", &intent_to_add, "record only the fact that the path will be added later"),
        OPT_BOOLEAN('A', "all", &addremove, "add all, noticing removal of tracked files"),
        OPT_BOOLEAN( 0 , "refresh", &refresh_only, "don't add, only refresh the index"),
        OPT_BOOLEAN( 0 , "ignore-errors", &ignore_add_errors, "just skip files which cannot be added because of errors"),
@@@ -298,23 -247,18 +298,23 @@@ int cmd_add(int argc, const char **argv
        int add_new_files;
        int require_pathspec;
  
 -      argc = parse_options(argc, argv, builtin_add_options,
 -                        builtin_add_usage, 0);
+       git_config(add_config, NULL);
 +      argc = parse_options(argc, argv, prefix, builtin_add_options,
 +                        builtin_add_usage, PARSE_OPT_KEEP_ARGV0);
        if (patch_interactive)
                add_interactive = 1;
        if (add_interactive)
 -              exit(interactive_add(argc, argv, prefix));
 +              exit(interactive_add(argc - 1, argv + 1, prefix));
 +
-       git_config(add_config, NULL);
 +      if (edit_interactive)
 +              return(edit_patch(argc, argv, prefix));
 +      argc--;
 +      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));
 +               (intent_to_add ? ADD_CACHE_INTENT : 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);
 -
 -      /*
 -       * 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);
 +      pathspec = validate_pathspec(argc, argv, prefix);
  
        if (read_cache() < 0)
                die("index file corrupt");
 +      treat_gitlinks(pathspec);
 +
 +      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 ll-merge.c
index 31d6f0a2ee63e8c3cad1053f9281a0ce9917fb32,e339b8cfaca7c3a08fae4b9c54b76c0351101a2b..9168958e863e50f0d1bd1446b3314cb3dd2e772f
@@@ -8,6 -8,7 +8,6 @@@
  #include "attr.h"
  #include "xdiff-interface.h"
  #include "run-command.h"
 -#include "interpolate.h"
  #include "ll-merge.h"
  
  struct ll_merge_driver;
@@@ -62,7 -63,6 +62,7 @@@ static int ll_xdl_merge(const struct ll
                        int virtual_ancestor)
  {
        xpparam_t xpp;
 +      int style = 0;
  
        if (buffer_is_binary(orig->ptr, orig->size) ||
            buffer_is_binary(src1->ptr, src1->size) ||
        }
  
        memset(&xpp, 0, sizeof(xpp));
 +      if (git_xmerge_style >= 0)
 +              style = git_xmerge_style;
        return xdl_merge(orig,
                         src1, name1,
                         src2, name2,
 -                       &xpp, XDL_MERGE_ZEALOUS,
 +                       &xpp, XDL_MERGE_ZEALOUS | style,
                         result);
  }
  
@@@ -97,15 -95,10 +97,15 @@@ static int ll_union_merge(const struct 
        char *src, *dst;
        long size;
        const int marker_size = 7;
 -
 -      int status = ll_xdl_merge(drv_unused, result, path_unused,
 -                                orig, src1, NULL, src2, NULL,
 -                                virtual_ancestor);
 +      int status, saved_style;
 +
 +      /* We have to force the RCS "merge" style */
 +      saved_style = git_xmerge_style;
 +      git_xmerge_style = 0;
 +      status = ll_xdl_merge(drv_unused, result, path_unused,
 +                            orig, src1, NULL, src2, NULL,
 +                            virtual_ancestor);
 +      git_xmerge_style = saved_style;
        if (status <= 0)
                return status;
        size = result->size;
@@@ -168,14 -161,14 +168,14 @@@ static int ll_ext_merge(const struct ll
                        int virtual_ancestor)
  {
        char temp[3][50];
 -      char cmdbuf[2048];
 -      struct interp table[] = {
 -              { "%O" },
 -              { "%A" },
 -              { "%B" },
 +      struct strbuf cmd = STRBUF_INIT;
 +      struct strbuf_expand_dict_entry dict[] = {
 +              { "O", temp[0] },
 +              { "A", temp[1] },
 +              { "B", temp[2] },
 +              { NULL }
        };
 -      struct child_process child;
 -      const char *args[20];
 +      const char *args[] = { "sh", "-c", NULL, NULL };
        int status, fd, i;
        struct stat st;
  
        create_temp(src1, temp[1]);
        create_temp(src2, temp[2]);
  
 -      interp_set_entry(table, 0, temp[0]);
 -      interp_set_entry(table, 1, temp[1]);
 -      interp_set_entry(table, 2, temp[2]);
 -
 -      interpolate(cmdbuf, sizeof(cmdbuf), fn->cmdline, table, 3);
 -
 -      memset(&child, 0, sizeof(child));
 -      child.argv = args;
 -      args[0] = "sh";
 -      args[1] = "-c";
 -      args[2] = cmdbuf;
 -      args[3] = NULL;
 +      strbuf_expand(&cmd, fn->cmdline, strbuf_expand_dict_cb, &dict);
  
 -      status = run_command(&child);
 +      args[2] = cmd.buf;
 +      status = run_command_v_opt(args, 0);
        if (status < -ERR_RUN_COMMAND_FORK)
                ; /* failure in run-command */
        else
        close(fd);
   bad:
        for (i = 0; i < 3; i++)
 -              unlink(temp[i]);
 +              unlink_or_warn(temp[i]);
 +      strbuf_release(&cmd);
        return status;
  }
  
@@@ -231,7 -233,7 +231,7 @@@ static int read_merge_config(const cha
  
        if (!strcmp(var, "merge.default")) {
                if (value)
-                       default_ll_merge = strdup(value);
+                       default_ll_merge = xstrdup(value);
                return 0;
        }
  
        if (!strcmp("name", ep)) {
                if (!value)
                        return error("%s: lacks value", var);
-               fn->description = strdup(value);
+               fn->description = xstrdup(value);
                return 0;
        }
  
                 * file named by %A, and signal that it has done with zero exit
                 * status.
                 */
-               fn->cmdline = strdup(value);
+               fn->cmdline = xstrdup(value);
                return 0;
        }
  
        if (!strcmp("recursive", ep)) {
                if (!value)
                        return error("%s: lacks value", var);
-               fn->recursive = strdup(value);
+               fn->recursive = xstrdup(value);
                return 0;
        }
  
diff --combined t/t3700-add.sh
index 6ce8256a17bb7c775eed301b3af8a88497a12c09,060a6ecabe4cf208dc88e4e4b1d42464c5f4d894..6ae5a2cd9511b83ad1d263c0ec0a374fc4bfc981
@@@ -30,7 -30,7 +30,7 @@@ test_expect_success 
         *) echo fail; git ls-files --stage xfoo1; (exit 1);;
         esac'
  
 -test_expect_success 'git add: filemode=0 should not get confused by symlink' '
 +test_expect_success SYMLINKS 'git add: filemode=0 should not get confused by symlink' '
        rm -f xfoo1 &&
        ln -s foo xfoo1 &&
        git add xfoo1 &&
@@@ -51,7 -51,7 +51,7 @@@ test_expect_success 
         *) echo fail; git ls-files --stage xfoo2; (exit 1);;
         esac'
  
 -test_expect_success 'git add: filemode=0 should not get confused by symlink' '
 +test_expect_success SYMLINKS 'git add: filemode=0 should not get confused by symlink' '
        rm -f xfoo2 &&
        ln -s foo xfoo2 &&
        git update-index --add xfoo2 &&
@@@ -61,7 -61,7 +61,7 @@@
        esac
  '
  
 -test_expect_success \
 +test_expect_success SYMLINKS \
        'git update-index --add: Test that executable bit is not used...' \
        'git config core.filemode 0 &&
         ln -s xfoo2 xfoo3 &&
@@@ -179,7 -179,7 +179,7 @@@ test_expect_success 'git add --refresh
        test -z "`git diff-index HEAD -- foo`"
  '
  
 -test_expect_success 'git add should fail atomically upon an unreadable file' '
 +test_expect_success POSIXPERM 'git add should fail atomically upon an unreadable file' '
        git reset --hard &&
        date >foo1 &&
        date >foo2 &&
  
  rm -f foo2
  
 -test_expect_success 'git add --ignore-errors' '
 +test_expect_success POSIXPERM 'git add --ignore-errors' '
        git reset --hard &&
        date >foo1 &&
        date >foo2 &&
  
  rm -f foo2
  
 -test_expect_success 'git add (add.ignore-errors)' '
 +test_expect_success POSIXPERM 'git add (add.ignore-errors)' '
        git config add.ignore-errors 1 &&
        git reset --hard &&
        date >foo1 &&
  '
  rm -f foo2
  
 -test_expect_success 'git add (add.ignore-errors = false)' '
 +test_expect_success POSIXPERM 'git add (add.ignore-errors = false)' '
        git config add.ignore-errors 0 &&
        git reset --hard &&
        date >foo1 &&
        test_must_fail git add --verbose . &&
        ! ( git ls-files foo1 | grep foo1 )
  '
+ rm -f foo2
+ test_expect_success '--no-ignore-errors overrides config' '
+        git config add.ignore-errors 1 &&
+        git reset --hard &&
+        date >foo1 &&
+        date >foo2 &&
+        chmod 0 foo2 &&
+        test_must_fail git add --verbose --no-ignore-errors . &&
+        ! ( git ls-files foo1 | grep foo1 ) &&
+        git config add.ignore-errors 0
+ '
+ rm -f foo2
  
 -test_expect_success 'git add '\''fo\[ou\]bar'\'' ignores foobar' '
 +test_expect_success BSLASHPSPEC "git add 'fo\\[ou\\]bar' ignores foobar" '
        git reset --hard &&
        touch fo\[ou\]bar foobar &&
        git add '\''fo\[ou\]bar'\'' &&
 -      git ls-files fo\[ou\]bar | grep -F fo\[ou\]bar &&
 +      git ls-files fo\[ou\]bar | fgrep fo\[ou\]bar &&
        ! ( git ls-files foobar | grep foobar )
  '
  
 +test_expect_success 'git add to resolve conflicts on otherwise ignored path' '
 +      git reset --hard &&
 +      H=$(git rev-parse :1/2/a) &&
 +      (
 +              echo "100644 $H 1       track-this"
 +              echo "100644 $H 3       track-this"
 +      ) | git update-index --index-info &&
 +      echo track-this >>.gitignore &&
 +      echo resolved >track-this &&
 +      git add track-this
 +'
 +
  test_done