129

I need some help with a Bash script that will automatically add the git's branch name as a hash in commit messages.

k0pernikus
  • 60,309
  • 67
  • 216
  • 347
Tomer Lichtash
  • 9,002
  • 16
  • 55
  • 71
  • 5
    For anyone coming here is seems the best answer is at the bottom of the page – Ben Taliadoros Jul 19 '18 at 10:34
  • Side note: all the `git branch | grep ...` for getting the current branch are the wrong way to do this. Consider either `git symbolic-ref -q HEAD` (as shown in [this answer](https://stackoverflow.com/a/17270862/1256452)) or `git rev-parse --abbrev-ref HEAD`. The symbolic-ref command will fail if you're on a detached HEAD, so if you wish to detect that case, use it. Otherwise the rev-parse --abbrev-ref method is probably best. – torek Apr 18 '19 at 20:41
  • @BenTaliadoros Unless by “bottom of the page” you meant that the best answer will always be the most recent one then I don’t think that’s what you meant (for posteriety). – Guildenstern Apr 21 '23 at 06:00
  • @BenTaliadoros Yeah uh which one? – Ryan Walker Jul 28 '23 at 19:53
  • https://stackoverflow.com/a/11524807/930875 – Ben Taliadoros Aug 01 '23 at 08:29

14 Answers14

199

Here is my commit-msg script as an example:

#!/bin/sh
#
# Automatically adds branch name and branch description to every commit message.
#
NAME=$(git branch | grep '*' | sed 's/* //') 
DESCRIPTION=$(git config branch."$NAME".description)

echo "$NAME"': '$(cat "$1") > "$1"
if [ -n "$DESCRIPTION" ] 
then
   echo "" >> "$1"
   echo $DESCRIPTION >> "$1"
fi 

Creates following commit message:

[branch_name]: [original_message]

[branch_description]

I'm using issue number as branch_name, issue description is placed to the branch_description using git branch --edit-description [branch_name] command.

More about branch descriptions you can find on this Q&A.

The code example is stored in following Gist.

Community
  • 1
  • 1
shytikov
  • 9,155
  • 8
  • 56
  • 103
  • 13
    This script squashes multi line commit messages to a single line. I replaced your echo statement with: echo -n "$NAME"': '|cat - "$1" > /tmp/out && mv /tmp/out "$1" – Alex Spence Jan 15 '14 at 16:52
  • 8
    Put this file into the folder PROJECT/.git/hooks/ – Display Name Jan 14 '15 at 14:52
  • 3
    It works well. But for Mac, I had to set the permission too to make it work: >>> sudo chmod 755 .git/hooks/commit-msg – Manoj Shrestha Sep 25 '17 at 18:48
  • 1
    @ManojShrestha yes it has to be executable – David Mann Aug 14 '18 at 16:14
  • 2
    @AlexSpence more simply you could use `echo $NAME: "$(cat $1)" > $1`. This works because the reason the newlines were being lost is that echo was treating each line of `$(cat "$1")` as a new argument and echoing each with a space between. By surrounding `$(cat "$1")` with double quotes, echo treats the cat output as a single argument. Also I don't think it is necessary to quote `$1` since its value is `.git/COMMIT_EDITMSG` – PiersyP Jun 27 '19 at 13:44
  • I get `Aborting commit due to empty commit message.` upon `git commit` – Hairi Dec 19 '19 at 09:15
  • Might be helpful to mention that this is also a git hook – BrutalSimplicity Jan 12 '20 at 18:37
  • How to add this to a submodule hook? Since I get error `cat: .git/COMMIT_EDITMSG: Not a directory` – Estevex Feb 21 '20 at 11:31
65

Use the prepare-commit-msg or commit-msg githook.

There are examples already in your PROJECT/.git/hooks/ directory.

As a security measure, you will have to manually enable such a hook on each repository you wish to use it. Though, you can commit the script and copy it on all clones into the .git/hooks/ directory.

k0pernikus
  • 60,309
  • 67
  • 216
  • 347
ninjagecko
  • 88,546
  • 24
  • 137
  • 145
  • 5
    I don't need to, you already have an example that does **exactly what you want**, as I already said, in `.git/hooks/prepare-commit-msg.sample`. =) All you need to modify (after following the directions in the comments) is to copy-paste whatever solution from http://stackoverflow.com/questions/1593051/how-to-programmatically-determine-the-current-checked-out-git-branch you'd like – ninjagecko May 05 '11 at 10:13
  • 5
    @ninjagecko, for me `.git/hooks/prepare-commit-msg.sample` contains three examples. One for commenting out conflicts section, adding `git diff --name-status -r` output to it and adding Signed-off-by lines... No adding branch name to the commit message. So I was forced to write my own hook. – shytikov Jul 18 '12 at 07:11
  • 1
    Does this `you will have to manually enable such a hook on each repository you wish to use it` mean that you have to give the FILE execute permissions? If so, may I edit the answer to include that (or could you, please)? – Dan Rosenstark Dec 26 '16 at 17:32
  • @DanRosenstark: I think the .git/hooks folder is not tracked (at least by default) and one may need a manually copy the files into the .git/hooks folder (or have a script to do it) or some other homebrew custom method. For what I meant, see http://stackoverflow.com/questions/4457031/tracking-changes-to-hooks-in-git-hooks . Also to answer your question: the sample files in the .git/hooks directory don't seem to suggest adding +exec permissions (though I do see some sites saying you might need exec permissions; thus I don't know the exact answer to your question). – ninjagecko Dec 28 '16 at 05:27
  • @ninjagecko okay, we'll leave this as is for now, but for ME on osx, changing permissions to 755 on the particular file in the hooks directory worked for me. However, in the end I went with an older solution, which doesn't use hooks at all: http://stackoverflow.com/questions/4086896/including-the-current-branch-name-in-the-commit-template/4316350#4316350 ... which only covers some part of the current question, but anyway... Thanks! – Dan Rosenstark Dec 28 '16 at 20:53
43

A bit simpler script that adds the branch name to the commit message before you edit it. So if you want want to change or remove it you can.

Create this file .git/hooks/prepare-commit-msg:

#!/bin/bash

branchPath=$(git symbolic-ref -q HEAD) #Somthing like refs/heads/myBranchName
branchName=${branchPath##*/}      #Get text behind the last / of the branch path

firstLine=$(head -n1 $1)

if [ -z "$firstLine"  ] ;then #Check that this is not an amend by checking that the first line is empty
    sed -i "1s/^/$branchName: \n/" $1 #Insert branch name at the start of the commit message file
fi
Pylinux
  • 11,278
  • 4
  • 60
  • 67
  • 6
    I get: `sed: 1: ".git/COMMIT_EDITMSG": invalid command code .` when using this. – Adam Parkin Jul 16 '13 at 18:08
  • 1
    Aha, Mac OSX difference, see: http://hintsforums.macworld.com/showpost.php?p=393450&postcount=11 for the fix – Adam Parkin Jul 16 '13 at 18:19
  • 2
    like the checking of the amend and fixup case – pogopaule Mar 27 '15 at 12:55
  • 5
    OSX : Needs file extension to work if you're getting the above error message. `sed -i '.bak' "1s/^/$branchName : \n/" $1` – canintex Jul 15 '15 at 18:44
  • 3
    You can use `@` as a `sed` separator instead of `/` since forward slashes are more likely to show up in the branch name or commit message, screwing up `sed`. – Ory Band Oct 28 '15 at 14:34
  • @OryBand the branchName is the text behind the last slash, i.e. it will never contain a slash – Pylinux Oct 29 '15 at 07:37
  • @Pylinux it failed for me on a branch name called `ory/SERVER-111-bla-bla` – Ory Band Oct 29 '15 at 10:10
  • 1
    @OryBand then it's not that branchName contains slash that is the issue, run this in a *nix cmd `branchPath=ory/SERVER-111-bla-bla; echo ${branchPath##*/}` and you'll se that the branchName don't contain slashes. – Pylinux Oct 29 '15 at 13:40
  • @Pylinux than why did replacing `/`s with `@`s solve the problem? nothing else included slashes. – Ory Band Oct 29 '15 at 14:11
  • @OryBand did you verify that the branchName variable contained slashes? The only job of line two is to remove "folders" from the branch. – Pylinux Oct 30 '15 at 16:13
  • @Pylinux it does contain slashes, but I want the branch to *keep* the slashes e.g. `ory/SERVER-111-bla-bla`. If I execute line 2 than the `ory/` part will be removed, and I don't want that. – Ory Band Nov 01 '15 at 07:48
  • 1
    @Pylinux I now understand what you were talking about. You're doing remove prefixed parts with `/`s from the branch name, thus avoiding the case I had. [I skipped the substitution in my version](https://gist.github.com/oryband/da880c74b6b8e7433adf), and didn't notice this until now. Thanks! – Ory Band Nov 02 '15 at 13:32
  • No problem, happy to help :-) – Pylinux Nov 03 '15 at 08:46
  • AFAICT this assumes that the message comes from `-m`: I needed to remove the `if` to get it to work with a message generated by a template. – snakecharmerb Dec 05 '20 at 14:46
  • 1
    @snakecharmerb This is meant to be used _without_ `-m`, i.e. just with `git commit` and the editor setup in git (usually vim). Pretty sure `-m` will override what we are trying to achieve here. – Pylinux Dec 06 '20 at 15:11
31

You can do it with a combination of the prepare-commit-msg and pre-commit hooks.

.git/hooks/prepare-commit-msg

#!/bin/sh

BRANCH=`git branch | grep '^\*' | cut -b3-`
FILE=`cat "$1"`
echo "$BRANCH $FILE" > "$1"

.git/hooks/pre-commit

#!/bin/bash

find vendor -name ".git*" -type d | while read i
do
        if [ -d "$i" ]; then
                DIR=`dirname $i`
                rm -fR $i
                git rm -r --cached $DIR > /dev/null 2>&1
                git add $DIR > /dev/null 2>&1
        fi
done

Set permissions

sudo chmod 755 .git/hooks/prepare-commit-msg
sudo chmod 755 .git/hooks/pre-commit
slm
  • 15,396
  • 12
  • 109
  • 124
Farid Movsumov
  • 12,350
  • 8
  • 71
  • 97
  • 1
    Note that this can remove the original commit message if you're using `--amend` for example. Instead of using `echo` you should use `sed` instead. Here it is in a one liner: `sed -i "1s@^@$(git branch | grep '^\*' | cut -b3-) @" $1` – Ory Band Oct 28 '15 at 14:32
16

add the below code in prepare-commit-msg file.

#!/bin/sh
#
# Automatically add branch name and branch description to every commit message except merge commit.
#

COMMIT_EDITMSG=$1

addBranchName() {
  NAME=$(git branch | grep '*' | sed 's/* //') 
  DESCRIPTION=$(git config branch."$NAME".description)
  echo "[$NAME]: $(cat $COMMIT_EDITMSG)" > $COMMIT_EDITMSG
  if [ -n "$DESCRIPTION" ] 
  then
     echo "" >> $COMMIT_EDITMSG
     echo $DESCRIPTION >> $COMMIT_EDITMSG
  fi 
}

MERGE=$(cat $COMMIT_EDITMSG|grep -i 'merge'|wc -l)

if [ $MERGE -eq 0 ] ; then
  addBranchName
fi

It will add branch name to commit message except merge-commit. The merge-commit has branch information by default so extra branch name is unnecessary and make the message ugly.

Tim
  • 161
  • 1
  • 6
  • 2
    So this will not amend the commit message when it find the word merge on the message ? – thoroc Feb 01 '16 at 09:39
  • 3
    @thoroc that is technically correct; however, in normal use this isn't a big deal. The commit message being parsed is the "default ones" prior to you editing them. So as long as your commit template doesn't have the word "merge" in it, I believe you should be okay (as long as the other "default" messages don't except for a default merge commit message). I misunderstood this originally, and believe I have it correct now. – Novice C Sep 19 '16 at 02:22
6

Inspired by Tim's answer which builds upon the top answer, it turns out the prepare-commit-msg hook takes as an argument what kind of commit is occurring. As seen in the default prepare-commit-msg if $2 is 'merge' then it is a merge commit. Thus the case switch can be altered to include Tim's addBranchName() function.

I've included my own preference for how to add the branch name, and all the uncommented parts of the default prepare-commit-msg.sample hook.

prepare-commit-msg

#!/bin/sh

addMyBranchName() {
  # Get name of current branch
  NAME=$(git branch | grep '*' | sed 's/* //')

  # First blank line is title, second is break for body, third is start of body
  BODY=`cut -d \| -f 6 $1 | grep -v -E .\+ -n | cut -d ':' -f1 | sed '3q;d'`

  # Put in string "(branch_name/): " at start of commit message body.
  # For templates with commit bodies
  if test ! -z $BODY; then
    awk 'NR=='$BODY'{$0="\('$NAME'/\): "}1;' $1 > tmp_msg && mv tmp_msg "$1"
  else
    echo "title\n\n($NAME/):\n`cat $1`\n" > "$1"
  fi
}

# You might need to consider squashes
case "$2,$3" in
  # Commits that already have a message
  commit,?*)
  ;;

  # Messages are one line messages you decide how to handle
  message,)
  ;;

  # Merge commits
  merge,)
    # Comments out the "Conflicts:" part of a merge commit.
    perl -i.bak -ne 's/^/# /, s/^# #/#/ if /^Conflicts/ .. /#/; print' "$1"
  ;;

  # Non-merges with no prior messages
  *)
    addMyBranchName $1
  ;;
esac
Novice C
  • 1,344
  • 2
  • 15
  • 27
6

In case you want the JIRA ticket added to the commit message use the script bellow.

Commit message something like PROJECT-2313: Add awesome feature This requires your branch name to start with the jira Ticket.

This is a combination of this solutions:

It is modified for OS X, with the sed -i '.bak' and it works as well from SourceTree.

https://gist.github.com/georgescumihai/c368e199a9455807b9fbd66f44160095

#!/bin/sh
#
# A hook script to prepare the commit log message.
# If the branch name it's a jira Ticket.
# It adds the branch name to the commit message, if it is not already part of it.

branchPath=$(git symbolic-ref -q HEAD) #Somthing like refs/heads/myBranchName
branchName=${branchPath##*/}      #Get text behind the last / of the branch path

regex="(PROJECTNAME-[0-9]*)"

if [[ $branchName =~ $regex ]]
then
    # Get the captured portion of the branch name.
    jiraTicketName="${BASH_REMATCH[1]}"

    originalMessage=`cat $1`

    # If the message already begins with PROJECTNAME-#, do not edit the commit message.
    if [[ $originalMessage == $jiraTicketName* ]]
        then
        exit
    fi

    sed -i '.bak' "1s/^/$jiraTicketName: /" $1 #Insert branch name at the start of the commit message file
fi
Mihai Georgescu
  • 674
  • 8
  • 20
  • This is working good on client side file : prepare-commit-msg to auto populate commit prefix. But if I want to do the same on the server side hook, which is bitbucket server (in my case) and I am trying to add this logic on pre-receive hook at the Bitbucket server path : BITBUCKET_HOME/shared/data/repositories//hooks/21_pre_receive , it is not working as "git symbolic-ref -q HEAD" giving 'master' though I am committing from my feature/abc branch from client side. Is there another way here? – santhosh Aug 14 '20 at 14:46
5

If you want to make it global (for all projects):

Create git-msg file with the content of shytikov's answer, and put it in some folder:

mkdir -p ~/.git_hooks
# make it executable
chmod a+x ~/.git_hooks/commit-msg

Now enable hooks:

git config --global init.templatedir '~/.git_hooks'

and git init again in each project you want to use it.

Maroun
  • 94,125
  • 30
  • 188
  • 241
  • 2
    I found that to use this feature, I had to put 'commit-msg' into a 'hooks' directory inside the directory configured for 'init.templatedir' so that when the whole templatedir gets copied on 'git init', 'commit-msg' ends up in the project's '.git/hooks' directory. – Dan Zaner Feb 06 '19 at 04:46
5

I edited this answer (https://stackoverflow.com/a/17270862/9266796) so it also works for branches that contain slashes in their name, using @ instead of / as a sed separator. Getting the branch name is also simpler now with git branch --show-current. I also moved the branch name to the bottom of the commit message since it makes more sense that the actual title of the message is what you see first.

The file should still be called .git/hooks/prepare-commit-msg.

#!/bin/bash

branchName=$(git branch --show-current)
firstLine=$(head -n1 $1)

if [ -z "$firstLine"  ] ;then #Check that this is not an amend by checking that the first line is empty
    sed -i "1s@^@\n\n$branchName@" $1 #Insert branch name at the end of the commit message file
fi
Johan Maes
  • 1,161
  • 13
  • 13
  • side note: for Mac users, remember to add an empty string as a backup file extension, i.e., `sed -i "" "1s@^@\n\n$branchName@" $1` – PJCHENder Mar 17 '23 at 02:41
3

I adapted this for my needs:

#!/bin/bash

# hook arguments
COMMIT_MSG_FILE=$1
COMMIT_SOURCE=$2
SHA1=$3

BRANCH_NAME=$(git branch --show-current)

# check branch name isn’t empty (typical e.g. during rebase)
if [ -n "$BRANCH_NAME" ]
then
  # check that this is a regular commit
  if [ "$COMMIT_SOURCE" = "message" ] || [ -z "$COMMIT_SOURCE" ]
  then
      sed -r -i "1!b;/^(fixup|squash)/! s@^@$BRANCH_NAME @" $COMMIT_MSG_FILE # insert branch name at the start of the commit message file
  fi
fi

Copy this snipped in a file called prepare-commit-msg inside your repos .git/hooks folder.

This should add the branch name in the case of git commit, git commit -m … and do nothing in case of merge, rebase etc.

Peter
  • 785
  • 2
  • 7
  • 18
  • 1
    Did the job for me (for my needs then with some modifications), but I did not know where to put this, found that it is in .git/hooks/prepare-commit-msg, you may update your answer with this ;) – gluttony Aug 23 '22 at 10:03
2

I was having issues getting these solutions to work on MacOS due to the fact that it uses BSD sed instead of GNU sed. I managed to create a simple script that does the job though. Still using .git/hooks/pre-commit:

#!/bin/sh
BRANCH=$(cat .git/HEAD  | cut -d '_' -f2)
if [ ! -z "$BRANCH" ]
then
    echo "$BRANCH" > "/Users/username/.gitmessage" 
else
    echo "[JIRA NUMBER]" > "/Users/username/.gitmessage"
fi 

This assumes a branch naming standard similar to functional-desc_JIRA-NUMBER. If your branch name is only your Jira ticket number you can simply get rid of everything from the pipe to the f2. It also requires that you have a file named .gitmessage in your home directory.

Patrick Mevzek
  • 10,995
  • 16
  • 38
  • 54
PhPGuy
  • 41
  • 3
1

Piecing together the excellent answers here, and adding a few tweaks, my .git/hooks/prepare-commit-msg script takes a branch name such as feature/ABC-#123-feature-title and outputs ABC #123:

COMMIT_MSG=$1
REPLACE_DELIMETER=true
FIRST_LINE=$(head -n1 $COMMIT_MSG)

addBranchName() {
  BRANCH_NAME=$(git branch | grep '*' | sed 's/* //') # get the branch name (e.g. `feature/ABC-#123-feature-title`)
  NAME=${BRANCH_NAME##*/} # get everything after the last slash (e.g. `ABC-#123-feature-title`)
  DELIMITER=$(echo $NAME | grep -o '[-_]' | head -n1) # get the character that separates the parts (e.g. `-`)
  FEATURE_ID=$(echo $NAME | cut -d $DELIMITER -f 1,2) # get the first two parts of the name, split by the delimeter found in the branch name (e.g. `ABC-#123`)

  if [ "$REPLACE_DELIMETER" = true ]; then
    FEATURE_ID=$(echo $FEATURE_ID | sed "s/$DELIMITER/ /g") # replace the delimiter if `REPLACE_DELIMETER` is set to true (e.g. `ABC #123`)
  fi

  echo "$FEATURE_ID: $(cat $COMMIT_MSG)" > $COMMIT_MSG # prepend the name and parse the existing commit message (for instance commented out commit details)
}

if [ -z "$FIRST_LINE" ]; then # check that this is not an amend by checking that the first line is empty
  addBranchName
fi
fredrivett
  • 5,419
  • 3
  • 35
  • 48
  • Just a quick note on that. Most tools that allow you to connect commit messages to another tool want the ticket id intact at the beginning, so if you ticket ID is ABC-123, you want to keep it intact in the commit message as ABC-123 – Brill Pappin Feb 09 '23 at 15:25
1

You can use git prepare-commit-msg hook for this.

Hook can look like this:

# Get the currently checked-out branch name
BRANCH_NAME=$(git branch | grep '*' | sed 's/* //')

# Get the commit message, removing lines that start with a #
MESSAGE=$(cat "$1" | sed '/^#.*/d')

# Check if the commit message is non-empty
if [ -n "$MESSAGE" ]
then
    # Add the branch name and the commit message
    echo "$BRANCH_NAME: $MESSAGE" > "$1"
else
    echo "Aborting commit due to empty commit message."
    exit 1
fi

You probably don't want to include the branch name when committing merges, as default merge comment already includes branch names. When git does merge - it adds MERGE_HEAD file into the .git folder. So we can use that to exit hook before prepending branch name.

# Check if the MERGE_HEAD file is present
if [ -f ".git/MERGE_HEAD" ]
then
    exit 0
fi

Full hook can be fetched from the gist.

Git hooks work per repository, so you will need to copy the hook file into the .git/hooks directory of each repository you want to use it in. To automate the process of adding the git hook to multiple repositories, you can run next script in the root folder of your repositories.

You can fetch the script from gist to the root folder like this:

curl -s "https://gist.githubusercontent.com/wolf6101/a90126e4b47a943e0235861516236eb3/raw/2f505db26d8adbabab9b93a8bb990ab42b2fb55c/apply-git-hooks.sh" -o "apply-git-hooks.sh"  

To add hook:

sh apply-git-hooks.sh     

To remove hook:

sh apply-git-hooks.sh remove

So now when you commit to the branch with the name ABC-123, like this:

git commit -m "I wrote something incredible, check this out"

You will get "ABC-123: I wrote something incredible, check this out"

This will work for commits via GUI as well.

0

my bash add branch name and version from VERSION file around commit text


    #!/bin/bash
    #вторая версия, умеет вытаскивать текст коммита сообщений вида:
    # "$BRANCH текст ($VERSION)"
    # "текст ($VERSION)"
    # "$BRANCH текст"
    # "текст"
    #долбанный баш, перед и после = нельзя ставить пробелы
    
    COMMIT_MSG_FILE=$(echo $1 | head -n1 | cut -d " " -f1)
    VERSION_FROM_FILE=$(head -n 1 version)
    
    COMMIT_MSG_TEXT=$(cat "$COMMIT_MSG_FILE"  | sed -r 's/.*PPP-[0-9]{0,6} (.*) ?/\1/') #убираем имя ветки если оно есть
    COMMIT_MSG_TEXT=$(echo "$COMMIT_MSG_TEXT"  | sed -r 's/(.* )\(.*\)/\1/') #убираем версию если она есть
    
    COMMIT_MSG_TEXT=$(echo "$COMMIT_MSG_TEXT" | xargs) #это обычный trim
    echo "текст коммита $COMMIT_MSG_TEXT"
    
    if [ -z "$BRANCHES_TO_SKIP" ]; then  #в этих ветках ничего не подставляем
     BRANCHES_TO_SKIP=(master develop test)
    fi
    
    BRANCH_NAME=$(git symbolic-ref --short HEAD)
    BRANCH_NAME="${BRANCH_NAME##*/}" #что тут происходит хз, какое то комбо на фаталити
    
    echo -n "$BRANCH_NAME $COMMIT_MSG_TEXT ($VERSION_FROM_FILE)" > $COMMIT_MSG_FILE

create new task in gradle(kotlin) with automatic install pre-commit-msg hook

task<Copy>("installGitHook") {
    group = "verification"
    description = "Install Pre-commit linting hook"
    from("gradle/prepare-commit-msg")
    into(".git/hooks")
    // Kotlin does not support octal litterals
    fileMode = 7 * 64 + 7 * 8 + 7
}

tasks.named("assemble") { dependsOn("installGitHook") }
Vovan
  • 478
  • 4
  • 5