Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion Documentation/git-add.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ SYNOPSIS
[synopsis]
git add [--verbose | -v] [--dry-run | -n] [--force | -f] [--interactive | -i] [--patch | -p]
[--edit | -e] [--[no-]all | -A | --[no-]ignore-removal | [--update | -u]] [--sparse]
[--intent-to-add | -N] [--refresh] [--ignore-errors] [--ignore-missing] [--renormalize]
[--intent-to-add | -N] [--refresh] [--ignore-errors] [--ignore-missing] [--renormalize] [--no-verify]
[--chmod=(+|-)x] [--pathspec-from-file=<file> [--pathspec-file-nul]]
[--] [<pathspec>...]

Expand Down Expand Up @@ -42,6 +42,10 @@ use the `--force` option to add ignored files. If you specify the exact
filename of an ignored file, `git add` will fail with a list of ignored
files. Otherwise it will silently ignore the file.

A `pre-add` hook can be run to inspect or reject the proposed index update
after `git add` computes staging and writes it to the index lockfile,
but before writing it to the final index. See linkgit:githooks[5].

Please see linkgit:git-commit[1] for alternative ways to add content to a
commit.

Expand Down Expand Up @@ -163,6 +167,10 @@ for `git add --no-all <pathspec>...`, i.e. ignored removed files.
Don't add the file(s), but only refresh their stat()
information in the index.

`--no-verify`::
Bypass the `pre-add` hook if it exists. See linkgit:githooks[5] for
more information about hooks.

`--ignore-errors`::
If some files could not be added because of errors indexing
them, do not abort the operation, but continue adding the
Expand Down Expand Up @@ -451,6 +459,7 @@ linkgit:git-reset[1]
linkgit:git-mv[1]
linkgit:git-commit[1]
linkgit:git-update-index[1]
linkgit:githooks[5]

GIT
---
Expand Down
30 changes: 30 additions & 0 deletions Documentation/githooks.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,36 @@ and is invoked after the patch is applied and a commit is made.
This hook is meant primarily for notification, and cannot affect
the outcome of `git am`.

pre-add
~~~~~~~

This hook is invoked by linkgit:git-add[1], and can be bypassed with the
`--no-verify` option. It is not invoked for `--interactive`, `--patch`,
`--edit`, or `--dry-run`.

It takes two parameters: the path to the index file for this invocation
of `git add`, and the path to the lockfile containing the proposed
index after staging. It does not read from standard input. If no index
exists yet, the first parameter names a path that does not exist and
should be treated as an empty index.

The hook is invoked after the index has been updated in memory and
written to the lockfile, but before it is committed to the final index
path. Exiting with a non-zero status causes `git add` to reject the
proposed state, roll back the lockfile, and leave the index unchanged.
Exiting with zero status allows the index update to be committed.

Git does not set `GIT_INDEX_FILE` for this hook. Hook authors may
set `GIT_INDEX_FILE="$1"` to inspect current index state and
`GIT_INDEX_FILE="$2"` to inspect proposed index state.

This hook can be used to prevent staging of files based on names, content,
or sizes (e.g., to block `.env` files, secret keys, or large files).

This hook is not invoked by `git commit -a` or `git commit --include`
which still can run the `pre-commit` hook, providing a control point at
commit time.

pre-commit
~~~~~~~~~~

Expand Down
47 changes: 43 additions & 4 deletions builtin/add.c
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
#include "strvec.h"
#include "submodule.h"
#include "add-interactive.h"
#include "hook.h"
#include "abspath.h"

static const char * const builtin_add_usage[] = {
N_("git add [<options>] [--] <pathspec>..."),
Expand All @@ -36,6 +38,7 @@ static int take_worktree_changes;
static int add_renormalize;
static int pathspec_file_nul;
static int include_sparse;
static int no_verify;
static const char *pathspec_from_file;

static int chmod_pathspec(struct repository *repo,
Expand Down Expand Up @@ -271,6 +274,7 @@ static struct option builtin_add_options[] = {
OPT_BOOL( 0 , "refresh", &refresh_only, N_("don't add, only refresh the index")),
OPT_BOOL( 0 , "ignore-errors", &ignore_add_errors, N_("just skip files which cannot be added because of errors")),
OPT_BOOL( 0 , "ignore-missing", &ignore_missing, N_("check if - even missing - files are ignored in dry run")),
OPT_BOOL( 0 , "no-verify", &no_verify, N_("bypass pre-add hook")),
OPT_BOOL(0, "sparse", &include_sparse, N_("allow updating entries outside of the sparse-checkout cone")),
OPT_STRING(0, "chmod", &chmod_arg, "(+|-)x",
N_("override the executable bit of the listed files")),
Expand Down Expand Up @@ -391,6 +395,8 @@ int cmd_add(int argc,
char *ps_matched = NULL;
struct lock_file lock_file = LOCK_INIT;
struct odb_transaction *transaction;
int run_pre_add = 0;
char *orig_index_path = NULL;

repo_config(repo, add_config, NULL);

Expand Down Expand Up @@ -576,6 +582,11 @@ int cmd_add(int argc,
string_list_clear(&only_match_skip_worktree, 0);
}

if (!show_only && !no_verify && find_hook(repo, "pre-add")) {
run_pre_add = 1;
orig_index_path = absolute_pathdup(repo_get_index_file(repo));
}

transaction = odb_transaction_begin(repo->objects);

ps_matched = xcalloc(pathspec.nr, 1);
Expand All @@ -587,8 +598,10 @@ int cmd_add(int argc,
include_sparse, flags);

if (take_worktree_changes && !add_renormalize && !ignore_add_errors &&
report_path_error(ps_matched, &pathspec))
report_path_error(ps_matched, &pathspec)) {
free(orig_index_path);
exit(128);
}

if (add_new_files)
exit_status |= add_files(repo, &dir, flags);
Expand All @@ -598,9 +611,35 @@ int cmd_add(int argc,
odb_transaction_commit(transaction);

finish:
if (write_locked_index(repo->index, &lock_file,
COMMIT_LOCK | SKIP_IF_UNCHANGED))
die(_("unable to write new index file"));
if (run_pre_add && repo->index->cache_changed) {
struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT;

if (write_locked_index(repo->index, &lock_file,
SKIP_INDEX_CHANGE_HOOK))
die(_("unable to write proposed index"));

strvec_push(&opt.args, orig_index_path);
strvec_push(&opt.args, get_lock_file_path(&lock_file));
if (run_hooks_opt(repo, "pre-add", &opt)) {
rollback_lock_file(&lock_file); /* hook rejected */
exit_status = 1;
} else if (commit_lock_file(&lock_file)) {
die(_("unable to write new index file"));
} else {
run_hooks_l(repo, "post-index-change",
repo->index->updated_workdir ? "1" : "0",
repo->index->updated_skipworktree ? "1" : "0",
NULL);
}
repo->index->updated_workdir = 0;
repo->index->updated_skipworktree = 0;
} else {
if (write_locked_index(repo->index, &lock_file,
COMMIT_LOCK | SKIP_IF_UNCHANGED))
die(_("unable to write new index file"));
}

free(orig_index_path);

free(ps_matched);
dir_clear(&dir);
Expand Down
1 change: 1 addition & 0 deletions read-cache-ll.h
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,7 @@ int is_index_unborn(struct index_state *);
/* For use with `write_locked_index()`. */
#define COMMIT_LOCK (1 << 0)
#define SKIP_IF_UNCHANGED (1 << 1)
#define SKIP_INDEX_CHANGE_HOOK (1 << 2)

/*
* Write the index while holding an already-taken lock. Close the lock,
Expand Down
13 changes: 7 additions & 6 deletions read-cache.c
Original file line number Diff line number Diff line change
Expand Up @@ -3161,12 +3161,13 @@ static int do_write_locked_index(struct index_state *istate,
else
ret = close_lock_file_gently(lock);

run_hooks_l(the_repository, "post-index-change",
istate->updated_workdir ? "1" : "0",
istate->updated_skipworktree ? "1" : "0", NULL);
istate->updated_workdir = 0;
istate->updated_skipworktree = 0;

if (!(flags & SKIP_INDEX_CHANGE_HOOK)) {
run_hooks_l(the_repository, "post-index-change",
istate->updated_workdir ? "1" : "0",
istate->updated_skipworktree ? "1" : "0", NULL);
istate->updated_workdir = 0;
istate->updated_skipworktree = 0;
}
return ret;
}

Expand Down
1 change: 1 addition & 0 deletions t/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -415,6 +415,7 @@ integration_tests = [
't3703-add-magic-pathspec.sh',
't3704-add-pathspec-file.sh',
't3705-add-sparse-checkout.sh',
't3706-pre-add-hook.sh',
't3800-mktag.sh',
't3900-i18n-commit.sh',
't3901-i18n-patch.sh',
Expand Down
Loading
Loading