Search code examples
gitgitattributes

Why is this clean filter being ignored the first time it runs?


In an attempt to automatically make .sh files executable on commit, I made a clean filter and was confused by its results.

First, I ran git config filter.executable.clean 'chmod +x %f'. This adds the following stanza to my test repository's .git/config file:

[filter "executable"]
        clean = chmod +x %f

Next I created a .gitattributes file:

*.sh    filter=executable

Let's see how this behaves. Of course, a newly-created .sh file isn't executable:

$ touch foo.sh
$ ls -l foo.sh
-rw-r--r-- foo.sh

If we add it our clean filter should run. But this results in an executable file in our working directory, a non-executable file in our staging area, and a dirty working copy!

$ git add foo.sh
$ ls -l foo.sh  # As expected
-rwxr-xr-x foo.sh
$ git ls-files -s foo.sh  # Confusing
100644 foo.sh
$ git status  # Confusing
Changes to be committed:
  new file: foo.sh

Changes not staged for commit:
  modified: foo.sh

Running git add again results in an executable file in the staging area and a clean working copy:

$ git add foo.sh
$ git ls-files -s foo.sh
10755 foo.sh
$ git status
Changes to be committed:
  new file: foo.sh

What's going on here?


Solution

  • Git's filters must take their input from STDIN and send their output to STDOUT:

    Upon checkout, when the smudge command is specified, the command is fed the blob object from its standard input, and its standard output is used to update the worktree file. Similarly, the clean command is used to convert the contents of worktree file upon checkin.

    The filter shown above doesn't behave this way. It ignores STDIN and it sends no output to STDOUT, so its effect is not captured with the first git add command. The staged file is not executable.

    However, the filter does run. This has the side effect of adding the execute bit on the file in the working copy. git status sees that the permissions on the file have changed.

    The second git add captures the executable bit, but not directly because of our filter. It simply stages the permission changes it sees in the working copy.

    I am not aware of any way to modify a file's permissions via STDIN and STDOUT. It may be very difficult to modify file permissions in clean and smudge filters.