Preventing the push of specific commits

The pre-push hooks are triggered whenever you use the push command and the script execution happens before the push; so, we can prevent a push if we find a reason to reject the push. One reason could be you have a commit that has the nopush text in the commit message.

Getting ready

To use the Git pre-push, we need to have a remote repository for which we will be cloning jgit again as follows:

$ git clone https://git.eclipse.org/r/jgit/jgit chapter7.1
Cloning into 'chapter7.1'...
remote: Counting objects: 2429, done
remote: Finding sources: 100% (534/534)
remote: Total 45639 (delta 145), reused 45578 (delta 145)
Receiving objects: 100% (45639/45639), 10.44 MiB | 2.07 MiB/s, done.
Resolving deltas: 100% (24528/24528), done.
Checking connectivity... done.
Checking out files: 100% (1576/1576), done.

How to do it...

We want to be able to push to a remote, but unfortunately, Git will try to authenticate through HTTPS for the jgit repository before the hooks are executed. Because of this, we will create a local clone from the chapter7.1 directory. This will make our remote a local folder.

$ git clone --branch master ./chapter7.1/ chapter7.2
Cloning into ' chapter7.2'...
done.
Checking out files: 100% (1576/1576), done.
$ cd chapter7.2

We are cloning the chapter7.1 directory in a folder named chapter7.2, and checking the master branch when the clone has finished. Now, we can go back to the chapter7.1 directory and continue with the exercise.

What we now want to do is to create a commit with a commit message that has nopush as part of it. By adding this word to the commit message, the code in the hook will automatically stop the push. We will be doing this on top of a branch. So, to start with, you should check out a prepushHook branch that tracks the origin/master branch and then creates a commit. We will try to push it for the remote when we have the pre-push commit in place, as follows:

  1. Start by creating a new branch named prepushHook that tracks origin/master:
    $ git checkout -b prepushHook  --track origin/master
    Branch prepushHook set up to track remote branch master from origin.
    Switched to a new branch 'prepushHook'
    
  2. Now, we reset back in time; it is not so important how far back we do this. So, I have just selected a random commit as follows:
    $ git reset --hard 2e0d178
    HEAD is now at 2e0d178 Add recursive variant of Config.getNames() methods
    
  3. Now we can create a commit. We will do a simple inline replace with sed and then add pom.xml and commit it:
    $ sed -i -r 's/2.9.1/3.0.0/g' pom.xml
    $ git add pom.xml
    $ git commit -m "Please nopush"
    [prepushHook 69d571e] Please nopush
    1 file changed, 1 insertion(+), 1 deletion(-)
    
  4. To verify whether we have the commit with the text, run git log -1:
    $ git log -1
    commit 1269d14fe0c32971ea33c95126a69ba6c0d52bbf
    Author: Rasmus Voss <[email protected]>
    Date:   Thu Mar 6 23:07:54 2014 +0100
    
        Please nopush
    
  5. We have what we want in the commit message. Now, we just need to prep the hook. We will start by copying the sample hook to the real name so that it will be executed on push:
    cp .git/hooks/pre-push.sample .git/hooks/pre-push
    
  6. Edit the hook so that it has the code as shown in the following snippet:
    #!/bin/bash
    echo "You are not allowed to push"
    exit 1
    
  7. Now, we are ready to push. We will be pushing our current branch HEAD to the master branch in the remote:
    $ git push origin HEAD:refs/heads/master
    You are not allowed to push
    error: failed to push some refs to '../chapter7.1/'
    
  8. As expected, the hook is being executed, and the push is being denied by the hook. Now we can implement the check we want. We want to exit if we have the word nopush in any commit message. We can use git log --grep to search for commits with the keyword nopush in the commit message, as shown in the following command:
    $ git log --grep "nopush"
    commit 51201284a618c2def690c9358a07c1c27bba22d5
    Author: Rasmus Voss <[email protected]>
    Date:   Thu Mar 6 23:07:54 2014 +0100
    
        Please nopush
    
  9. We have our newly created commit with the keyword nopush. Now, we will make a simple check for this in the hook and edit the pre-push hook so that it has the following text:
    #!/bin/bash
    COMMITS=$(git log --grep "nopush")
    if [ "$COMMITS" ]; then 
      echo "You have commit(s) with nopush message"
      echo "aborting push"
      exit 1
    fi
    
  10. Now we can try to push again to see what the result will be. We will try to push our HEAD to the master branch on the remote origin:
    $ git push origin HEAD:refs/heads/master
    You have commit(s) with nopush message
    aborting push
    error: failed to push some refs to 'c:/Users/Rasmus/repos/./chapter7.1/'
    

As expected, we are not allowed to push as we have the nopush message in the commit.

There's more...

Having a hook that can prevent you from pushing commits that you don't want to push is very handy. You can specify any keywords you want. Words such as reword, temp, nopush, temporary, or hack can all be things you want to stop, but sometimes you want to get them through anyway.

What you can do is have a small checker that will check for specific words and then list the commits and ask if you want to push anyway.

If you change the script to the following snippet, the hook will try to find commits with the keyword nopush and list them. If you wish to push them anyway, then you can find an answer to the question and Git will push anyway.

#!/bin/bash
COMMITS=$(git log --grep "nopush" --format=format:%H)
if [ $COMMITS ]; then
       exitmaybe=1
fi
if [ $exitmaybe -eq 1 ]; then
while true
do
  'clear'
  for commit in "$COMMITS"
  do
    echo "$commit has no push in the message"
  done
  echo "Are you sure you want to push the commit(s) "
  read REPLY <&1
  case $REPLY in
    [Yy]* ) break;;
    [Nn]* ) exit 1;;
    * ) echo "Please answer yes or no.";;
  esac
done
fi

Try it with the git push command again as shown in the following snippet:

$ git push origin HEAD:refs/heads/master
Commit 70fea355bac0c65fd51f4874d75e65b4a29ad763 has nopush in message
Are you sure you want to push the commit(s)

Type n and press enter. Then, expect the push to be aborted with the following message:

error: failed to push some refs to 'c:/Users/Rasmus/repos/./chapter7.1/'

As predicted, it will not push. However, on the other hand, if you press y, Git will push to the remote. Try it now using the following command:

$ git push origin HEAD:refs/heads/master
054c5f78fdc82141e9d73e6b6955c38ff79c8b2e has no push in the message
Are you sure you want to push the commit(s)
y
To c:/Users/Rasmus/repos/./chapter7.1/
 ! [rejected]        HEAD -> master (non-fast-forward)
error: failed to push some refs to 'c:/Users/Rasmus/repos/./chapter7.1/'
hint: Updates were rejected because a pushed branch tip is behind its remote
hint: counterpart. Check out this branch and integrate the remote changes
hint: (e.g. 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.

As predicted, the push will be tried, but as you can see from the output, we are rejected by the remote. This is because we diverged, and the push was not working at the tip of the master branch.

So, with this hook, you can make your life a little easier by having the hook prevent you from accidentally pushing something you are not interested in getting pushed. This example also considers commits that have been released; so, if you select a different keyword, then other commits, not only the locally created ones, will be taken into consideration by the script.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset