builtin / add.con commit submodule: port submodule subcommand 'status' from shell to C (a9f8a37)
   1/*
   2 * "git add" builtin command
   3 *
   4 * Copyright (C) 2006 Linus Torvalds
   5 */
   6#include "cache.h"
   7#include "config.h"
   8#include "builtin.h"
   9#include "lockfile.h"
  10#include "dir.h"
  11#include "pathspec.h"
  12#include "exec_cmd.h"
  13#include "cache-tree.h"
  14#include "run-command.h"
  15#include "parse-options.h"
  16#include "diff.h"
  17#include "diffcore.h"
  18#include "revision.h"
  19#include "bulk-checkin.h"
  20#include "argv-array.h"
  21#include "submodule.h"
  22
  23static const char * const builtin_add_usage[] = {
  24        N_("git add [<options>] [--] <pathspec>..."),
  25        NULL
  26};
  27static int patch_interactive, add_interactive, edit_interactive;
  28static int take_worktree_changes;
  29
  30struct update_callback_data {
  31        int flags;
  32        int add_errors;
  33};
  34
  35static void chmod_pathspec(struct pathspec *pathspec, char flip)
  36{
  37        int i;
  38
  39        for (i = 0; i < active_nr; i++) {
  40                struct cache_entry *ce = active_cache[i];
  41
  42                if (pathspec && !ce_path_match(ce, pathspec, NULL))
  43                        continue;
  44
  45                if (chmod_cache_entry(ce, flip) < 0)
  46                        fprintf(stderr, "cannot chmod %cx '%s'\n", flip, ce->name);
  47        }
  48}
  49
  50static int fix_unmerged_status(struct diff_filepair *p,
  51                               struct update_callback_data *data)
  52{
  53        if (p->status != DIFF_STATUS_UNMERGED)
  54                return p->status;
  55        if (!(data->flags & ADD_CACHE_IGNORE_REMOVAL) && !p->two->mode)
  56                /*
  57                 * This is not an explicit add request, and the
  58                 * path is missing from the working tree (deleted)
  59                 */
  60                return DIFF_STATUS_DELETED;
  61        else
  62                /*
  63                 * Either an explicit add request, or path exists
  64                 * in the working tree.  An attempt to explicitly
  65                 * add a path that does not exist in the working tree
  66                 * will be caught as an error by the caller immediately.
  67                 */
  68                return DIFF_STATUS_MODIFIED;
  69}
  70
  71static void update_callback(struct diff_queue_struct *q,
  72                            struct diff_options *opt, void *cbdata)
  73{
  74        int i;
  75        struct update_callback_data *data = cbdata;
  76
  77        for (i = 0; i < q->nr; i++) {
  78                struct diff_filepair *p = q->queue[i];
  79                const char *path = p->one->path;
  80                switch (fix_unmerged_status(p, data)) {
  81                default:
  82                        die(_("unexpected diff status %c"), p->status);
  83                case DIFF_STATUS_MODIFIED:
  84                case DIFF_STATUS_TYPE_CHANGED:
  85                        if (add_file_to_index(&the_index, path, data->flags)) {
  86                                if (!(data->flags & ADD_CACHE_IGNORE_ERRORS))
  87                                        die(_("updating files failed"));
  88                                data->add_errors++;
  89                        }
  90                        break;
  91                case DIFF_STATUS_DELETED:
  92                        if (data->flags & ADD_CACHE_IGNORE_REMOVAL)
  93                                break;
  94                        if (!(data->flags & ADD_CACHE_PRETEND))
  95                                remove_file_from_index(&the_index, path);
  96                        if (data->flags & (ADD_CACHE_PRETEND|ADD_CACHE_VERBOSE))
  97                                printf(_("remove '%s'\n"), path);
  98                        break;
  99                }
 100        }
 101}
 102
 103int add_files_to_cache(const char *prefix,
 104                       const struct pathspec *pathspec, int flags)
 105{
 106        struct update_callback_data data;
 107        struct rev_info rev;
 108
 109        memset(&data, 0, sizeof(data));
 110        data.flags = flags;
 111
 112        init_revisions(&rev, prefix);
 113        setup_revisions(0, NULL, &rev, NULL);
 114        if (pathspec)
 115                copy_pathspec(&rev.prune_data, pathspec);
 116        rev.diffopt.output_format = DIFF_FORMAT_CALLBACK;
 117        rev.diffopt.format_callback = update_callback;
 118        rev.diffopt.format_callback_data = &data;
 119        rev.diffopt.flags |= DIFF_OPT_OVERRIDE_SUBMODULE_CONFIG;
 120        rev.max_count = 0; /* do not compare unmerged paths with stage #2 */
 121        run_diff_files(&rev, DIFF_RACY_IS_MODIFIED);
 122        clear_pathspec(&rev.prune_data);
 123        return !!data.add_errors;
 124}
 125
 126static char *prune_directory(struct dir_struct *dir, struct pathspec *pathspec, int prefix)
 127{
 128        char *seen;
 129        int i;
 130        struct dir_entry **src, **dst;
 131
 132        seen = xcalloc(pathspec->nr, 1);
 133
 134        src = dst = dir->entries;
 135        i = dir->nr;
 136        while (--i >= 0) {
 137                struct dir_entry *entry = *src++;
 138                if (dir_path_match(entry, pathspec, prefix, seen))
 139                        *dst++ = entry;
 140        }
 141        dir->nr = dst - dir->entries;
 142        add_pathspec_matches_against_index(pathspec, &the_index, seen);
 143        return seen;
 144}
 145
 146static void refresh(int verbose, const struct pathspec *pathspec)
 147{
 148        char *seen;
 149        int i;
 150
 151        seen = xcalloc(pathspec->nr, 1);
 152        refresh_index(&the_index, verbose ? REFRESH_IN_PORCELAIN : REFRESH_QUIET,
 153                      pathspec, seen, _("Unstaged changes after refreshing the index:"));
 154        for (i = 0; i < pathspec->nr; i++) {
 155                if (!seen[i])
 156                        die(_("pathspec '%s' did not match any files"),
 157                            pathspec->items[i].match);
 158        }
 159        free(seen);
 160}
 161
 162int run_add_interactive(const char *revision, const char *patch_mode,
 163                        const struct pathspec *pathspec)
 164{
 165        int status, i;
 166        struct argv_array argv = ARGV_ARRAY_INIT;
 167
 168        argv_array_push(&argv, "add--interactive");
 169        if (patch_mode)
 170                argv_array_push(&argv, patch_mode);
 171        if (revision)
 172                argv_array_push(&argv, revision);
 173        argv_array_push(&argv, "--");
 174        for (i = 0; i < pathspec->nr; i++)
 175                /* pass original pathspec, to be re-parsed */
 176                argv_array_push(&argv, pathspec->items[i].original);
 177
 178        status = run_command_v_opt(argv.argv, RUN_GIT_CMD);
 179        argv_array_clear(&argv);
 180        return status;
 181}
 182
 183int interactive_add(int argc, const char **argv, const char *prefix, int patch)
 184{
 185        struct pathspec pathspec;
 186
 187        parse_pathspec(&pathspec, 0,
 188                       PATHSPEC_PREFER_FULL |
 189                       PATHSPEC_SYMLINK_LEADING_PATH |
 190                       PATHSPEC_PREFIX_ORIGIN,
 191                       prefix, argv);
 192
 193        return run_add_interactive(NULL,
 194                                   patch ? "--patch" : NULL,
 195                                   &pathspec);
 196}
 197
 198static int edit_patch(int argc, const char **argv, const char *prefix)
 199{
 200        char *file = git_pathdup("ADD_EDIT.patch");
 201        const char *apply_argv[] = { "apply", "--recount", "--cached",
 202                NULL, NULL };
 203        struct child_process child = CHILD_PROCESS_INIT;
 204        struct rev_info rev;
 205        int out;
 206        struct stat st;
 207
 208        apply_argv[3] = file;
 209
 210        git_config(git_diff_basic_config, NULL); /* no "diff" UI options */
 211
 212        if (read_cache() < 0)
 213                die(_("Could not read the index"));
 214
 215        init_revisions(&rev, prefix);
 216        rev.diffopt.context = 7;
 217
 218        argc = setup_revisions(argc, argv, &rev, NULL);
 219        rev.diffopt.output_format = DIFF_FORMAT_PATCH;
 220        rev.diffopt.use_color = 0;
 221        DIFF_OPT_SET(&rev.diffopt, IGNORE_DIRTY_SUBMODULES);
 222        out = open(file, O_CREAT | O_WRONLY, 0666);
 223        if (out < 0)
 224                die(_("Could not open '%s' for writing."), file);
 225        rev.diffopt.file = xfdopen(out, "w");
 226        rev.diffopt.close_file = 1;
 227        if (run_diff_files(&rev, 0))
 228                die(_("Could not write patch"));
 229
 230        if (launch_editor(file, NULL, NULL))
 231                die(_("editing patch failed"));
 232
 233        if (stat(file, &st))
 234                die_errno(_("Could not stat '%s'"), file);
 235        if (!st.st_size)
 236                die(_("Empty patch. Aborted."));
 237
 238        child.git_cmd = 1;
 239        child.argv = apply_argv;
 240        if (run_command(&child))
 241                die(_("Could not apply '%s'"), file);
 242
 243        unlink(file);
 244        free(file);
 245        return 0;
 246}
 247
 248static struct lock_file lock_file;
 249
 250static const char ignore_error[] =
 251N_("The following paths are ignored by one of your .gitignore files:\n");
 252
 253static int verbose, show_only, ignored_too, refresh_only;
 254static int ignore_add_errors, intent_to_add, ignore_missing;
 255static int warn_on_embedded_repo = 1;
 256
 257#define ADDREMOVE_DEFAULT 1
 258static int addremove = ADDREMOVE_DEFAULT;
 259static int addremove_explicit = -1; /* unspecified */
 260
 261static char *chmod_arg;
 262
 263static int ignore_removal_cb(const struct option *opt, const char *arg, int unset)
 264{
 265        /* if we are told to ignore, we are not adding removals */
 266        *(int *)opt->value = !unset ? 0 : 1;
 267        return 0;
 268}
 269
 270static struct option builtin_add_options[] = {
 271        OPT__DRY_RUN(&show_only, N_("dry run")),
 272        OPT__VERBOSE(&verbose, N_("be verbose")),
 273        OPT_GROUP(""),
 274        OPT_BOOL('i', "interactive", &add_interactive, N_("interactive picking")),
 275        OPT_BOOL('p', "patch", &patch_interactive, N_("select hunks interactively")),
 276        OPT_BOOL('e', "edit", &edit_interactive, N_("edit current diff and apply")),
 277        OPT__FORCE(&ignored_too, N_("allow adding otherwise ignored files")),
 278        OPT_BOOL('u', "update", &take_worktree_changes, N_("update tracked files")),
 279        OPT_BOOL('N', "intent-to-add", &intent_to_add, N_("record only the fact that the path will be added later")),
 280        OPT_BOOL('A', "all", &addremove_explicit, N_("add changes from all tracked and untracked files")),
 281        { OPTION_CALLBACK, 0, "ignore-removal", &addremove_explicit,
 282          NULL /* takes no arguments */,
 283          N_("ignore paths removed in the working tree (same as --no-all)"),
 284          PARSE_OPT_NOARG, ignore_removal_cb },
 285        OPT_BOOL( 0 , "refresh", &refresh_only, N_("don't add, only refresh the index")),
 286        OPT_BOOL( 0 , "ignore-errors", &ignore_add_errors, N_("just skip files which cannot be added because of errors")),
 287        OPT_BOOL( 0 , "ignore-missing", &ignore_missing, N_("check if - even missing - files are ignored in dry run")),
 288        OPT_STRING( 0 , "chmod", &chmod_arg, N_("(+/-)x"), N_("override the executable bit of the listed files")),
 289        OPT_HIDDEN_BOOL(0, "warn-embedded-repo", &warn_on_embedded_repo,
 290                        N_("warn when adding an embedded repository")),
 291        OPT_END(),
 292};
 293
 294static int add_config(const char *var, const char *value, void *cb)
 295{
 296        if (!strcmp(var, "add.ignoreerrors") ||
 297            !strcmp(var, "add.ignore-errors")) {
 298                ignore_add_errors = git_config_bool(var, value);
 299                return 0;
 300        }
 301        return git_default_config(var, value, cb);
 302}
 303
 304static const char embedded_advice[] = N_(
 305"You've added another git repository inside your current repository.\n"
 306"Clones of the outer repository will not contain the contents of\n"
 307"the embedded repository and will not know how to obtain it.\n"
 308"If you meant to add a submodule, use:\n"
 309"\n"
 310"       git submodule add <url> %s\n"
 311"\n"
 312"If you added this path by mistake, you can remove it from the\n"
 313"index with:\n"
 314"\n"
 315"       git rm --cached %s\n"
 316"\n"
 317"See \"git help submodule\" for more information."
 318);
 319
 320static void check_embedded_repo(const char *path)
 321{
 322        struct strbuf name = STRBUF_INIT;
 323
 324        if (!warn_on_embedded_repo)
 325                return;
 326        if (!ends_with(path, "/"))
 327                return;
 328
 329        /* Drop trailing slash for aesthetics */
 330        strbuf_addstr(&name, path);
 331        strbuf_strip_suffix(&name, "/");
 332
 333        warning(_("adding embedded git repository: %s"), name.buf);
 334        if (advice_add_embedded_repo) {
 335                advise(embedded_advice, name.buf, name.buf);
 336                /* there may be multiple entries; advise only once */
 337                advice_add_embedded_repo = 0;
 338        }
 339
 340        strbuf_release(&name);
 341}
 342
 343static int add_files(struct dir_struct *dir, int flags)
 344{
 345        int i, exit_status = 0;
 346
 347        if (dir->ignored_nr) {
 348                fprintf(stderr, _(ignore_error));
 349                for (i = 0; i < dir->ignored_nr; i++)
 350                        fprintf(stderr, "%s\n", dir->ignored[i]->name);
 351                fprintf(stderr, _("Use -f if you really want to add them.\n"));
 352                exit_status = 1;
 353        }
 354
 355        for (i = 0; i < dir->nr; i++) {
 356                check_embedded_repo(dir->entries[i]->name);
 357                if (add_file_to_index(&the_index, dir->entries[i]->name, flags)) {
 358                        if (!ignore_add_errors)
 359                                die(_("adding files failed"));
 360                        exit_status = 1;
 361                }
 362        }
 363        return exit_status;
 364}
 365
 366int cmd_add(int argc, const char **argv, const char *prefix)
 367{
 368        int exit_status = 0;
 369        struct pathspec pathspec;
 370        struct dir_struct dir;
 371        int flags;
 372        int add_new_files;
 373        int require_pathspec;
 374        char *seen = NULL;
 375
 376        git_config(add_config, NULL);
 377
 378        argc = parse_options(argc, argv, prefix, builtin_add_options,
 379                          builtin_add_usage, PARSE_OPT_KEEP_ARGV0);
 380        if (patch_interactive)
 381                add_interactive = 1;
 382        if (add_interactive)
 383                exit(interactive_add(argc - 1, argv + 1, prefix, patch_interactive));
 384
 385        if (edit_interactive)
 386                return(edit_patch(argc, argv, prefix));
 387        argc--;
 388        argv++;
 389
 390        if (0 <= addremove_explicit)
 391                addremove = addremove_explicit;
 392        else if (take_worktree_changes && ADDREMOVE_DEFAULT)
 393                addremove = 0; /* "-u" was given but not "-A" */
 394
 395        if (addremove && take_worktree_changes)
 396                die(_("-A and -u are mutually incompatible"));
 397
 398        if (!take_worktree_changes && addremove_explicit < 0 && argc)
 399                /* Turn "git add pathspec..." to "git add -A pathspec..." */
 400                addremove = 1;
 401
 402        if (!show_only && ignore_missing)
 403                die(_("Option --ignore-missing can only be used together with --dry-run"));
 404
 405        if (chmod_arg && ((chmod_arg[0] != '-' && chmod_arg[0] != '+') ||
 406                          chmod_arg[1] != 'x' || chmod_arg[2]))
 407                die(_("--chmod param '%s' must be either -x or +x"), chmod_arg);
 408
 409        add_new_files = !take_worktree_changes && !refresh_only;
 410        require_pathspec = !(take_worktree_changes || (0 < addremove_explicit));
 411
 412        hold_locked_index(&lock_file, LOCK_DIE_ON_ERROR);
 413
 414        flags = ((verbose ? ADD_CACHE_VERBOSE : 0) |
 415                 (show_only ? ADD_CACHE_PRETEND : 0) |
 416                 (intent_to_add ? ADD_CACHE_INTENT : 0) |
 417                 (ignore_add_errors ? ADD_CACHE_IGNORE_ERRORS : 0) |
 418                 (!(addremove || take_worktree_changes)
 419                  ? ADD_CACHE_IGNORE_REMOVAL : 0));
 420
 421        if (require_pathspec && argc == 0) {
 422                fprintf(stderr, _("Nothing specified, nothing added.\n"));
 423                fprintf(stderr, _("Maybe you wanted to say 'git add .'?\n"));
 424                return 0;
 425        }
 426
 427        if (read_cache() < 0)
 428                die(_("index file corrupt"));
 429
 430        die_in_unpopulated_submodule(&the_index, prefix);
 431
 432        /*
 433         * Check the "pathspec '%s' did not match any files" block
 434         * below before enabling new magic.
 435         */
 436        parse_pathspec(&pathspec, 0,
 437                       PATHSPEC_PREFER_FULL |
 438                       PATHSPEC_SYMLINK_LEADING_PATH,
 439                       prefix, argv);
 440
 441        die_path_inside_submodule(&the_index, &pathspec);
 442
 443        if (add_new_files) {
 444                int baselen;
 445
 446                /* Set up the default git porcelain excludes */
 447                memset(&dir, 0, sizeof(dir));
 448                if (!ignored_too) {
 449                        dir.flags |= DIR_COLLECT_IGNORED;
 450                        setup_standard_excludes(&dir);
 451                }
 452
 453                /* This picks up the paths that are not tracked */
 454                baselen = fill_directory(&dir, &the_index, &pathspec);
 455                if (pathspec.nr)
 456                        seen = prune_directory(&dir, &pathspec, baselen);
 457        }
 458
 459        if (refresh_only) {
 460                refresh(verbose, &pathspec);
 461                goto finish;
 462        }
 463
 464        if (pathspec.nr) {
 465                int i;
 466
 467                if (!seen)
 468                        seen = find_pathspecs_matching_against_index(&pathspec, &the_index);
 469
 470                /*
 471                 * file_exists() assumes exact match
 472                 */
 473                GUARD_PATHSPEC(&pathspec,
 474                               PATHSPEC_FROMTOP |
 475                               PATHSPEC_LITERAL |
 476                               PATHSPEC_GLOB |
 477                               PATHSPEC_ICASE |
 478                               PATHSPEC_EXCLUDE);
 479
 480                for (i = 0; i < pathspec.nr; i++) {
 481                        const char *path = pathspec.items[i].match;
 482                        if (pathspec.items[i].magic & PATHSPEC_EXCLUDE)
 483                                continue;
 484                        if (!seen[i] && path[0] &&
 485                            ((pathspec.items[i].magic &
 486                              (PATHSPEC_GLOB | PATHSPEC_ICASE)) ||
 487                             !file_exists(path))) {
 488                                if (ignore_missing) {
 489                                        int dtype = DT_UNKNOWN;
 490                                        if (is_excluded(&dir, &the_index, path, &dtype))
 491                                                dir_add_ignored(&dir, &the_index,
 492                                                                path, pathspec.items[i].len);
 493                                } else
 494                                        die(_("pathspec '%s' did not match any files"),
 495                                            pathspec.items[i].original);
 496                        }
 497                }
 498                free(seen);
 499        }
 500
 501        plug_bulk_checkin();
 502
 503        exit_status |= add_files_to_cache(prefix, &pathspec, flags);
 504
 505        if (add_new_files)
 506                exit_status |= add_files(&dir, flags);
 507
 508        if (chmod_arg && pathspec.nr)
 509                chmod_pathspec(&pathspec, chmod_arg[0]);
 510        unplug_bulk_checkin();
 511
 512finish:
 513        if (active_cache_changed) {
 514                if (write_locked_index(&the_index, &lock_file, COMMIT_LOCK))
 515                        die(_("Unable to write new index file"));
 516        }
 517
 518        UNLEAK(pathspec);
 519        UNLEAK(dir);
 520        return exit_status;
 521}