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
27 changes: 27 additions & 0 deletions Documentation/githooks.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,33 @@ 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 a copy of the index before 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. No special environment
variables are set. 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 location.

Exiting with a non-zero status causes `git add` to abort and leaves the
index unchanged. Exiting with zero status causes the staged changes to
take effect.

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
68 changes: 64 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 "copy.h"

static const char * const builtin_add_usage[] = {
N_("git add [<options>] [--] <pathspec>..."),
Expand All @@ -36,6 +38,7 @@
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 @@
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,9 @@
char *ps_matched = NULL;
struct lock_file lock_file = LOCK_INIT;
struct odb_transaction *transaction;
int run_pre_add = 0;
struct tempfile *orig_index = NULL;
char *orig_index_path = NULL;

repo_config(repo, add_config, NULL);

Expand Down Expand Up @@ -576,6 +583,34 @@
string_list_clear(&only_match_skip_worktree, 0);
}

if (!show_only && !no_verify && find_hook(repo, "pre-add")) {
int fd_in, status;
const char *index_file = repo_get_index_file(repo);
char *template;

run_pre_add = 1;
template = xstrfmt("%s.pre-add.XXXXXX", index_file);
orig_index = xmks_tempfile(template);
free(template);

fd_in = open(index_file, O_RDONLY);
if (fd_in >= 0) {
status = copy_fd(fd_in, get_tempfile_fd(orig_index));
if (close(fd_in))
die_errno(_("unable to close index for pre-add hook"));
if (close_tempfile_gently(orig_index))
die_errno(_("unable to close temporary index copy"));
if (status < 0)
die(_("failed to copy index for pre-add hook"));
} else if (errno == ENOENT) {
orig_index_path = xstrdup(get_tempfile_path(orig_index));
if (delete_tempfile(&orig_index))
die_errno(_("unable to remove temporary index copy"));
} else {
die_errno(_("unable to open index for pre-add hook"));
}
}

transaction = odb_transaction_begin(repo->objects);

ps_matched = xcalloc(pathspec.nr, 1);
Expand All @@ -587,8 +622,12 @@
include_sparse, flags);

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

if (add_new_files)
exit_status |= add_files(repo, &dir, flags);
Expand All @@ -598,9 +637,30 @@
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 && !exit_status && repo->index->cache_changed) {
struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT;

if (write_locked_index(repo->index, &lock_file, 0))
die(_("unable to write new index file"));

Check failure on line 645 in builtin/add.c

View workflow job for this annotation

GitHub Actions / almalinux-8 (almalinux:8)

builtin/add.c:645:30: 'RUN_HOOKS_OPT_INIT' undeclared (first use in this function); did you mean 'RUN_HOOKS_OPT_INIT_SERIAL'?

Check failure on line 645 in builtin/add.c

View workflow job for this annotation

GitHub Actions / debian-11 (debian:11)

builtin/add.c:645:30: 'RUN_HOOKS_OPT_INIT' undeclared (first use in this function); did you mean 'RUN_HOOKS_OPT_INIT_SERIAL'?

Check failure on line 645 in builtin/add.c

View workflow job for this annotation

GitHub Actions / linux-leaks (ubuntu:rolling)

builtin/add.c:645:44: ‘RUN_HOOKS_OPT_INIT’ undeclared (first use in this function); did you mean ‘RUN_HOOKS_OPT_INIT_SERIAL’?

Check failure on line 645 in builtin/add.c

View workflow job for this annotation

GitHub Actions / linux-reftable-leaks (ubuntu:rolling)

builtin/add.c:645:44: ‘RUN_HOOKS_OPT_INIT’ undeclared (first use in this function); did you mean ‘RUN_HOOKS_OPT_INIT_SERIAL’?

Check failure on line 645 in builtin/add.c

View workflow job for this annotation

GitHub Actions / linux-TEST-vars (ubuntu:20.04)

builtin/add.c:645:30: 'RUN_HOOKS_OPT_INIT' undeclared (first use in this function); did you mean 'RUN_HOOKS_OPT_INIT_SERIAL'?

Check failure on line 645 in builtin/add.c

View workflow job for this annotation

GitHub Actions / linux32 (i386/ubuntu:focal)

builtin/add.c:645:30: 'RUN_HOOKS_OPT_INIT' undeclared (first use in this function); did you mean 'RUN_HOOKS_OPT_INIT_SERIAL'?

Check failure on line 645 in builtin/add.c

View workflow job for this annotation

GitHub Actions / linux-sha256 (ubuntu:rolling)

builtin/add.c:645:30: use of undeclared identifier 'RUN_HOOKS_OPT_INIT'

Check failure on line 645 in builtin/add.c

View workflow job for this annotation

GitHub Actions / linux-breaking-changes (ubuntu:rolling)

builtin/add.c:645:44: ‘RUN_HOOKS_OPT_INIT’ undeclared (first use in this function); did you mean ‘RUN_HOOKS_OPT_INIT_SERIAL’?

Check failure on line 645 in builtin/add.c

View workflow job for this annotation

GitHub Actions / linux-asan-ubsan (ubuntu:rolling)

builtin/add.c:645:30: use of undeclared identifier 'RUN_HOOKS_OPT_INIT'

Check failure on line 645 in builtin/add.c

View workflow job for this annotation

GitHub Actions / linux-reftable (ubuntu:rolling)

builtin/add.c:645:30: use of undeclared identifier 'RUN_HOOKS_OPT_INIT'

Check failure on line 645 in builtin/add.c

View workflow job for this annotation

GitHub Actions / win build

builtin/add.c:645:44: 'RUN_HOOKS_OPT_INIT' undeclared (first use in this function); did you mean 'RUN_HOOKS_OPT_INIT_SERIAL'?
strvec_push(&opt.args, orig_index ? get_tempfile_path(orig_index) :
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)) /* hook approved */
die(_("unable to write new index file"));
}
} else {
if (write_locked_index(repo->index, &lock_file,
COMMIT_LOCK | SKIP_IF_UNCHANGED))
die(_("unable to write new index file"));
}

delete_tempfile(&orig_index);
free(orig_index_path);

free(ps_matched);
dir_clear(&dir);
Expand Down
1 change: 1 addition & 0 deletions t/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -412,6 +412,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