20

I'd like my remote repository to refuse any pushes that contains a file that contains a tab, but only if the file belongs in a certain class (based on the filename). Is that possible?
I have looked a bit at the update hook in githooks, and I think that is the correct one.

So in short, a push should be rejected if:

  1. there is a file of the listed types (*.cpp, *.h, CMakeLists.txt)
  2. that contains one or more tab characters.
VonC
  • 1,262,500
  • 529
  • 4,410
  • 5,250
  • Are you asking for help writing the hook, or whether an update hook will work for this purpose? – Cascabel Oct 21 '10 at 15:08
  • Both actually :) I was much surprised that this was not easily findable and downloadable, so I have the feeling it is non-trivial. An example of how such a hook might look would help a lot. – Esben Mose Hansen Oct 21 '10 at 18:41

3 Answers3

16

Uh oh, this question seems to have slipped through the cracks. Hope you're still out there, Esben!

You're looking for an update hook, which is run once for each ref updated. The arguments are the name of the ref, the old object name (commit SHA1), and the new object name.

So, all you really need to do is check the diff between the old and new and make sure it meets your standards. This isn't totally straightforward, of course, but it's totally manageable. Here's what I'd do:

Save the following script to .git/hooks/update.

old=$2
new=$3

# that's a literal tab, because (ba)sh turns \t into t, not a tab
# make sure your editor doesn't expand it to spaces
git diff --name-only $old $new | egrep '(\.(cpp|h)$)|^CMakeLists.txt$' | xargs -d'\n' git diff -U0 $old $new -- | grep -q '^+.* ' && exit 1

That lists all the files which differ between the old and new, greps for all the desired ones, gets the diff for them (with zero lines of context, since we don't care), and greps for an added line (starting with +) containing a tab. The grep exits success if it finds one, which will let the && run exit 1, causing the hook to exit failure and abort the update!

Note that this is slightly different from your requirements - it checks if the diff adds any tab characters. This is probably better in the long run; once you've made sure your existing code's okay, it's the same thing, except much faster since it doesn't have to search all the content.

Stevoisiak
  • 23,794
  • 27
  • 122
  • 225
Cascabel
  • 479,068
  • 72
  • 370
  • 318
  • That's perfect. I was actually coming back here to upload my solution, and it is very close... except I use -- $(git ls-files ....) to filter the file types. It is a great comfort to see that my solution matches the experts :) – Esben Mose Hansen Nov 17 '10 at 15:45
  • I just tried this with a commit that did not contain any *.cpp, *.h or CMakeLists.txt (just some random .txt with another name). xargs then executes `git diff -U0 $old $new --` which just diffs the whole commit. The xargs command should be: `xargs -r -d'\n' git diff -U0 $old $new --` to prevent it from executing on empty inputs. – ancow Aug 24 '17 at 14:20
3

You could setup a pre-push hook, but this is not really in the spirit of the git publication mechanism.

I would rather go with:

  • a pre-commit hook, preventing any commit with the wrong content
  • or a filter driver that you can easily associate with the right types of file, and which can fix or report any improper content.
Community
  • 1
  • 1
VonC
  • 1,262,500
  • 529
  • 4,410
  • 5,250
  • In this case, it seems more likely the OP wants an update hook than a pre-push hook; if it's about keeping bad content out of the public repo, you probably want to put a roadblock on the central repo, rather than relying on developers always setting up right. I'm with you on the pre-commit hook, though. I'd much rather find out there's an issue with my code when I'm committing than when I publish. – Cascabel Oct 21 '10 at 15:08
  • @Jefromi: agreed. I find it better to detect problems earlier in the development lifecycle ;) – VonC Oct 21 '10 at 15:15
  • I have already written the pre-commit hook. But that was relatively easy; the update hook seems much more complicated. An example of what might be the right direction would be much appreciated. And yes, I intend to have both: pre-commit to help the devs against them selves and update to help against each other. Devs wanted tab already have a script that he uses to convert back and forth upon checkout/commit. – Esben Mose Hansen Oct 21 '10 at 18:38
  • @EsbenMoseHansen What did your pre-commit hook look like? – Stevoisiak Apr 03 '17 at 20:57
1

Based on benprew's work, here's a pre-script hook that displays an error if any tab characters have been added, as well as the relevant line number. Save the following to .git/hooks/pre-commit.

(Note: pre-commit is the filename. There shouldn't be any . extension)

#!/bin/sh

if git rev-parse --verify HEAD 2>/dev/null
then
  git diff-index -p -M --cached HEAD
else
    :
fi |
perl -e '
    my $found_bad = 0;
    my $filename;
    my $reported_filename = "";
    my $lineno;
    sub bad_line {
        my ($why, $line) = @_;
        if (!$found_bad) {
            print STDERR "*\n";
            print STDERR "* You have some suspicious patch lines:\n";
            print STDERR "*\n";
            $found_bad = 1;
        }
        if ($reported_filename ne $filename) {
            print STDERR "* In $filename\n";
            $reported_filename = $filename;
        }
        print STDERR "* $why (line $lineno)\n";
        print STDERR "$filename:$lineno:$line\n";
    }
    while (<>) {
        if (m|^diff --git a/(.*) b/\1$|) {
            $filename = $1;
            next;
        }
        if (/^@@ -\S+ \+(\d+)/) {
            $lineno = $1 - 1;
            next;
        }
        if (/^ /) {
            $lineno++;
            next;
        }
        if (s/^\+//) {
            $lineno++;
            chomp;
            if (/   /) {
                bad_line("TAB character", $_);
            }
        }
    }
    exit($found_bad);
'

It's not exactly what you asked for since it doesn't do any filename checking, but hopefully it helps regardless.

Stevoisiak
  • 23,794
  • 27
  • 122
  • 225