Get rid of magic numbers
[TortoiseGit.git] / src / libgit2 / filter-filter.c
blob31b23af3f59729100647a63112554ccd30d295ad
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2014, 2016-2018 TortoiseGit
5 // This program is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU General Public License
7 // as published by the Free Software Foundation; either version 2
8 // of the License, or (at your option) any later version.
10 // This program is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 // GNU General Public License for more details.
15 // You should have received a copy of the GNU General Public License
16 // along with this program; if not, write to the Free Software Foundation,
17 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20 #include "git2/attr.h"
21 #include "git2/blob.h"
22 #include "git2/index.h"
23 #include "git2/sys/filter.h"
25 #include "common.h"
26 #include "fileops.h"
27 #include "hash.h"
28 #include "filter.h"
29 #include "buf_text.h"
30 #include "repository.h"
32 #include "system-call.h"
33 #include "filter-filter.h"
35 struct filter_filter {
36 git_filter f;
37 LPWSTR* pEnv;
38 LPWSTR shexepath;
41 static int filter_check(
42 git_filter *self,
43 void **payload, /* points to NULL ptr on entry, may be set */
44 const git_filter_source *src,
45 const char **attr_values)
47 GIT_UNUSED(self);
48 GIT_UNUSED(src);
50 if (!attr_values)
51 return GIT_PASSTHROUGH;
53 if (GIT_ATTR_UNSPECIFIED(attr_values[0]))
54 return GIT_PASSTHROUGH;
56 if (GIT_ATTR_FALSE(attr_values[0]))
57 return GIT_PASSTHROUGH;
59 if (GIT_ATTR_TRUE(attr_values[0]))
60 return GIT_PASSTHROUGH;
62 *payload = git__strdup(attr_values[0]);
63 if (!*payload)
65 giterr_set_oom();
66 return -1;
69 return 0;
72 static int expandPerCentF(git_buf *buf, const char *replaceWith)
74 ssize_t foundPercentage = git_buf_find(buf, '%');
75 if (foundPercentage) {
76 git_buf expanded = GIT_BUF_INIT;
77 const char *end = buf->ptr + buf->size;
78 const char *lastPercentage = buf->ptr;
79 const char *idx = buf->ptr + foundPercentage;
80 while (idx < end) {
81 if (*idx == '%') {
82 if (idx + 1 == end || (idx + 1 < end && *(idx + 1) == '%')) { // one '%' is at the end of the string OR "%%" is in the string
83 git_buf_putc(&expanded, '%');
84 ++idx;
85 ++idx;
86 lastPercentage = idx;
87 continue;
89 // now we know, that we're not at the end of the string and that the next char is not '%'
90 git_buf_put(&expanded, lastPercentage, idx - lastPercentage);
91 ++idx;
92 if (*idx == 'f')
93 git_buf_printf(&expanded, "\"%s\"", replaceWith);
95 ++idx;
96 lastPercentage = idx;
97 continue;
99 ++idx;
101 if (lastPercentage)
102 git_buf_put(&expanded, lastPercentage, idx - lastPercentage);
103 if (git_buf_oom(&expanded))
105 giterr_set_oom();
106 return -1;
108 git_buf_swap(buf, &expanded);
109 git_buf_dispose(&expanded);
111 return 0;
114 static void setProcessError(DWORD exitCode, git_buf *errBuf)
116 if (!git_buf_oom(errBuf) && git_buf_len(errBuf))
117 giterr_set(GITERR_FILTER, "External filter application exited non-zero (%ld) and reported:\n%s", exitCode, errBuf->ptr);
118 else
119 giterr_set(GITERR_FILTER, "External filter application exited non-zero: %ld", exitCode);
122 static int filter_apply(
123 git_filter *self,
124 void **payload, /* may be read and/or set */
125 git_buf *to,
126 const git_buf *from,
127 const git_filter_source *src)
129 struct filter_filter *ffs = (struct filter_filter *)self;
130 git_config *config;
131 git_buf configKey = GIT_BUF_INIT;
132 int isRequired = FALSE;
133 int error;
134 git_buf cmd = GIT_BUF_INIT;
135 wchar_t *wide_cmd;
136 COMMAND_HANDLE commandHandle;
137 git_buf errBuf = GIT_BUF_INIT;
138 DWORD exitCode;
140 if (!*payload)
141 return GIT_PASSTHROUGH;
143 if (git_repository_config__weakptr(&config, git_filter_source_repo(src)))
144 return -1;
146 git_buf_join3(&configKey, '.', "filter", *payload, "required");
147 if (git_buf_oom(&configKey)) {
148 giterr_set_oom();
149 return -1;
152 error = git_config_get_bool(&isRequired, config, configKey.ptr);
153 git_buf_dispose(&configKey);
154 if (error && error != GIT_ENOTFOUND)
155 return -1;
157 git_buf_join(&configKey, '.', "filter", *payload);
158 if (git_filter_source_mode(src) == GIT_FILTER_SMUDGE) {
159 git_buf_puts(&configKey, ".smudge");
160 } else {
161 git_buf_puts(&configKey, ".clean");
163 if (git_buf_oom(&configKey)) {
164 giterr_set_oom();
165 return -1;
168 error = git_config_get_string_buf(&cmd, config, configKey.ptr);
169 git_buf_dispose(&configKey);
170 if (error && error != GIT_ENOTFOUND)
171 return -1;
173 if (error == GIT_ENOTFOUND) {
174 if (isRequired)
175 return -1;
176 return GIT_PASSTHROUGH;
179 if (expandPerCentF(&cmd, git_filter_source_path(src)))
180 return -1;
182 if (ffs->shexepath) {
183 // build params for sh.exe
184 git_buf shParams = GIT_BUF_INIT;
185 git_buf_puts(&shParams, " -c \"");
186 git_buf_text_puts_escaped(&shParams, cmd.ptr, "\"\\", "\\");
187 git_buf_puts(&shParams, "\"");
188 if (git_buf_oom(&shParams)) {
189 git_buf_dispose(&cmd);
190 giterr_set_oom();
191 return -1;
193 git_buf_swap(&shParams, &cmd);
194 git_buf_dispose(&shParams);
197 if (git__utf8_to_16_alloc(&wide_cmd, cmd.ptr) < 0)
199 git_buf_dispose(&cmd);
200 giterr_set_oom();
201 return -1;
203 git_buf_dispose(&cmd);
205 if (ffs->shexepath) {
206 // build cmd, i.e. shexepath + params
207 size_t len = wcslen(ffs->shexepath) + wcslen(wide_cmd) + 1;
208 wchar_t *tmp = git__calloc(len, sizeof(wchar_t));
209 if (!tmp) {
210 git__free(wide_cmd);
211 giterr_set_oom();
212 return -1;
214 wcscat_s(tmp, len, ffs->shexepath);
215 wcscat_s(tmp, len, wide_cmd);
216 git__free(wide_cmd);
217 wide_cmd = tmp;
220 command_init(&commandHandle);
221 commandHandle.errBuf = &errBuf;
222 if (command_start(wide_cmd, &commandHandle, ffs->pEnv, 0)) {
223 git__free(wide_cmd);
224 if (isRequired)
225 return -1;
226 return GIT_PASSTHROUGH;
228 git__free(wide_cmd);
230 if (commmand_start_stdout_reading_thread(&commandHandle, to)) {
231 command_close(&commandHandle);
232 return -1;
235 if (command_write_gitbuf(&commandHandle, from)) {
236 exitCode = command_close(&commandHandle);
237 if (exitCode)
238 setProcessError(exitCode, &errBuf);
239 git_buf_dispose(&errBuf);
240 if (isRequired)
241 return -1;
242 return GIT_PASSTHROUGH;
244 command_close_stdin(&commandHandle);
246 if (command_wait_stdout_reading_thread(&commandHandle)) {
247 exitCode = command_close(&commandHandle);
248 if (exitCode)
249 setProcessError(exitCode, &errBuf);
250 git_buf_dispose(&errBuf);
251 if (isRequired)
252 return -1;
253 return GIT_PASSTHROUGH;
256 exitCode = command_close(&commandHandle);
257 if (exitCode) {
258 if (isRequired) {
259 setProcessError(exitCode, &errBuf);
260 git_buf_dispose(&errBuf);
261 return -1;
263 git_buf_dispose(&errBuf);
264 return GIT_PASSTHROUGH;
267 git_buf_dispose(&errBuf);
269 return 0;
272 static void filter_cleanup(
273 git_filter *self,
274 void *payload)
276 GIT_UNUSED(self);
277 git__free(payload);
280 static void filter_free(git_filter *self)
282 struct filter_filter *ffs = (struct filter_filter *)self;
284 git__free(ffs->shexepath);
286 git__free(self);
289 git_filter *git_filter_filter_new(LPCWSTR shexepath, LPWSTR* pEnv)
291 struct filter_filter *f = git__calloc(1, sizeof(struct filter_filter));
292 if (!f)
293 return NULL;
295 f->f.version = GIT_FILTER_VERSION;
296 f->f.attributes = "filter";
297 f->f.initialize = NULL;
298 f->f.shutdown = filter_free;
299 f->f.check = filter_check;
300 f->f.apply = filter_apply;
301 f->f.cleanup = filter_cleanup;
302 f->shexepath = NULL;
303 if (shexepath && shexepath[0])
304 f->shexepath = wcsdup(shexepath);
305 f->pEnv = pEnv;
307 return (git_filter *)f;