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
10 changes: 8 additions & 2 deletions Documentation/git-add.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ 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]
[--chmod=(+|-)x] [--pathspec-from-file=<file> [--pathspec-file-nul]]
[--no-verify] [--chmod=(+|-)x] [--pathspec-from-file=<file> [--pathspec-file-nul]]
[--] [<pathspec>...]

DESCRIPTION
Expand Down Expand Up @@ -42,10 +42,11 @@ 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 used to reject `git add` (see linkgit:githooks[5]).

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


OPTIONS
-------
`<pathspec>...`::
Expand Down Expand Up @@ -163,6 +164,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 +456,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 arguments: the path to the index file for this invocation
of `git add`, and the path to the lockfile containing the proposed
index after staging. If no index exists yet, the first argument 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. The
hook accepts or rejects the entire proposed update; per-path filtering
is not supported. Both files should be treated as read-only by the hook.

Hook authors may set `GIT_INDEX_FILE="$1"` to inspect the current index
state and `GIT_INDEX_FILE="$2"` to inspect the 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
38 changes: 35 additions & 3 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 && hook_exists(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 @@ -598,9 +609,30 @@ 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 {
emit_post_index_change(repo->index);
}
} 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
3 changes: 3 additions & 0 deletions read-cache-ll.h
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,9 @@ 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)

void emit_post_index_change(struct index_state *istate);

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

if (!(flags & SKIP_INDEX_CHANGE_HOOK))
emit_post_index_change(istate);
return ret;
}

void emit_post_index_change(struct index_state *istate)
{
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;
}

static int write_split_index(struct index_state *istate,
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