Can a pre-commit Git hook zip a directory and add it to the repository?

I’m doing development on a WordPress plugin. My development directory contains a lot of development-specific stuff (e.g. Grunt files, Sass files, the git repository itself, etc.).

Obviously, I don’t want to distribute this folder containing all of those development files; people don’t want a few MB of Grunt files when they download my WordPress plugin.

Read More

Up until now, though, my “release” process has been cumbersome:

  1. Commit the Git changes
  2. Zip the entire folder
  3. Open the zip file and delete the .git folder, grunt files, and all the other development-specific files
  4. Release the new zip

I don’t know the best way to accomplish this, but I’m very vaguely familiar with Git hooks, and I had this thought: could I set up a Git hook that would zip ONLY the needed production files into a ZIP file and store it with the repo? That way, every time I commit it would automatically create a new release ZIP.

Is that possible? If so, could someone point me in the right direction?

Oh also, I’m on Windows (・_・;). So I’m hoping that there’s a way to do it on Windows.

Related posts

Leave a Reply

2 comments

  1. I can’t speak for Windows, but:

    1. It’s technically possible to do that sort of thing in a pre-commit hook.
    2. Don’t.

    A pre-commit hook that modifies “what you will commit” is annoying (if nothing else, it violates the “rule of least astonishment”, where your version control system simply stores the versions you tell it to store). Apart from that, storing large pre-compressed binaries interferes with git’s attempt to save space in pack files, and will cause rapid repository bloat, poor performance, running out of memory, and so on. A ZIP-archive is a pre-compressed binary and hence will behave badly.

    In general, a more reasonable “hook-y” way to handle releases is to set up a “release server” to which you push new releases, and have the push trigger the archive-generation. (There are ways to do this without a separate server / repository, and you can do it in a more pull-style fashion, but the push-style is easy to illustrate.)

    [Edit: I had originally considered git archive but did not realize you could get it to exclude files conveniently, so wrote up the below instead. So, jthill’s answer is better and should be one’s first resort. I’ll leave this in place as an alternative for some case where for some reason, git archive might not do.]

    For instance, here’s a server-side post-receive hook code fragment that checks whether a branch whose name matches release* has been pushed-to, and if so, invokes a shell function with the name of the branch (once for each such branch):

    #! /bin/sh
    
    NULL_SHA1=0000000000000000000000000000000000000000
    
    scan()
    {
        local oldsha newsha fullref shortref
        local optype
    
        while read oldsha newsha fullref; do
            case $oldsha,$newsha in
            $NULL_SHA1,*) optype=create;;
            *,$NULL_SHA1) optype=delete;;
            *)            optype=update;;
            esac
            case $fullref in
            refs/heads/*)
                reftype=branch
                shortref=${fullref#refs/heads/}
                ;;
            *)
                reftype=other
                shortref=fullref
                ;;
            esac
    
            case $optype,$reftype,$shortref in
            create,branch,release*|update,branch,release*)
                do_release $shortref;;
            esac
        done
    }
    
    scan
    

    (much of the above is boilerplate, which I have stripped down to essentials). You would have to write the do_release function, which might resemble (totally untested):

    do_release()
    {
        local tmpdir=/tmp/build.$$ # or use mktemp -d
    
        # $tmpdir/index is git's index; $tmpdir/t is the work tree
        trap "rm -rf $tmpdir; exit 1" 1 2 3 15
        rm -rf $tmpdir
        mkdir $tmpdir/t
    
        GIT_INDEX_FILE=$tmpdir/index GIT_WORK_TREE=$tmpdir/t git checkout $1
    
        # now clean out grunt files and make zip archive
        (cd $workdir/t; rm -rf grunt; zip ../t.zip .)
    
        # put completed zip archive in export location, name it
        # based on the branch name
        mv $workdir/t.zip /place/where/zip/files/live/$1.zip
    
        # clean up temp dir now, and no longer need to clean up
        # on signal related abort
        rm -rf $tmpdir
        trap - 1 2 3 15
    }
    
  2. There’s actually a command for this, git archive.

    git archive master -o wizzo-v1.13.0.zip
    

    See the EXAMPLES section, you can select paths, add prefixes to them, define custom postprocessing by output extension, and some more minor tweaks.

    Also see the ATTRIBUTES section: you can give files — arbitrary patterns, really — an export-ignore attribute to exclude them from archives.

    It’s got a bunch more handy-dandies, you can get archives from remote repos, expand arbitrary git log --pretty=format: placeholders, the git manpages are definitely worth whatever time you can invest in them.