Reset: Show progress and handled files when used with libgit2
[TortoiseGit.git] / src / libgit2 / filter-filter.c
blobe2bf927d8ac389d5403feb7b939987c963afc9e2
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)
62 giterr_set_oom();
63 return -1;
66 return 0;
69 static int expandPerCentF(git_buf *buf, const char *replaceWith)
71 ssize_t foundPercentage = git_buf_find(buf, '%');
72 if (foundPercentage) {
73 git_buf expanded = GIT_BUF_INIT;
74 const char *end = buf->ptr + buf->size;
75 const char *lastPercentage = buf->ptr;
76 const char *idx = buf->ptr + foundPercentage;
77 while (idx < end) {
78 if (*idx == '%') {
79 if (idx + 1 == end || (idx + 1 < end && *(idx + 1) == '%')) { // one '%' is at the end of the string OR "%%" is in the string
80 git_buf_putc(&expanded, '%');
81 ++idx;
82 ++idx;
83 lastPercentage = idx;
84 continue;
86 // now we know, that we're not at the end of the string and that the next char is not '%'
87 git_buf_put(&expanded, lastPercentage, idx - lastPercentage);
88 ++idx;
89 if (*idx == 'f')
90 git_buf_printf(&expanded, "\"%s\"", replaceWith);
92 ++idx;
93 lastPercentage = idx;
94 continue;
96 ++idx;
98 if (lastPercentage)
99 git_buf_put(&expanded, lastPercentage, idx - lastPercentage);
100 if (git_buf_oom(&expanded))
102 giterr_set_oom();
103 return -1;
105 git_buf_swap(buf, &expanded);
106 git_buf_free(&expanded);
108 return 0;
111 static void setProcessError(DWORD exitCode, git_buf *errBuf)
113 if (!git_buf_oom(errBuf) && git_buf_len(errBuf))
114 giterr_set(GITERR_FILTER, "External filter application exited non-zero (%ld) and reported:\n%s", exitCode, errBuf->ptr);
115 else
116 giterr_set(GITERR_FILTER, "External filter application exited non-zero: %ld", exitCode);
119 static int filter_apply(
120 git_filter *self,
121 void **payload, /* may be read and/or set */
122 git_buf *to,
123 const git_buf *from,
124 const git_filter_source *src)
126 struct filter_filter *ffs = (struct filter_filter *)self;
127 git_config *config;
128 git_buf configKey = GIT_BUF_INIT;
129 int isRequired = FALSE;
130 int error;
131 const char *cmd = NULL;
132 git_buf cmdBuf = GIT_BUF_INIT;
133 wchar_t *wide_cmd;
134 COMMAND_HANDLE commandHandle;
135 git_buf errBuf = GIT_BUF_INIT;
136 DWORD exitCode;
138 if (!*payload)
139 return GIT_PASSTHROUGH;
141 if (git_repository_config__weakptr(&config, git_filter_source_repo(src)))
142 return -1;
144 git_buf_join3(&configKey, '.', "filter", *payload, "required");
145 if (git_buf_oom(&configKey)) {
146 giterr_set_oom();
147 return -1;
150 error = git_config_get_bool(&isRequired, config, configKey.ptr);
151 git_buf_free(&configKey);
152 if (error && error != GIT_ENOTFOUND)
153 return -1;
155 git_buf_join(&configKey, '.', "filter", *payload);
156 if (git_filter_source_mode(src) == GIT_FILTER_SMUDGE) {
157 git_buf_puts(&configKey, ".smudge");
158 } else {
159 git_buf_puts(&configKey, ".clean");
161 if (git_buf_oom(&configKey)) {
162 giterr_set_oom();
163 return -1;
166 error = git_config_get_string(&cmd, config, configKey.ptr);
167 git_buf_free(&configKey);
168 if (error && error != GIT_ENOTFOUND)
169 return -1;
171 if (error == GIT_ENOTFOUND) {
172 if (isRequired)
173 return -1;
174 return GIT_PASSTHROUGH;
177 git_buf_puts(&cmdBuf, cmd);
178 if (git_buf_oom(&cmdBuf)) {
179 giterr_set_oom();
180 return -1;
183 if (expandPerCentF(&cmdBuf, git_filter_source_path(src)))
184 return -1;
186 if (ffs->shexepath) {
187 // build params for sh.exe
188 git_buf shParams = GIT_BUF_INIT;
189 git_buf_puts(&shParams, " -c \"");
190 git_buf_text_puts_escaped(&shParams, cmdBuf.ptr, "\"\\", "\\");
191 git_buf_puts(&shParams, "\"");
192 if (git_buf_oom(&shParams)) {
193 git_buf_free(&cmdBuf);
194 giterr_set_oom();
195 return -1;
197 git_buf_swap(&shParams, &cmdBuf);
198 git_buf_free(&shParams);
201 if (git__utf8_to_16_alloc(&wide_cmd, cmdBuf.ptr) < 0)
203 git_buf_free(&cmdBuf);
204 giterr_set_oom();
205 return -1;
207 git_buf_free(&cmdBuf);
209 if (ffs->shexepath) {
210 // build cmd, i.e. shexepath + params
211 size_t len = wcslen(ffs->shexepath) + wcslen(wide_cmd) + 1;
212 wchar_t *tmp = git__calloc(len, sizeof(wchar_t));
213 if (!tmp) {
214 git__free(wide_cmd);
215 giterr_set_oom();
216 return -1;
218 wcscat_s(tmp, len, ffs->shexepath);
219 wcscat_s(tmp, len, wide_cmd);
220 git__free(wide_cmd);
221 wide_cmd = tmp;
224 command_init(&commandHandle);
225 commandHandle.errBuf = &errBuf;
226 if (command_start(wide_cmd, &commandHandle, ffs->pEnv, 0)) {
227 git__free(wide_cmd);
228 if (isRequired)
229 return -1;
230 return GIT_PASSTHROUGH;
232 git__free(wide_cmd);
234 if (commmand_start_stdout_reading_thread(&commandHandle, to)) {
235 command_close(&commandHandle);
236 return -1;
239 if (command_write_gitbuf(&commandHandle, from)) {
240 DWORD exitCode = command_close(&commandHandle);
241 if (exitCode)
242 setProcessError(exitCode, &errBuf);
243 git_buf_free(&errBuf);
244 if (isRequired)
245 return -1;
246 return GIT_PASSTHROUGH;
248 command_close_stdin(&commandHandle);
250 if (command_wait_stdout_reading_thread(&commandHandle)) {
251 DWORD exitCode = command_close(&commandHandle);
252 if (exitCode)
253 setProcessError(exitCode, &errBuf);
254 git_buf_free(&errBuf);
255 if (isRequired)
256 return -1;
257 return GIT_PASSTHROUGH;
260 exitCode = command_close(&commandHandle);
261 if (exitCode) {
262 if (isRequired) {
263 setProcessError(exitCode, &errBuf);
264 git_buf_free(&errBuf);
265 return -1;
267 git_buf_free(&errBuf);
268 return GIT_PASSTHROUGH;
271 git_buf_free(&errBuf);
273 return 0;
276 static void filter_cleanup(
277 git_filter *self,
278 void *payload)
280 GIT_UNUSED(self);
281 git__free(payload);
284 static void filter_free(git_filter *self)
286 struct filter_filter *ffs = (struct filter_filter *)self;
288 if (ffs->shexepath)
289 git__free(ffs->shexepath);
291 git__free(self);
294 git_filter *git_filter_filter_new(LPCWSTR shexepath, LPWSTR pEnv)
296 struct filter_filter *f = git__calloc(1, sizeof(struct filter_filter));
298 f->f.version = GIT_FILTER_VERSION;
299 f->f.attributes = "filter";
300 f->f.initialize = NULL;
301 f->f.shutdown = filter_free;
302 f->f.check = filter_check;
303 f->f.apply = filter_apply;
304 f->f.cleanup = filter_cleanup;
305 f->shexepath = wcsdup(shexepath);
306 f->pEnv = pEnv;
308 return (git_filter *)f;