Add a setting to require a filter to be successful

By default, a missing filter driver or a failure from the filter driver is not an error, but merely makes the filter operation a no-op pass through. This is useful to massage the content into a shape that is more convenient for the platform, filesystem, and the user to use, and the content filter mechanism is not used to turn something unusable into usable. However, we could also use of the content filtering mechanism and store the content that cannot be directly used in the repository (e.g. a UUID that refers to the true content stored outside git, or an encrypted content) and turn it into a usable form upon checkout (e.g. download the external content, or decrypt the encrypted content). For such a use case, the content cannot be used when filter driver fails, and we need a way to tell Git to abort the whole operation for such a failing or missing filter driver. Add a new "filter.<driver>.required" configuration variable to mark the second use case. When it is set, git will abort the operation when the filter driver does not exist or exits with a non-zero status code. Signed-off-by: Jehan Bing <jehan@orb.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Jehan Bing committed Feb 16, 2012 at 17:19 UTC 36daaaca0046542f4d5576ad5766ac55f43e3fc3
3 files changed +92 -14
Documentation/gitattributes.txt
+31 -10
@@ -294,16 +294,27 @@ output is used to update the worktree file. Similarly, the
294 `clean` command is used to convert the contents of worktree file
295 upon checkin.
296
297 -A missing filter driver definition in the config is not an error
298 -but makes the filter a no-op passthru.
299 -
300 -The content filtering is done to massage the content into a
301 -shape that is more convenient for the platform, filesystem, and
302 -the user to use. The key phrase here is "more convenient" and not
303 -"turning something unusable into usable". In other words, the
304 -intent is that if someone unsets the filter driver definition,
305 -or does not have the appropriate filter program, the project
306 -should still be usable.
297 +One use of the content filtering is to massage the content into a shape
298 +that is more convenient for the platform, filesystem, and the user to use.
299 +For this mode of operation, the key phrase here is "more convenient" and
300 +not "turning something unusable into usable". In other words, the intent
301 +is that if someone unsets the filter driver definition, or does not have
302 +the appropriate filter program, the project should still be usable.
303 +
304 +Another use of the content filtering is to store the content that cannot
305 +be directly used in the repository (e.g. a UUID that refers to the true
306 +content stored outside git, or an encrypted content) and turn it into a
307 +usable form upon checkout (e.g. download the external content, or decrypt
308 +the encrypted content).
309 +
310 +These two filters behave differently, and by default, a filter is taken as
311 +the former, massaging the contents into more convenient shape. A missing
312 +filter driver definition in the config, or a filter driver that exits with
313 +a non-zero status, is not an error but makes the filter a no-op passthru.
314 +
315 +You can declare that a filter turns a content that by itself is unusable
316 +into a usable content by setting the filter.<driver>.required configuration
317 +variable to `true`.
318
319 For example, in .gitattributes, you would assign the `filter`
320 attribute for paths.
@@ -335,6 +346,16 @@ input that is already correctly indented. In this case, the lack of a
346 smudge filter means that the clean filter _must_ accept its own output
347 without modifying it.
348
349 +If a filter _must_ succeed in order to make the stored contents usable,
350 +you can declare that the filter is `required`, in the configuration:
351 +
352 +------------------------
353 +[filter "crypt"]
354 + clean = openssl enc ...
355 + smudge = openssl enc -d ...
356 + required
357 +------------------------
358 +
359 Sequence "%f" on the filter command line is replaced with the name of
360 the file the filter is working on. A filter might use this in keyword
361 substitution. For example:
convert.c
+24 -4
@@ -429,6 +429,7 @@ static struct convert_driver {
429 struct convert_driver *next;
430 const char *smudge;
431 const char *clean;
432 + int required;
433 } *user_convert, **user_convert_tail;
434
435 static int read_convert_config(const char *var, const char *value, void *cb)
@@ -472,6 +473,11 @@ static int read_convert_config(const char *var, const char *value, void *cb)
473 if (!strcmp("clean", ep))
474 return git_config_string(&drv->clean, var, value);
475
476 + if (!strcmp("required", ep)) {
477 + drv->required = git_config_bool(var, value);
478 + return 0;
479 + }
480 +
481 return 0;
482 }
483
@@ -747,13 +753,19 @@ int convert_to_git(const char *path, const char *src, size_t len,
753 {
754 int ret = 0;
755 const char *filter = NULL;
756 + int required = 0;
757 struct conv_attrs ca;
758
759 convert_attrs(&ca, path);
753 - if (ca.drv)
760 + if (ca.drv) {
761 filter = ca.drv->clean;
762 + required = ca.drv->required;
763 + }
764
765 ret |= apply_filter(path, src, len, dst, filter);
766 + if (!ret && required)
767 + die("%s: clean filter '%s' failed", path, ca.drv->name);
768 +
769 if (ret) {
770 src = dst->buf;
771 len = dst->len;
@@ -771,13 +783,16 @@ static int convert_to_working_tree_internal(const char *path, const char *src,
783 size_t len, struct strbuf *dst,
784 int normalizing)
785 {
774 - int ret = 0;
786 + int ret = 0, ret_filter = 0;
787 const char *filter = NULL;
788 + int required = 0;
789 struct conv_attrs ca;
790
791 convert_attrs(&ca, path);
779 - if (ca.drv)
792 + if (ca.drv) {
793 filter = ca.drv->smudge;
794 + required = ca.drv->required;
795 + }
796
797 ret |= ident_to_worktree(path, src, len, dst, ca.ident);
798 if (ret) {
@@ -796,7 +811,12 @@ static int convert_to_working_tree_internal(const char *path, const char *src,
811 len = dst->len;
812 }
813 }
799 - return ret | apply_filter(path, src, len, dst, filter);
814 +
815 + ret_filter = apply_filter(path, src, len, dst, filter);
816 + if (!ret_filter && required)
817 + die("%s: smudge filter %s failed", path, ca.drv->name);
818 +
819 + return ret | ret_filter;
820 }
821
822 int convert_to_working_tree(const char *path, const char *src, size_t len, struct strbuf *dst)
t/t0021-conversion.sh
+37
@@ -153,4 +153,41 @@ test_expect_success 'filter shell-escaped filenames' '
153 :
154 '
155
156 +test_expect_success 'required filter success' '
157 + git config filter.required.smudge cat &&
158 + git config filter.required.clean cat &&
159 + git config filter.required.required true &&
160 +
161 + echo "*.r filter=required" >.gitattributes &&
162 +
163 + echo test >test.r &&
164 + git add test.r &&
165 + rm -f test.r &&
166 + git checkout -- test.r
167 +'
168 +
169 +test_expect_success 'required filter smudge failure' '
170 + git config filter.failsmudge.smudge false &&
171 + git config filter.failsmudge.clean cat &&
172 + git config filter.failsmudge.required true &&
173 +
174 + echo "*.fs filter=failsmudge" >.gitattributes &&
175 +
176 + echo test >test.fs &&
177 + git add test.fs &&
178 + rm -f test.fs &&
179 + test_must_fail git checkout -- test.fs
180 +'
181 +
182 +test_expect_success 'required filter clean failure' '
183 + git config filter.failclean.smudge cat &&
184 + git config filter.failclean.clean false &&
185 + git config filter.failclean.required true &&
186 +
187 + echo "*.fc filter=failclean" >.gitattributes &&
188 +
189 + echo test >test.fc &&
190 + test_must_fail git add test.fc
191 +'
192 +
193 test_done