run-command: add stdin callback for parallelization
[git] / hook.c
1 #include "cache.h"
2 #include "hook.h"
3 #include "run-command.h"
4
5 const char *find_hook(const char *name)
6 {
7         static struct strbuf path = STRBUF_INIT;
8
9         strbuf_reset(&path);
10         strbuf_git_path(&path, "hooks/%s", name);
11         if (access(path.buf, X_OK) < 0) {
12                 int err = errno;
13
14 #ifdef STRIP_EXTENSION
15                 strbuf_addstr(&path, STRIP_EXTENSION);
16                 if (access(path.buf, X_OK) >= 0)
17                         return path.buf;
18                 if (errno == EACCES)
19                         err = errno;
20 #endif
21
22                 if (err == EACCES && advice_ignored_hook) {
23                         static struct string_list advise_given = STRING_LIST_INIT_DUP;
24
25                         if (!string_list_lookup(&advise_given, name)) {
26                                 string_list_insert(&advise_given, name);
27                                 advise(_("The '%s' hook was ignored because "
28                                          "it's not set as executable.\n"
29                                          "You can disable this warning with "
30                                          "`git config advice.ignoredHook false`."),
31                                        path.buf);
32                         }
33                 }
34                 return NULL;
35         }
36         return path.buf;
37 }
38
39 int hook_exists(const char *name)
40 {
41         return !!find_hook(name);
42 }
43
44 void run_hooks_opt_clear(struct run_hooks_opt *o)
45 {
46         strvec_clear(&o->env);
47         strvec_clear(&o->args);
48 }
49
50 static int pick_next_hook(struct child_process *cp,
51                           struct strbuf *out,
52                           void *pp_cb,
53                           void **pp_task_cb)
54 {
55         struct hook_cb_data *hook_cb = pp_cb;
56         struct hook *run_me = hook_cb->run_me;
57
58         if (!run_me)
59                 BUG("did we not return 1 in notify_hook_finished?");
60
61         /* reopen the file for stdin; run_command closes it. */
62         if (hook_cb->options->path_to_stdin) {
63                 cp->no_stdin = 0;
64                 cp->in = xopen(hook_cb->options->path_to_stdin, O_RDONLY);
65         } else {
66                 cp->no_stdin = 1;
67         }
68         cp->env = hook_cb->options->env.v;
69         cp->stdout_to_stderr = 1;
70         cp->trace2_hook_name = hook_cb->hook_name;
71         cp->dir = hook_cb->options->dir;
72
73         /* add command */
74         strvec_push(&cp->args, run_me->hook_path);
75
76         /*
77          * add passed-in argv, without expanding - let the user get back
78          * exactly what they put in
79          */
80         strvec_pushv(&cp->args, hook_cb->options->args.v);
81
82         /* Provide context for errors if necessary */
83         *pp_task_cb = run_me;
84
85         return 1;
86 }
87
88 static int notify_start_failure(struct strbuf *out,
89                                 void *pp_cb,
90                                 void *pp_task_cp)
91 {
92         struct hook_cb_data *hook_cb = pp_cb;
93         struct hook *attempted = pp_task_cp;
94
95         /* |= rc in cb */
96         hook_cb->rc |= 1;
97
98         strbuf_addf(out, _("Couldn't start hook '%s'\n"),
99                     attempted->hook_path);
100
101         return 1;
102 }
103
104 static int notify_hook_finished(int result,
105                                 struct strbuf *out,
106                                 void *pp_cb,
107                                 void *pp_task_cb)
108 {
109         struct hook_cb_data *hook_cb = pp_cb;
110
111         /* |= rc in cb */
112         hook_cb->rc |= result;
113
114         return 1;
115 }
116
117
118 int run_found_hooks(const char *hook_name, const char *hook_path,
119                     struct run_hooks_opt *options)
120 {
121         struct strbuf abs_path = STRBUF_INIT;
122         struct hook my_hook = {
123                 .hook_path = hook_path,
124         };
125         struct hook_cb_data cb_data = {
126                 .rc = 0,
127                 .hook_name = hook_name,
128                 .options = options,
129         };
130         if (options->absolute_path) {
131                 strbuf_add_absolute_path(&abs_path, hook_path);
132                 my_hook.hook_path = abs_path.buf;
133         }
134         cb_data.run_me = &my_hook;
135
136         if (options->jobs != 1)
137                 BUG("we do not handle %d or any other != 1 job number yet", options->jobs);
138
139         run_processes_parallel_tr2(options->jobs,
140                                    pick_next_hook,
141                                    notify_start_failure,
142                                    NULL,
143                                    notify_hook_finished,
144                                    &cb_data,
145                                    "hook",
146                                    hook_name);
147         if (options->absolute_path)
148                 strbuf_release(&abs_path);
149
150         return cb_data.rc;
151 }
152
153 int run_hooks(const char *hook_name, struct run_hooks_opt *options)
154 {
155         const char *hook_path;
156         int ret;
157         if (!options)
158                 BUG("a struct run_hooks_opt must be provided to run_hooks");
159
160         hook_path = find_hook(hook_name);
161
162         /* Care about nonexistence? Use run_found_hooks() */
163         if (!hook_path)
164                 return 0;
165
166         ret = run_found_hooks(hook_name, hook_path, options);
167         return ret;
168 }