/src/git/builtin/interpret-trailers.c
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | * Builtin "git interpret-trailers" |
3 | | * |
4 | | * Copyright (c) 2013, 2014 Christian Couder <chriscool@tuxfamily.org> |
5 | | * |
6 | | */ |
7 | | |
8 | | #include "builtin.h" |
9 | | #include "gettext.h" |
10 | | #include "parse-options.h" |
11 | | #include "string-list.h" |
12 | | #include "tempfile.h" |
13 | | #include "trailer.h" |
14 | | #include "config.h" |
15 | | |
16 | | static const char * const git_interpret_trailers_usage[] = { |
17 | | N_("git interpret-trailers [--in-place] [--trim-empty]\n" |
18 | | " [(--trailer (<key>|<key-alias>)[(=|:)<value>])...]\n" |
19 | | " [--parse] [<file>...]"), |
20 | | NULL |
21 | | }; |
22 | | |
23 | | static enum trailer_where where; |
24 | | static enum trailer_if_exists if_exists; |
25 | | static enum trailer_if_missing if_missing; |
26 | | |
27 | | static int option_parse_where(const struct option *opt, |
28 | | const char *arg, int unset UNUSED) |
29 | 0 | { |
30 | | /* unset implies NULL arg, which is handled in our helper */ |
31 | 0 | return trailer_set_where(opt->value, arg); |
32 | 0 | } |
33 | | |
34 | | static int option_parse_if_exists(const struct option *opt, |
35 | | const char *arg, int unset UNUSED) |
36 | 0 | { |
37 | | /* unset implies NULL arg, which is handled in our helper */ |
38 | 0 | return trailer_set_if_exists(opt->value, arg); |
39 | 0 | } |
40 | | |
41 | | static int option_parse_if_missing(const struct option *opt, |
42 | | const char *arg, int unset UNUSED) |
43 | 0 | { |
44 | | /* unset implies NULL arg, which is handled in our helper */ |
45 | 0 | return trailer_set_if_missing(opt->value, arg); |
46 | 0 | } |
47 | | |
48 | | static void new_trailers_clear(struct list_head *trailers) |
49 | 0 | { |
50 | 0 | struct list_head *pos, *tmp; |
51 | 0 | struct new_trailer_item *item; |
52 | |
|
53 | 0 | list_for_each_safe(pos, tmp, trailers) { |
54 | 0 | item = list_entry(pos, struct new_trailer_item, list); |
55 | 0 | list_del(pos); |
56 | 0 | free(item); |
57 | 0 | } |
58 | 0 | } |
59 | | |
60 | | static int option_parse_trailer(const struct option *opt, |
61 | | const char *arg, int unset) |
62 | 0 | { |
63 | 0 | struct list_head *trailers = opt->value; |
64 | 0 | struct new_trailer_item *item; |
65 | |
|
66 | 0 | if (unset) { |
67 | 0 | new_trailers_clear(trailers); |
68 | 0 | return 0; |
69 | 0 | } |
70 | | |
71 | 0 | if (!arg) |
72 | 0 | return -1; |
73 | | |
74 | 0 | item = xmalloc(sizeof(*item)); |
75 | 0 | item->text = arg; |
76 | 0 | item->where = where; |
77 | 0 | item->if_exists = if_exists; |
78 | 0 | item->if_missing = if_missing; |
79 | 0 | list_add_tail(&item->list, trailers); |
80 | 0 | return 0; |
81 | 0 | } |
82 | | |
83 | | static int parse_opt_parse(const struct option *opt, const char *arg, |
84 | | int unset) |
85 | 0 | { |
86 | 0 | struct process_trailer_options *v = opt->value; |
87 | 0 | v->only_trailers = 1; |
88 | 0 | v->only_input = 1; |
89 | 0 | v->unfold = 1; |
90 | 0 | BUG_ON_OPT_NEG(unset); |
91 | 0 | BUG_ON_OPT_ARG(arg); |
92 | 0 | return 0; |
93 | 0 | } |
94 | | |
95 | | static struct tempfile *trailers_tempfile; |
96 | | |
97 | | static FILE *create_in_place_tempfile(const char *file) |
98 | 0 | { |
99 | 0 | struct stat st; |
100 | 0 | struct strbuf filename_template = STRBUF_INIT; |
101 | 0 | const char *tail; |
102 | 0 | FILE *outfile; |
103 | |
|
104 | 0 | if (stat(file, &st)) |
105 | 0 | die_errno(_("could not stat %s"), file); |
106 | 0 | if (!S_ISREG(st.st_mode)) |
107 | 0 | die(_("file %s is not a regular file"), file); |
108 | 0 | if (!(st.st_mode & S_IWUSR)) |
109 | 0 | die(_("file %s is not writable by user"), file); |
110 | | |
111 | | /* Create temporary file in the same directory as the original */ |
112 | 0 | tail = strrchr(file, '/'); |
113 | 0 | if (tail) |
114 | 0 | strbuf_add(&filename_template, file, tail - file + 1); |
115 | 0 | strbuf_addstr(&filename_template, "git-interpret-trailers-XXXXXX"); |
116 | |
|
117 | 0 | trailers_tempfile = xmks_tempfile_m(filename_template.buf, st.st_mode); |
118 | 0 | strbuf_release(&filename_template); |
119 | 0 | outfile = fdopen_tempfile(trailers_tempfile, "w"); |
120 | 0 | if (!outfile) |
121 | 0 | die_errno(_("could not open temporary file")); |
122 | | |
123 | 0 | return outfile; |
124 | 0 | } |
125 | | |
126 | | static void read_input_file(struct strbuf *sb, const char *file) |
127 | 0 | { |
128 | 0 | if (file) { |
129 | 0 | if (strbuf_read_file(sb, file, 0) < 0) |
130 | 0 | die_errno(_("could not read input file '%s'"), file); |
131 | 0 | } else { |
132 | 0 | if (strbuf_read(sb, fileno(stdin), 0) < 0) |
133 | 0 | die_errno(_("could not read from stdin")); |
134 | 0 | } |
135 | 0 | strbuf_complete_line(sb); |
136 | 0 | } |
137 | | |
138 | | static void interpret_trailers(const struct process_trailer_options *opts, |
139 | | struct list_head *new_trailer_head, |
140 | | const char *file) |
141 | 0 | { |
142 | 0 | LIST_HEAD(head); |
143 | 0 | struct strbuf sb = STRBUF_INIT; |
144 | 0 | struct strbuf trailer_block = STRBUF_INIT; |
145 | 0 | struct trailer_info *info; |
146 | 0 | FILE *outfile = stdout; |
147 | |
|
148 | 0 | trailer_config_init(); |
149 | |
|
150 | 0 | read_input_file(&sb, file); |
151 | |
|
152 | 0 | if (opts->in_place) |
153 | 0 | outfile = create_in_place_tempfile(file); |
154 | |
|
155 | 0 | info = parse_trailers(opts, sb.buf, &head); |
156 | | |
157 | | /* Print the lines before the trailers */ |
158 | 0 | if (!opts->only_trailers) |
159 | 0 | fwrite(sb.buf, 1, trailer_block_start(info), outfile); |
160 | |
|
161 | 0 | if (!opts->only_trailers && !blank_line_before_trailer_block(info)) |
162 | 0 | fprintf(outfile, "\n"); |
163 | | |
164 | |
|
165 | 0 | if (!opts->only_input) { |
166 | 0 | LIST_HEAD(config_head); |
167 | 0 | LIST_HEAD(arg_head); |
168 | 0 | parse_trailers_from_config(&config_head); |
169 | 0 | parse_trailers_from_command_line_args(&arg_head, new_trailer_head); |
170 | 0 | list_splice(&config_head, &arg_head); |
171 | 0 | process_trailers_lists(&head, &arg_head); |
172 | 0 | } |
173 | | |
174 | | /* Print trailer block. */ |
175 | 0 | format_trailers(opts, &head, &trailer_block); |
176 | 0 | free_trailers(&head); |
177 | 0 | fwrite(trailer_block.buf, 1, trailer_block.len, outfile); |
178 | 0 | strbuf_release(&trailer_block); |
179 | | |
180 | | /* Print the lines after the trailers as is */ |
181 | 0 | if (!opts->only_trailers) |
182 | 0 | fwrite(sb.buf + trailer_block_end(info), 1, sb.len - trailer_block_end(info), outfile); |
183 | 0 | trailer_info_release(info); |
184 | |
|
185 | 0 | if (opts->in_place) |
186 | 0 | if (rename_tempfile(&trailers_tempfile, file)) |
187 | 0 | die_errno(_("could not rename temporary file to %s"), file); |
188 | | |
189 | 0 | strbuf_release(&sb); |
190 | 0 | } |
191 | | |
192 | | int cmd_interpret_trailers(int argc, const char **argv, const char *prefix) |
193 | 0 | { |
194 | 0 | struct process_trailer_options opts = PROCESS_TRAILER_OPTIONS_INIT; |
195 | 0 | LIST_HEAD(trailers); |
196 | |
|
197 | 0 | struct option options[] = { |
198 | 0 | OPT_BOOL(0, "in-place", &opts.in_place, N_("edit files in place")), |
199 | 0 | OPT_BOOL(0, "trim-empty", &opts.trim_empty, N_("trim empty trailers")), |
200 | |
|
201 | 0 | OPT_CALLBACK(0, "where", &where, N_("placement"), |
202 | 0 | N_("where to place the new trailer"), option_parse_where), |
203 | 0 | OPT_CALLBACK(0, "if-exists", &if_exists, N_("action"), |
204 | 0 | N_("action if trailer already exists"), option_parse_if_exists), |
205 | 0 | OPT_CALLBACK(0, "if-missing", &if_missing, N_("action"), |
206 | 0 | N_("action if trailer is missing"), option_parse_if_missing), |
207 | |
|
208 | 0 | OPT_BOOL(0, "only-trailers", &opts.only_trailers, N_("output only the trailers")), |
209 | 0 | OPT_BOOL(0, "only-input", &opts.only_input, N_("do not apply trailer.* configuration variables")), |
210 | 0 | OPT_BOOL(0, "unfold", &opts.unfold, N_("reformat multiline trailer values as single-line values")), |
211 | 0 | OPT_CALLBACK_F(0, "parse", &opts, NULL, N_("alias for --only-trailers --only-input --unfold"), |
212 | 0 | PARSE_OPT_NOARG | PARSE_OPT_NONEG, parse_opt_parse), |
213 | 0 | OPT_BOOL(0, "no-divider", &opts.no_divider, N_("do not treat \"---\" as the end of input")), |
214 | 0 | OPT_CALLBACK(0, "trailer", &trailers, N_("trailer"), |
215 | 0 | N_("trailer(s) to add"), option_parse_trailer), |
216 | 0 | OPT_END() |
217 | 0 | }; |
218 | |
|
219 | 0 | git_config(git_default_config, NULL); |
220 | |
|
221 | 0 | argc = parse_options(argc, argv, prefix, options, |
222 | 0 | git_interpret_trailers_usage, 0); |
223 | |
|
224 | 0 | if (opts.only_input && !list_empty(&trailers)) |
225 | 0 | usage_msg_opt( |
226 | 0 | _("--trailer with --only-input does not make sense"), |
227 | 0 | git_interpret_trailers_usage, |
228 | 0 | options); |
229 | | |
230 | 0 | if (argc) { |
231 | 0 | int i; |
232 | 0 | for (i = 0; i < argc; i++) |
233 | 0 | interpret_trailers(&opts, &trailers, argv[i]); |
234 | 0 | } else { |
235 | 0 | if (opts.in_place) |
236 | 0 | die(_("no input file given for in-place editing")); |
237 | 0 | interpret_trailers(&opts, &trailers, NULL); |
238 | 0 | } |
239 | | |
240 | 0 | new_trailers_clear(&trailers); |
241 | |
|
242 | 0 | return 0; |
243 | 0 | } |