Coverage Report

Created: 2024-09-08 06:23

/src/git/builtin/add.c
Line
Count
Source (jump to first uncovered line)
1
/*
2
 * "git add" builtin command
3
 *
4
 * Copyright (C) 2006 Linus Torvalds
5
 */
6
7
#include "builtin.h"
8
#include "advice.h"
9
#include "config.h"
10
#include "lockfile.h"
11
#include "editor.h"
12
#include "dir.h"
13
#include "gettext.h"
14
#include "pathspec.h"
15
#include "run-command.h"
16
#include "parse-options.h"
17
#include "path.h"
18
#include "preload-index.h"
19
#include "diff.h"
20
#include "read-cache.h"
21
#include "repository.h"
22
#include "revision.h"
23
#include "bulk-checkin.h"
24
#include "strvec.h"
25
#include "submodule.h"
26
#include "add-interactive.h"
27
28
static const char * const builtin_add_usage[] = {
29
  N_("git add [<options>] [--] <pathspec>..."),
30
  NULL
31
};
32
static int patch_interactive, add_interactive, edit_interactive;
33
static int take_worktree_changes;
34
static int add_renormalize;
35
static int pathspec_file_nul;
36
static int include_sparse;
37
static const char *pathspec_from_file;
38
39
static int chmod_pathspec(struct pathspec *pathspec, char flip, int show_only)
40
0
{
41
0
  int i, ret = 0;
42
43
0
  for (i = 0; i < the_repository->index->cache_nr; i++) {
44
0
    struct cache_entry *ce = the_repository->index->cache[i];
45
0
    int err;
46
47
0
    if (!include_sparse &&
48
0
        (ce_skip_worktree(ce) ||
49
0
         !path_in_sparse_checkout(ce->name, the_repository->index)))
50
0
      continue;
51
52
0
    if (pathspec && !ce_path_match(the_repository->index, ce, pathspec, NULL))
53
0
      continue;
54
55
0
    if (!show_only)
56
0
      err = chmod_index_entry(the_repository->index, ce, flip);
57
0
    else
58
0
      err = S_ISREG(ce->ce_mode) ? 0 : -1;
59
60
0
    if (err < 0)
61
0
      ret = error(_("cannot chmod %cx '%s'"), flip, ce->name);
62
0
  }
63
64
0
  return ret;
65
0
}
66
67
static int renormalize_tracked_files(const struct pathspec *pathspec, int flags)
68
0
{
69
0
  int i, retval = 0;
70
71
0
  for (i = 0; i < the_repository->index->cache_nr; i++) {
72
0
    struct cache_entry *ce = the_repository->index->cache[i];
73
74
0
    if (!include_sparse &&
75
0
        (ce_skip_worktree(ce) ||
76
0
         !path_in_sparse_checkout(ce->name, the_repository->index)))
77
0
      continue;
78
0
    if (ce_stage(ce))
79
0
      continue; /* do not touch unmerged paths */
80
0
    if (!S_ISREG(ce->ce_mode) && !S_ISLNK(ce->ce_mode))
81
0
      continue; /* do not touch non blobs */
82
0
    if (pathspec && !ce_path_match(the_repository->index, ce, pathspec, NULL))
83
0
      continue;
84
0
    retval |= add_file_to_index(the_repository->index, ce->name,
85
0
              flags | ADD_CACHE_RENORMALIZE);
86
0
  }
87
88
0
  return retval;
89
0
}
90
91
static char *prune_directory(struct dir_struct *dir, struct pathspec *pathspec, int prefix)
92
0
{
93
0
  char *seen;
94
0
  int i;
95
0
  struct dir_entry **src, **dst;
96
97
0
  seen = xcalloc(pathspec->nr, 1);
98
99
0
  src = dst = dir->entries;
100
0
  i = dir->nr;
101
0
  while (--i >= 0) {
102
0
    struct dir_entry *entry = *src++;
103
0
    if (dir_path_match(the_repository->index, entry, pathspec, prefix, seen))
104
0
      *dst++ = entry;
105
0
  }
106
0
  dir->nr = dst - dir->entries;
107
0
  add_pathspec_matches_against_index(pathspec, the_repository->index, seen,
108
0
             PS_IGNORE_SKIP_WORKTREE);
109
0
  return seen;
110
0
}
111
112
static int refresh(int verbose, const struct pathspec *pathspec)
113
0
{
114
0
  char *seen;
115
0
  int i, ret = 0;
116
0
  char *skip_worktree_seen = NULL;
117
0
  struct string_list only_match_skip_worktree = STRING_LIST_INIT_NODUP;
118
0
  unsigned int flags = REFRESH_IGNORE_SKIP_WORKTREE |
119
0
        (verbose ? REFRESH_IN_PORCELAIN : REFRESH_QUIET);
120
121
0
  seen = xcalloc(pathspec->nr, 1);
122
0
  refresh_index(the_repository->index, flags, pathspec, seen,
123
0
          _("Unstaged changes after refreshing the index:"));
124
0
  for (i = 0; i < pathspec->nr; i++) {
125
0
    if (!seen[i]) {
126
0
      const char *path = pathspec->items[i].original;
127
128
0
      if (matches_skip_worktree(pathspec, i, &skip_worktree_seen) ||
129
0
          !path_in_sparse_checkout(path, the_repository->index)) {
130
0
        string_list_append(&only_match_skip_worktree,
131
0
               pathspec->items[i].original);
132
0
      } else {
133
0
        die(_("pathspec '%s' did not match any files"),
134
0
            pathspec->items[i].original);
135
0
      }
136
0
    }
137
0
  }
138
139
0
  if (only_match_skip_worktree.nr) {
140
0
    advise_on_updating_sparse_paths(&only_match_skip_worktree);
141
0
    ret = 1;
142
0
  }
143
144
0
  free(seen);
145
0
  free(skip_worktree_seen);
146
0
  string_list_clear(&only_match_skip_worktree, 0);
147
0
  return ret;
148
0
}
149
150
int interactive_add(const char **argv, const char *prefix, int patch)
151
0
{
152
0
  struct pathspec pathspec;
153
0
  int ret;
154
155
0
  parse_pathspec(&pathspec, 0,
156
0
           PATHSPEC_PREFER_FULL |
157
0
           PATHSPEC_SYMLINK_LEADING_PATH |
158
0
           PATHSPEC_PREFIX_ORIGIN,
159
0
           prefix, argv);
160
161
0
  if (patch)
162
0
    ret = !!run_add_p(the_repository, ADD_P_ADD, NULL, &pathspec);
163
0
  else
164
0
    ret = !!run_add_i(the_repository, &pathspec);
165
166
0
  clear_pathspec(&pathspec);
167
0
  return ret;
168
0
}
169
170
static int edit_patch(int argc, const char **argv, const char *prefix)
171
0
{
172
0
  char *file = git_pathdup("ADD_EDIT.patch");
173
0
  struct child_process child = CHILD_PROCESS_INIT;
174
0
  struct rev_info rev;
175
0
  int out;
176
0
  struct stat st;
177
178
0
  git_config(git_diff_basic_config, NULL); /* no "diff" UI options */
179
180
0
  if (repo_read_index(the_repository) < 0)
181
0
    die(_("could not read the index"));
182
183
0
  repo_init_revisions(the_repository, &rev, prefix);
184
0
  rev.diffopt.context = 7;
185
186
0
  argc = setup_revisions(argc, argv, &rev, NULL);
187
0
  rev.diffopt.output_format = DIFF_FORMAT_PATCH;
188
0
  rev.diffopt.use_color = 0;
189
0
  rev.diffopt.flags.ignore_dirty_submodules = 1;
190
0
  out = xopen(file, O_CREAT | O_WRONLY | O_TRUNC, 0666);
191
0
  rev.diffopt.file = xfdopen(out, "w");
192
0
  rev.diffopt.close_file = 1;
193
0
  run_diff_files(&rev, 0);
194
195
0
  if (launch_editor(file, NULL, NULL))
196
0
    die(_("editing patch failed"));
197
198
0
  if (stat(file, &st))
199
0
    die_errno(_("could not stat '%s'"), file);
200
0
  if (!st.st_size)
201
0
    die(_("empty patch. aborted"));
202
203
0
  child.git_cmd = 1;
204
0
  strvec_pushl(&child.args, "apply", "--recount", "--cached", file,
205
0
         NULL);
206
0
  if (run_command(&child))
207
0
    die(_("could not apply '%s'"), file);
208
209
0
  unlink(file);
210
0
  free(file);
211
0
  release_revisions(&rev);
212
0
  return 0;
213
0
}
214
215
static const char ignore_error[] =
216
N_("The following paths are ignored by one of your .gitignore files:\n");
217
218
static int verbose, show_only, ignored_too, refresh_only;
219
static int ignore_add_errors, intent_to_add, ignore_missing;
220
static int warn_on_embedded_repo = 1;
221
222
0
#define ADDREMOVE_DEFAULT 1
223
static int addremove = ADDREMOVE_DEFAULT;
224
static int addremove_explicit = -1; /* unspecified */
225
226
static char *chmod_arg;
227
228
static int ignore_removal_cb(const struct option *opt, const char *arg, int unset)
229
0
{
230
0
  BUG_ON_OPT_ARG(arg);
231
232
  /* if we are told to ignore, we are not adding removals */
233
0
  *(int *)opt->value = !unset ? 0 : 1;
234
0
  return 0;
235
0
}
236
237
static struct option builtin_add_options[] = {
238
  OPT__DRY_RUN(&show_only, N_("dry run")),
239
  OPT__VERBOSE(&verbose, N_("be verbose")),
240
  OPT_GROUP(""),
241
  OPT_BOOL('i', "interactive", &add_interactive, N_("interactive picking")),
242
  OPT_BOOL('p', "patch", &patch_interactive, N_("select hunks interactively")),
243
  OPT_BOOL('e', "edit", &edit_interactive, N_("edit current diff and apply")),
244
  OPT__FORCE(&ignored_too, N_("allow adding otherwise ignored files"), 0),
245
  OPT_BOOL('u', "update", &take_worktree_changes, N_("update tracked files")),
246
  OPT_BOOL(0, "renormalize", &add_renormalize, N_("renormalize EOL of tracked files (implies -u)")),
247
  OPT_BOOL('N', "intent-to-add", &intent_to_add, N_("record only the fact that the path will be added later")),
248
  OPT_BOOL('A', "all", &addremove_explicit, N_("add changes from all tracked and untracked files")),
249
  OPT_CALLBACK_F(0, "ignore-removal", &addremove_explicit,
250
    NULL /* takes no arguments */,
251
    N_("ignore paths removed in the working tree (same as --no-all)"),
252
    PARSE_OPT_NOARG, ignore_removal_cb),
253
  OPT_BOOL( 0 , "refresh", &refresh_only, N_("don't add, only refresh the index")),
254
  OPT_BOOL( 0 , "ignore-errors", &ignore_add_errors, N_("just skip files which cannot be added because of errors")),
255
  OPT_BOOL( 0 , "ignore-missing", &ignore_missing, N_("check if - even missing - files are ignored in dry run")),
256
  OPT_BOOL(0, "sparse", &include_sparse, N_("allow updating entries outside of the sparse-checkout cone")),
257
  OPT_STRING(0, "chmod", &chmod_arg, "(+|-)x",
258
       N_("override the executable bit of the listed files")),
259
  OPT_HIDDEN_BOOL(0, "warn-embedded-repo", &warn_on_embedded_repo,
260
      N_("warn when adding an embedded repository")),
261
  OPT_PATHSPEC_FROM_FILE(&pathspec_from_file),
262
  OPT_PATHSPEC_FILE_NUL(&pathspec_file_nul),
263
  OPT_END(),
264
};
265
266
static int add_config(const char *var, const char *value,
267
          const struct config_context *ctx, void *cb)
268
0
{
269
0
  if (!strcmp(var, "add.ignoreerrors") ||
270
0
      !strcmp(var, "add.ignore-errors")) {
271
0
    ignore_add_errors = git_config_bool(var, value);
272
0
    return 0;
273
0
  }
274
275
0
  if (git_color_config(var, value, cb) < 0)
276
0
    return -1;
277
278
0
  return git_default_config(var, value, ctx, cb);
279
0
}
280
281
static const char embedded_advice[] = N_(
282
"You've added another git repository inside your current repository.\n"
283
"Clones of the outer repository will not contain the contents of\n"
284
"the embedded repository and will not know how to obtain it.\n"
285
"If you meant to add a submodule, use:\n"
286
"\n"
287
" git submodule add <url> %s\n"
288
"\n"
289
"If you added this path by mistake, you can remove it from the\n"
290
"index with:\n"
291
"\n"
292
" git rm --cached %s\n"
293
"\n"
294
"See \"git help submodule\" for more information."
295
);
296
297
static void check_embedded_repo(const char *path)
298
0
{
299
0
  struct strbuf name = STRBUF_INIT;
300
0
  static int adviced_on_embedded_repo = 0;
301
302
0
  if (!warn_on_embedded_repo)
303
0
    return;
304
0
  if (!ends_with(path, "/"))
305
0
    return;
306
307
  /* Drop trailing slash for aesthetics */
308
0
  strbuf_addstr(&name, path);
309
0
  strbuf_strip_suffix(&name, "/");
310
311
0
  warning(_("adding embedded git repository: %s"), name.buf);
312
0
  if (!adviced_on_embedded_repo) {
313
0
    advise_if_enabled(ADVICE_ADD_EMBEDDED_REPO,
314
0
          embedded_advice, name.buf, name.buf);
315
0
    adviced_on_embedded_repo = 1;
316
0
  }
317
318
0
  strbuf_release(&name);
319
0
}
320
321
static int add_files(struct dir_struct *dir, int flags)
322
0
{
323
0
  int i, exit_status = 0;
324
0
  struct string_list matched_sparse_paths = STRING_LIST_INIT_NODUP;
325
326
0
  if (dir->ignored_nr) {
327
0
    fprintf(stderr, _(ignore_error));
328
0
    for (i = 0; i < dir->ignored_nr; i++)
329
0
      fprintf(stderr, "%s\n", dir->ignored[i]->name);
330
0
    advise_if_enabled(ADVICE_ADD_IGNORED_FILE,
331
0
          _("Use -f if you really want to add them."));
332
0
    exit_status = 1;
333
0
  }
334
335
0
  for (i = 0; i < dir->nr; i++) {
336
0
    if (!include_sparse &&
337
0
        !path_in_sparse_checkout(dir->entries[i]->name, the_repository->index)) {
338
0
      string_list_append(&matched_sparse_paths,
339
0
             dir->entries[i]->name);
340
0
      continue;
341
0
    }
342
0
    if (add_file_to_index(the_repository->index, dir->entries[i]->name, flags)) {
343
0
      if (!ignore_add_errors)
344
0
        die(_("adding files failed"));
345
0
      exit_status = 1;
346
0
    } else {
347
0
      check_embedded_repo(dir->entries[i]->name);
348
0
    }
349
0
  }
350
351
0
  if (matched_sparse_paths.nr) {
352
0
    advise_on_updating_sparse_paths(&matched_sparse_paths);
353
0
    exit_status = 1;
354
0
  }
355
356
0
  string_list_clear(&matched_sparse_paths, 0);
357
358
0
  return exit_status;
359
0
}
360
361
int cmd_add(int argc, const char **argv, const char *prefix)
362
0
{
363
0
  int exit_status = 0;
364
0
  struct pathspec pathspec;
365
0
  struct dir_struct dir = DIR_INIT;
366
0
  int flags;
367
0
  int add_new_files;
368
0
  int require_pathspec;
369
0
  char *seen = NULL;
370
0
  char *ps_matched = NULL;
371
0
  struct lock_file lock_file = LOCK_INIT;
372
373
0
  git_config(add_config, NULL);
374
375
0
  argc = parse_options(argc, argv, prefix, builtin_add_options,
376
0
        builtin_add_usage, PARSE_OPT_KEEP_ARGV0);
377
0
  if (patch_interactive)
378
0
    add_interactive = 1;
379
0
  if (add_interactive) {
380
0
    if (show_only)
381
0
      die(_("options '%s' and '%s' cannot be used together"), "--dry-run", "--interactive/--patch");
382
0
    if (pathspec_from_file)
383
0
      die(_("options '%s' and '%s' cannot be used together"), "--pathspec-from-file", "--interactive/--patch");
384
0
    exit(interactive_add(argv + 1, prefix, patch_interactive));
385
0
  }
386
387
0
  if (edit_interactive) {
388
0
    if (pathspec_from_file)
389
0
      die(_("options '%s' and '%s' cannot be used together"), "--pathspec-from-file", "--edit");
390
0
    return(edit_patch(argc, argv, prefix));
391
0
  }
392
0
  argc--;
393
0
  argv++;
394
395
0
  if (0 <= addremove_explicit)
396
0
    addremove = addremove_explicit;
397
0
  else if (take_worktree_changes && ADDREMOVE_DEFAULT)
398
0
    addremove = 0; /* "-u" was given but not "-A" */
399
400
0
  if (addremove && take_worktree_changes)
401
0
    die(_("options '%s' and '%s' cannot be used together"), "-A", "-u");
402
403
0
  if (!show_only && ignore_missing)
404
0
    die(_("the option '%s' requires '%s'"), "--ignore-missing", "--dry-run");
405
406
0
  if (chmod_arg && ((chmod_arg[0] != '-' && chmod_arg[0] != '+') ||
407
0
        chmod_arg[1] != 'x' || chmod_arg[2]))
408
0
    die(_("--chmod param '%s' must be either -x or +x"), chmod_arg);
409
410
0
  add_new_files = !take_worktree_changes && !refresh_only && !add_renormalize;
411
0
  require_pathspec = !(take_worktree_changes || (0 < addremove_explicit));
412
413
0
  prepare_repo_settings(the_repository);
414
0
  the_repository->settings.command_requires_full_index = 0;
415
416
0
  repo_hold_locked_index(the_repository, &lock_file, LOCK_DIE_ON_ERROR);
417
418
  /*
419
   * Check the "pathspec '%s' did not match any files" block
420
   * below before enabling new magic.
421
   */
422
0
  parse_pathspec(&pathspec, 0,
423
0
           PATHSPEC_PREFER_FULL |
424
0
           PATHSPEC_SYMLINK_LEADING_PATH,
425
0
           prefix, argv);
426
427
0
  if (pathspec_from_file) {
428
0
    if (pathspec.nr)
429
0
      die(_("'%s' and pathspec arguments cannot be used together"), "--pathspec-from-file");
430
431
0
    parse_pathspec_file(&pathspec, 0,
432
0
            PATHSPEC_PREFER_FULL |
433
0
            PATHSPEC_SYMLINK_LEADING_PATH,
434
0
            prefix, pathspec_from_file, pathspec_file_nul);
435
0
  } else if (pathspec_file_nul) {
436
0
    die(_("the option '%s' requires '%s'"), "--pathspec-file-nul", "--pathspec-from-file");
437
0
  }
438
439
0
  if (require_pathspec && pathspec.nr == 0) {
440
0
    fprintf(stderr, _("Nothing specified, nothing added.\n"));
441
0
    advise_if_enabled(ADVICE_ADD_EMPTY_PATHSPEC,
442
0
          _("Maybe you wanted to say 'git add .'?"));
443
0
    return 0;
444
0
  }
445
446
0
  if (!take_worktree_changes && addremove_explicit < 0 && pathspec.nr)
447
    /* Turn "git add pathspec..." to "git add -A pathspec..." */
448
0
    addremove = 1;
449
450
0
  flags = ((verbose ? ADD_CACHE_VERBOSE : 0) |
451
0
     (show_only ? ADD_CACHE_PRETEND : 0) |
452
0
     (intent_to_add ? ADD_CACHE_INTENT : 0) |
453
0
     (ignore_add_errors ? ADD_CACHE_IGNORE_ERRORS : 0) |
454
0
     (!(addremove || take_worktree_changes)
455
0
      ? ADD_CACHE_IGNORE_REMOVAL : 0));
456
457
0
  if (repo_read_index_preload(the_repository, &pathspec, 0) < 0)
458
0
    die(_("index file corrupt"));
459
460
0
  die_in_unpopulated_submodule(the_repository->index, prefix);
461
0
  die_path_inside_submodule(the_repository->index, &pathspec);
462
463
0
  if (add_new_files) {
464
0
    int baselen;
465
466
    /* Set up the default git porcelain excludes */
467
0
    if (!ignored_too) {
468
0
      dir.flags |= DIR_COLLECT_IGNORED;
469
0
      setup_standard_excludes(&dir);
470
0
    }
471
472
    /* This picks up the paths that are not tracked */
473
0
    baselen = fill_directory(&dir, the_repository->index, &pathspec);
474
0
    if (pathspec.nr)
475
0
      seen = prune_directory(&dir, &pathspec, baselen);
476
0
  }
477
478
0
  if (refresh_only) {
479
0
    exit_status |= refresh(verbose, &pathspec);
480
0
    goto finish;
481
0
  }
482
483
0
  if (pathspec.nr) {
484
0
    int i;
485
0
    char *skip_worktree_seen = NULL;
486
0
    struct string_list only_match_skip_worktree = STRING_LIST_INIT_NODUP;
487
488
0
    if (!seen)
489
0
      seen = find_pathspecs_matching_against_index(&pathspec,
490
0
          the_repository->index, PS_IGNORE_SKIP_WORKTREE);
491
492
    /*
493
     * file_exists() assumes exact match
494
     */
495
0
    GUARD_PATHSPEC(&pathspec,
496
0
             PATHSPEC_FROMTOP |
497
0
             PATHSPEC_LITERAL |
498
0
             PATHSPEC_GLOB |
499
0
             PATHSPEC_ICASE |
500
0
             PATHSPEC_EXCLUDE |
501
0
             PATHSPEC_ATTR);
502
503
0
    for (i = 0; i < pathspec.nr; i++) {
504
0
      const char *path = pathspec.items[i].match;
505
506
0
      if (pathspec.items[i].magic & PATHSPEC_EXCLUDE)
507
0
        continue;
508
0
      if (seen[i])
509
0
        continue;
510
511
0
      if (!include_sparse &&
512
0
          matches_skip_worktree(&pathspec, i, &skip_worktree_seen)) {
513
0
        string_list_append(&only_match_skip_worktree,
514
0
               pathspec.items[i].original);
515
0
        continue;
516
0
      }
517
518
      /* Don't complain at 'git add .' on empty repo */
519
0
      if (!path[0])
520
0
        continue;
521
522
0
      if ((pathspec.items[i].magic & (PATHSPEC_GLOB | PATHSPEC_ICASE)) ||
523
0
          !file_exists(path)) {
524
0
        if (ignore_missing) {
525
0
          int dtype = DT_UNKNOWN;
526
0
          if (is_excluded(&dir, the_repository->index, path, &dtype))
527
0
            dir_add_ignored(&dir, the_repository->index,
528
0
                path, pathspec.items[i].len);
529
0
        } else
530
0
          die(_("pathspec '%s' did not match any files"),
531
0
              pathspec.items[i].original);
532
0
      }
533
0
    }
534
535
536
0
    if (only_match_skip_worktree.nr) {
537
0
      advise_on_updating_sparse_paths(&only_match_skip_worktree);
538
0
      exit_status = 1;
539
0
    }
540
541
0
    free(seen);
542
0
    free(skip_worktree_seen);
543
0
    string_list_clear(&only_match_skip_worktree, 0);
544
0
  }
545
546
0
  begin_odb_transaction();
547
548
0
  ps_matched = xcalloc(pathspec.nr, 1);
549
0
  if (add_renormalize)
550
0
    exit_status |= renormalize_tracked_files(&pathspec, flags);
551
0
  else
552
0
    exit_status |= add_files_to_cache(the_repository, prefix,
553
0
              &pathspec, ps_matched,
554
0
              include_sparse, flags);
555
556
0
  if (take_worktree_changes && !add_renormalize && !ignore_add_errors &&
557
0
      report_path_error(ps_matched, &pathspec))
558
0
    exit(128);
559
560
0
  if (add_new_files)
561
0
    exit_status |= add_files(&dir, flags);
562
563
0
  if (chmod_arg && pathspec.nr)
564
0
    exit_status |= chmod_pathspec(&pathspec, chmod_arg[0], show_only);
565
0
  end_odb_transaction();
566
567
0
finish:
568
0
  if (write_locked_index(the_repository->index, &lock_file,
569
0
             COMMIT_LOCK | SKIP_IF_UNCHANGED))
570
0
    die(_("unable to write new index file"));
571
572
0
  free(ps_matched);
573
0
  dir_clear(&dir);
574
0
  clear_pathspec(&pathspec);
575
0
  return exit_status;
576
0
}