Merge branch 'gt/add-u-commit-i-pathspec-check'

"git add -u <pathspec>" and "git commit [-i] <pathspec>" did not
diagnose a pathspec element that did not match any files in certain
situations, unlike "git add <pathspec>" did.

* gt/add-u-commit-i-pathspec-check:
  builtin/add: error out when passing untracked path with -u
  builtin/commit: error out when passing untracked path with -i
  revision: optionally record matches with pathspec elements
This commit is contained in:
Junio C Hamano 2024-04-15 14:11:43 -07:00
commit d75ec4c627
9 changed files with 46 additions and 25 deletions

View File

@ -368,6 +368,7 @@ int cmd_add(int argc, const char **argv, const char *prefix)
int add_new_files;
int require_pathspec;
char *seen = NULL;
char *ps_matched = NULL;
struct lock_file lock_file = LOCK_INIT;
git_config(add_config, NULL);
@ -545,12 +546,17 @@ int cmd_add(int argc, const char **argv, const char *prefix)
begin_odb_transaction();
ps_matched = xcalloc(pathspec.nr, 1);
if (add_renormalize)
exit_status |= renormalize_tracked_files(&pathspec, flags);
else
exit_status |= add_files_to_cache(the_repository, prefix,
&pathspec, include_sparse,
flags);
&pathspec, ps_matched,
include_sparse, flags);
if (take_worktree_changes && !add_renormalize && !ignore_add_errors &&
report_path_error(ps_matched, &pathspec))
exit(128);
if (add_new_files)
exit_status |= add_files(&dir, flags);
@ -564,6 +570,7 @@ finish:
COMMIT_LOCK | SKIP_IF_UNCHANGED))
die(_("unable to write new index file"));
free(ps_matched);
dir_clear(&dir);
clear_pathspec(&pathspec);
return exit_status;

View File

@ -882,7 +882,8 @@ static int merge_working_tree(const struct checkout_opts *opts,
* entries in the index.
*/
add_files_to_cache(the_repository, NULL, NULL, 0, 0);
add_files_to_cache(the_repository, NULL, NULL, NULL, 0,
0);
init_merge_options(&o, the_repository);
o.verbosity = 0;
work = write_in_core_index_as_tree(the_repository);

View File

@ -441,16 +441,21 @@ static const char *prepare_index(const char **argv, const char *prefix,
* (B) on failure, rollback the real index.
*/
if (all || (also && pathspec.nr)) {
char *ps_matched = xcalloc(pathspec.nr, 1);
repo_hold_locked_index(the_repository, &index_lock,
LOCK_DIE_ON_ERROR);
add_files_to_cache(the_repository, also ? prefix : NULL,
&pathspec, 0, 0);
&pathspec, ps_matched, 0, 0);
if (!all && report_path_error(ps_matched, &pathspec))
exit(128);
refresh_cache_or_die(refresh_flags);
cache_tree_update(&the_index, WRITE_TREE_SILENT);
if (write_locked_index(&the_index, &index_lock, 0))
die(_("unable to write new index file"));
commit_style = COMMIT_NORMAL;
ret = get_lock_file_path(&index_lock);
free(ps_matched);
goto out;
}

View File

@ -127,7 +127,16 @@ void run_diff_files(struct rev_info *revs, unsigned int option)
if (diff_can_quit_early(&revs->diffopt))
break;
if (!ce_path_match(istate, ce, &revs->prune_data, NULL))
/*
* NEEDSWORK:
* Here we filter with pathspec but the result is further
* filtered out when --relative is in effect. To end-users,
* a pathspec element that matched only to paths outside the
* current directory is like not matching anything at all;
* the handling of ps_matched[] here may become problematic
* if/when we add the "--error-unmatch" option to "git diff".
*/
if (!ce_path_match(istate, ce, &revs->prune_data, revs->ps_matched))
continue;
if (revs->diffopt.prefix &&

View File

@ -480,8 +480,8 @@ extern int verify_ce_order;
int cmp_cache_name_compare(const void *a_, const void *b_);
int add_files_to_cache(struct repository *repo, const char *prefix,
const struct pathspec *pathspec, int include_sparse,
int flags);
const struct pathspec *pathspec, char *ps_matched,
int include_sparse, int flags);
void overlay_tree_on_index(struct index_state *istate,
const char *tree_name, const char *prefix);

View File

@ -3958,8 +3958,8 @@ static void update_callback(struct diff_queue_struct *q,
}
int add_files_to_cache(struct repository *repo, const char *prefix,
const struct pathspec *pathspec, int include_sparse,
int flags)
const struct pathspec *pathspec, char *ps_matched,
int include_sparse, int flags)
{
struct update_callback_data data;
struct rev_info rev;
@ -3971,8 +3971,10 @@ int add_files_to_cache(struct repository *repo, const char *prefix,
repo_init_revisions(repo, &rev, prefix);
setup_revisions(0, NULL, &rev, NULL);
if (pathspec)
if (pathspec) {
copy_pathspec(&rev.prune_data, pathspec);
rev.ps_matched = ps_matched;
}
rev.diffopt.output_format = DIFF_FORMAT_CALLBACK;
rev.diffopt.format_callback = update_callback;
rev.diffopt.format_callback_data = &data;

View File

@ -142,6 +142,7 @@ struct rev_info {
/* Basic information */
const char *prefix;
const char *def;
char *ps_matched; /* optionally record matches of prune_data */
struct pathspec prune_data;
/*

View File

@ -65,6 +65,16 @@ test_expect_success 'update did not touch untracked files' '
test_must_be_empty out
'
test_expect_success 'error out when passing untracked path' '
git reset --hard &&
echo content >>baz &&
echo content >>top &&
test_must_fail git add -u baz top 2>err &&
test_grep -e "error: pathspec .baz. did not match any file(s) known to git" err &&
git diff --cached --name-only >actual &&
test_must_be_empty actual
'
test_expect_success 'cache tree has not been corrupted' '
git ls-files -s |

View File

@ -101,22 +101,8 @@ test_expect_success 'fail to commit untracked file (even with --include/--only)'
test_must_fail git commit --only -m "baz" baz 2>err &&
test_grep -e "$error" err &&
# TODO: as for --include, the below command will fail because
# nothing is staged. If something was staged, it would not fail
# even though the provided pathspec does not match any tracked
# path. (However, the untracked paths that match the pathspec are
# not committed and only the staged changes get committed.)
# In either cases, no error is returned to stderr like in (--only
# and without --only/--include) cases. In a similar manner,
# "git add -u baz" also does not error out.
#
# Therefore, the below test is just to document the current behavior
# and is not an endorsement to the current behavior, and we may
# want to fix this. And when that happens, this test should be
# updated accordingly.
test_must_fail git commit --include -m "baz" baz 2>err &&
test_must_be_empty err
test_grep -e "$error" err
'
test_expect_success 'setup: non-initial commit' '