Add support for reading stderr
[TortoiseGit.git] / src / libgit2 / filter-filter.c
blob644569b9ca33558663418d50251f4c808cb26290
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2014 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 (GIT_ATTR_UNSPECIFIED(attr_values[0]))
51 return GIT_PASSTHROUGH;
53 if (GIT_ATTR_FALSE(attr_values[0]))
54 return GIT_PASSTHROUGH;
56 if (GIT_ATTR_TRUE(attr_values[0]))
57 return GIT_PASSTHROUGH;
59 *payload = git__strdup(attr_values[0]);
60 if (!*payload)
61 return 1;
63 return 0;
66 static int expandPerCentF(git_buf *buf, const char *replaceWith)
68 ssize_t foundPercentage = git_buf_find(buf, '%');
69 if (foundPercentage) {
70 git_buf expanded = GIT_BUF_INIT;
71 const char *end = buf->ptr + buf->size;
72 const char *lastPercentage = buf->ptr;
73 const char *idx = buf->ptr + foundPercentage;
74 while (idx < end) {
75 if (*idx == '%') {
76 if (idx + 1 == buf->ptr + buf->size || (idx + 1 < end && *(idx + 1) == '%')) { // one '%' is at the end of the string OR "%%" is in the string
77 git_buf_putc(&expanded, '%');
78 ++idx;
79 ++idx;
80 lastPercentage = idx;
81 continue;
83 // now we know, that we're not at the end of the string and that the next char is not '%'
84 git_buf_put(&expanded, lastPercentage, idx - lastPercentage);
85 ++idx;
86 if (*idx == 'f')
87 git_buf_printf(&expanded, "\"%s\"", replaceWith);
89 ++idx;
90 lastPercentage = idx;
91 continue;
93 ++idx;
95 if (lastPercentage)
96 git_buf_put(&expanded, lastPercentage, idx - lastPercentage);
97 if (git_buf_oom(&expanded))
98 return -1;
99 git_buf_swap(buf, &expanded);
100 git_buf_free(&expanded);
102 return 0;
105 static void setProcessError(DWORD exitCode, git_buf *errBuf)
107 if (!git_buf_oom(errBuf) && git_buf_len(errBuf))
108 giterr_set(GITERR_FILTER, "External filter application exited non-zero (%ld) and reported:\n%s", exitCode, errBuf->ptr);
109 else
110 giterr_set(GITERR_FILTER, "External filter application exited non-zero: %ld", exitCode);
113 static int filter_apply(
114 git_filter *self,
115 void **payload, /* may be read and/or set */
116 git_buf *to,
117 const git_buf *from,
118 const git_filter_source *src)
120 struct filter_filter *ffs = (struct filter_filter *)self;
122 if (!*payload)
123 return GIT_PASSTHROUGH;
125 git_config *config;
126 if (git_repository_config__weakptr(&config, git_filter_source_repo(src)))
127 return -1;
129 git_buf configKey = GIT_BUF_INIT;
130 git_buf_join3(&configKey, '.', "filter", *payload, "required");
131 if (git_buf_oom(&configKey))
132 return -1;
134 int isRequired = FALSE;
135 int error = git_config_get_bool(&isRequired, config, configKey.ptr);
136 git_buf_free(&configKey);
137 if (error && error != GIT_ENOTFOUND)
138 return -1;
140 git_buf_join(&configKey, '.', "filter", *payload);
141 if (git_filter_source_mode(src) == GIT_FILTER_SMUDGE) {
142 git_buf_puts(&configKey, ".smudge");
143 } else {
144 git_buf_puts(&configKey, ".clean");
146 if (git_buf_oom(&configKey))
147 return -1;
149 const char *cmd = NULL;
150 error = git_config_get_string(&cmd, config, configKey.ptr);
151 git_buf_free(&configKey);
152 if (error && error != GIT_ENOTFOUND)
153 return -1;
155 if (error == GIT_ENOTFOUND) {
156 if (isRequired)
157 return -1;
158 return GIT_PASSTHROUGH;
161 git_buf cmdBuf = GIT_BUF_INIT;
162 git_buf_puts(&cmdBuf, cmd);
163 if (git_buf_oom(&cmdBuf))
164 return -1;
166 if (expandPerCentF(&cmdBuf, git_filter_source_path(src)))
167 return 1;
169 if (ffs->shexepath) {
170 // build params for sh.exe
171 git_buf shParams = GIT_BUF_INIT;
172 git_buf_puts(&shParams, " -c \"");
173 git_buf_text_puts_escaped(&shParams, cmdBuf.ptr, "\"\\", "\\");
174 git_buf_puts(&shParams, "\"");
175 git_buf_swap(&shParams, &cmdBuf);
176 git_buf_free(&shParams);
179 wchar_t *wide_cmd;
180 if (git__utf8_to_16_alloc(&wide_cmd, cmdBuf.ptr) < 0)
182 git_buf_free(&cmdBuf);
183 return 1;
185 git_buf_free(&cmdBuf);
187 if (ffs->shexepath) {
188 // build cmd, i.e. shexepath + params
189 size_t len = wcslen(ffs->shexepath) + wcslen(wide_cmd) + 1;
190 wchar_t *tmp = git__calloc(len, sizeof(wchar_t));
191 if (!tmp) {
192 git__free(wide_cmd);
193 return -1;
195 wcscat_s(tmp, len, ffs->shexepath);
196 wcscat_s(tmp, len, wide_cmd);
197 git__free(wide_cmd);
198 wide_cmd = tmp;
201 COMMAND_HANDLE commandHandle = { 0 };
202 git_buf errBuf = GIT_BUF_INIT;
203 commandHandle.errBuf = &errBuf;
204 if (command_start(wide_cmd, &commandHandle, ffs->pEnv)) {
205 git__free(wide_cmd);
206 if (isRequired)
207 return -1;
208 return GIT_PASSTHROUGH;
210 git__free(wide_cmd);
212 HANDLE readingThread = commmand_start_stdout_reading_thread(&commandHandle, to);
213 if (!readingThread) {
214 command_close(&commandHandle);
215 return -1;
218 if (command_write_gitbuf(&commandHandle, from)) {
219 DWORD exitCode = command_close(&commandHandle);
220 if (exitCode)
221 setProcessError(exitCode, &errBuf);
222 CloseHandle(readingThread);
223 git_buf_free(&errBuf);
224 if (isRequired)
225 return -1;
226 return GIT_PASSTHROUGH;
228 command_close_stdin(&commandHandle);
230 DWORD exitCode = MAXDWORD;
231 WaitForSingleObject(readingThread, INFINITE);
232 if (!GetExitCodeThread(readingThread, &exitCode) || exitCode) {
233 exitCode = command_close(&commandHandle);
234 if (exitCode)
235 setProcessError(exitCode, &errBuf);
236 CloseHandle(readingThread);
237 git_buf_free(&errBuf);
238 if (isRequired)
239 return -1;
240 return GIT_PASSTHROUGH;
242 CloseHandle(readingThread);
244 exitCode = command_close(&commandHandle);
245 if (exitCode) {
246 if (isRequired) {
247 setProcessError(exitCode, &errBuf);
248 git_buf_free(&errBuf);
249 return -1;
251 git_buf_free(&errBuf);
252 return GIT_PASSTHROUGH;
255 git_buf_free(&errBuf);
257 return 0;
260 static void filter_cleanup(
261 git_filter *self,
262 void *payload)
264 GIT_UNUSED(self);
265 git__free(payload);
268 static void filter_free(git_filter *self)
270 struct filter_filter *ffs = (struct filter_filter *)self;
272 if (ffs->shexepath)
273 git__free(ffs->shexepath);
275 git__free(self);
278 git_filter *git_filter_filter_new(LPCWSTR shexepath, LPWSTR pEnv)
280 struct filter_filter *f = git__calloc(1, sizeof(struct filter_filter));
282 f->f.version = GIT_FILTER_VERSION;
283 f->f.attributes = "filter";
284 f->f.initialize = NULL;
285 f->f.shutdown = filter_free;
286 f->f.check = filter_check;
287 f->f.apply = filter_apply;
288 f->f.cleanup = filter_cleanup;
289 f->shexepath = wcsdup(shexepath);
290 f->pEnv = pEnv;
292 return (git_filter *)f;