Pre-commit hook to check for Jira issue key - windows

I am looking for somehelp to write a pre-commit hook on windows to check for Jira issue key while commiting.Commit should not be allowed if Jira key is not present.I couldnt find any way.I am new to scripting.Any help would be highly appreciated.

I assume you are talking about a hooks in a Git repository.
Navigate to your local Git repository and go into the folder .git\hooks
Create a file named commit-msg
Insert the following content (no idea how to format it correctly)
#!/bin/bash
# The script below adds the branch name automatically to
# every one of your commit messages. The regular expression
# below searches for JIRA issue key's. The issue key will
# be extracted out of your branch name
REGEX_ISSUE_ID="[a-zA-Z0-9,\.\_\-]+-[0-9]+"
# Find current branch name
BRANCH_NAME=$(git symbolic-ref --short HEAD)
if [[ -z "$BRANCH_NAME" ]]; then
echo "No branch name... "; exit 1
fi
# Extract issue id from branch name
ISSUE_ID=$(echo "$BRANCH_NAME" | grep -o -E "$REGEX_ISSUE_ID")
echo "$ISSUE_ID"': '$(cat "$1") > "$1"
If you have now a branch named like feature/MYKEY-1234-That-a-branch-name
and add as commit message "Add a new feature"
Your final commit message will look like
MYKEY-1234: Add a new feature
You can put the hook globally when using Git 2.9.
Please find here further useful information:
https://andy-carter.com/blog/automating-git-commit-messages-with-git-hooks
Git hooks : applying `git config core.hooksPath`

You have to put the following script in your local Git repository at .git/hooks/prepare-commit-msg. This will be run whenever you add a new commit.
#!/bin/bash
# get current branch
branchName=`git rev-parse --abbrev-ref HEAD`
# search jira issue id in pattern
jiraId=$(echo $branchName | sed -nr 's,[a-z]*\/*([A-Z]+-[0-9]+)-.+,\1,p')
# only prepare commit message if pattern matched and jiraId was found
if [[ ! -z $jiraId ]]; then
# $1 is the name of the file containing the commit message
sed -i.bak -e "1s/^/\n\n$jiraId: /" $1
fi
First, we get the branch name, for example feature/JIRA-2393-add-max-character-limit.
Next, we extract the key, removing the prefix feature.
The resulting commit message will be prefixed by "JIRA-2393: "
The script also works when there is no prefix, e.g. without feature/, bugfix/, etc.

You can use git server-side pre-receive hook.
https://git-scm.com/docs/git-receive-pack
In the code below for a successful push, you must specify the Jira issue key in the comment for commit.
#!/bin/bash
#
# check commit messages for JIRA issue numbers
# This file must be named pre-receive, and be saved in the hook directory in a bare git repository.
# Run "chmod +x pre-receive" to make it executable.
#
# Don't forget to change
# - Jira id regex
jiraIdRegex="\[JIRA\-[0-9]*\]"
error_msg="[POLICY] The commit doesn't reference a JIRA issue"
while read oldrev newrev refname
do
for sha1Commit in $(git rev-list $oldrev..$newrev);
do
echo "sha1 : $sha1Commit";
commitMessage=$(git log --format=%B -n 1 $sha1Commit)
jiraIds=$(echo $commitMessage | grep -Pqo $jiraIdRegex)
if ! jiraIds; then
echo "$error_msg: $commitMessage" >&2
exit 1
fi
done
done
exit 0

Related

Can git filter repo create a monorepo from many repos interweaving commits by date?

Using git-filter-repo is it possible to combine N repositories into a mono-repository re-writing the commits so that the commits are interwoven, or "zippered" up by date?
Currently, I'm testing this with only 2 repos with each repo having their own subdirectory. After the operation, the commits for each repo are on "top" of each other rather than interwoven. What I really want is to be able to have a completely linear history by authored data without the added merge commits.
rm -rf ___x
mkdir ___x
cd ___x
echo "creating the monorepo"
git init
touch "README.md"
git add .
git commit -am "Hello World!"
declare -A data
data=(
["foo"]="https://github.com/bcanzanella/foo.git"
["bar"]="https://github.com/bcanzanella/bar.git"
)
for d in "${!data[#]}";
do {
REPO_NAME=$d
REPO_REMOTE=${data[$d]}
# since we can use a foo/bar as the repo identifier, replace the / with a -
REPO_DIR_TMP="$(mktemp -d -t "${REPO_NAME/\//-}.XXXX")"
echo "REPO REMOTE: $REPO_REMOTE"
echo "REPO NAME: $REPO_NAME"
echo "REPO TMP DIR: $REPO_DIR_TMP"
echo ""
echo "Cloning..."
git clone "$REPO_REMOTE" "$REPO_DIR_TMP"
echo "filtering into ..."
cd $REPO_DIR_TMP && git-filter-repo --to-subdirectory-filter "$REPO_NAME"
# cat .git/filter-repo/commit-map
## merge the rewritten repo
git remote add "$REPO_NAME" "$REPO_DIR_TMP"
echo "fetching..."
git fetch "$REPO_NAME"
echo "merging..."
git merge --allow-unrelated-histories "$REPO_NAME/master" --no-edit
## delete the rewritten repo
echo "Removing temp dir $REPO_DIR_TMP..."
rm -rf "$REPO_DIR_TMP"
echo "Removing remote $REPO_NAME..."
# git remote rm "$REPO_NAME"
echo "$REPO_NAME done!"
}
done
To emphasize on eftshift0's comment : rebasing and rewriting history can lead to commits being ordered in seemingly absurd chronoogical order.
If you know for a fact that all commits are well ordered (e.g : the commit date of a parent commit is always "older" than the commit date of its child commit), you may be able to generate the correct list of commits to feed in a git rebase -i script.
[edit] after thinking about it, this may be enough for your use case :
Look at the history of your repo using --date-order :
git log --graph --oneline --date-order
If the sequence of commits matches what you expect, you can use git log to generate a rebase -i sequence script :
# --reverse : 'rebase -i' asks for entries starting from the oldest
# --no-merges : do not mention the "merge" commits
# sed -e 's/^/pick /' : use any way you see fit to prefix each line with 'pick '
# (another valid way is to copy paste the list of commits in an editor,
# and add 'pick ' to each line ...)
git log --reverse --no-merges --oneline --date-order |\
sed -e 's/^/pick /' > /tmp/rebase-apply.txt
Then rebase the complete history of your repo :
git rebase -i --root
In the editor, copy/paste the script you created with your first command,
save & close.
Hopefully, you will get a non conflicting unified history.

get the git commit hash of the last commit which affected files not listed in a .gitignore-like file

I am using a CI/CD system to automate the building of Docker Images from a git repository. The Image Tag of the image corresponds to the short (i.e. 8-characters) hash of the corresponding git commit, e.g. myimage:123456ab.
The repository contains source code that gets packaged in the Docker Image and stuff like documentation and deployment configuration that is excluded using a .dockerignore file (similar to .gitignore).
While the process works in general, it leads to rebuilding and redeploying Docker Images that are absolute identical, because the only changes were made to files that did not become part of the Image (e.g. the repositories README).
Using only the shell (bash in this case), git and standard *nix tools, is there a way to get the short hash of the latest commit that changed a file which is not ignored by the .dockerignore file? This should as well cover removing a non-ignored file.
You can do this through a combination of git log and git show.
The following script will go backwards through the revision history and find the first commit to have a change that would not be ignored by .dockerignore
for commit in $(git log --pretty=%H)
do
# Get the changed file names for the commit.
# Use `sed 1d` to remove the first line, which is the commit description
files=$(git show $commit --oneline --name-only | sed 1d)
if docker-check-ignore $files
then
echo $commit
exit 0
fi
done
exit 1
And then you could define docker-check-ignore as a script like the following:
#!/bin/sh
DIR=$(mktemp -d)
pushd $DIR
# Set up a temporary git repository so we can use
# git check-ignore with .dockerignore
git init
popd
cp .dockerignore $DIR/.gitignore
pushd $DIR
git check-ignore $#
# Store the error code
ERROR=$?
popd
rm -rf $DIR
exit $ERROR
I will leave reducing the number of file system operations rather than creating/removing a directory for each commit.
#!/usr/bin/env bash
declare -a ign_table=()
# Populates ign_table with patterns from .dockerignore
while IFS= read -r line || [[ ${line} ]]; do
ign_table+=("${line}")
done < <(sed '/^#/d;/^$/d' .dockerignore)
is_docker_ignored() {
locale -i ignore=1 # false, default not ignored
for ign_patt in "${ign_table[#]}"; do
# If pattern starts with ! it is an exception rule
# when filename match !pattern, do not ignore it
# shellcheck disable=SC2053 # $ign_patt must not use quotes to match wildcards
if [[ ${ign_patt} =~ ^\!(.*) ]] && [[ ${1} == ${BASH_REMATCH[1]} ]]; then
return 1 # false: no need to check further patterns, file not ignored
fi
# Normal exclusion pattern, if file match,
# shellcheck disable=SC2053 # $ign_patt must not use quotes to match wildcards
if [[ ${1} == $ign_patt ]]; then
ignore=0 # true: it match an ignore pattern, file may not be ignored if it later matches an exception pattern
fi
done
return "${ignore}"
}
while IFS= read -r file
do
is_docker_ignored "${file}" && continue # File is in .dockerignore
commit_hash="$(git rev-list --all -1 "${file}")"
printf '%s\n' "${commit_hash:0:8}"
done < <(git ls-files)

change directory in bash is not affecting the post-commit hook

i've made the following bash script to commit the parent repo after some change in submodule.
it's all about that the script want to cd .. to check the parent repo current branch but the problem is that the cd .. is not affecting the upcoming commands because i guess the subshell
i've tried to run
1- cd ../ && before each command
2- make alias but didn't succeed
3- run exec but the script didn't continued
#!/bin/sh
#
# An example hook script to verify what is about to be committed.
# Called by "git commit" with no arguments. The hook should
# exit with non-zero status after issuing an appropriate message if
# it wants to stop the commit.
#
# To enable this hook, rename this file to "post-commit".
commit_msg= git log -1 --pretty=%B
if [[ $(git branch | grep \* | cut -d ' ' -f2) == "int1177/next" ]]; then
cd ..
if [[ $(git branch | grep \* | cut -d ' ' -f2) == "B0/next" ]]; then
git add 6_Tests
git commit -m "bs esss"
echo "development branch B0/next has now new commit"
else
echo "development branch isn't B0/next"
fi
else
echo "current branch isn't int1177/next"
fi
Actually, this particular problem is not a bash issue, but rather a Git issue.
Why doesn't "cd" work in a shell script? is valid in general, and is a suitable answer to many other questions. But this particular post-commit hook is trying to chdir out of a submodule into its parent superproject, then make a commit within the parent superproject. That is possible. It may be a bad idea for other reasons—in general it's unwise to have Git commit hooks create commits, even in other repositories1—but in this particular case you're running into the fact that Git finds its directories through environment variables.
In particular, there's an environment variable GIT_DIR that tells Git: The .git directory containing the repository is at this path. When Git runs a Git hook, Git typically sets $GIT_DIR to . or .git. If $GIT_DIR is not set, Git will find the .git directory by means of a directory-tree search, but if $GIT_DIR is set, Git assumes that $GIT_DIR is set correctly.
The solution is to unset GIT_DIR:
unset GIT_DIR
cd ..
The rest of the sub-shell commands will run in the one-step-up directory, and now that $GIT_DIR is no longer set, Git will search the superproject's work-tree for the .git directory for the superproject.
As an aside, this:
$(git branch | grep \* | cut -d ' ' -f2)
is a clumsy way to get the name of the current branch. Use:
git rev-parse --abbrev-ref HEAD
instead, here. (The other option is git symbolic-ref --short HEAD but that fails noisily with a detached HEAD, while you probably want the quiet result to be just the word HEAD, which the rev-parse method will produce.)
1The main danger in this case is that the superproject repository is not necessarily in any shape to handle a commit right now. Edit: or, as discovered in this comment, is not even set up to be a superproject for that submodule, yet, much less to have a submodule-updating commit added.

Git server-side pre-receive hook

I am working on enforcing git pre-commit hook as a server-side pre-receive or an update hook and unable to find proper examples on achieving it.
I was able to successfully implement/test the pre-commit hook
#!/bin/bash
echo "Running pre-commit hook"
checks=($APPSETTING_DEVPASSWORD $APPSETTING_DEVUSER $APPSETTING_DEVPASS_ELMAH) # create an array
git diff --cached --name-status | while read flag file; do
if [ "$flag" == 'D' ]; then continue; fi
for word in ${checks[#]}
do
if egrep -q "$word" "$file"; then
echo "ERROR: Disallowed expression \"${word}\" in file: ${file}" >&2
exit 1
fi
done
done
I am trying to translate the same into server-side hook where the hook should look for the checks array and exit with 1 if the diff contains the values in the checks array.
Information Found online
Bitbucket server contains only the base repository and doesn't contain the files from the local repository, Hence the diff of the commit sha needs to be evaluated while pushing.
Can someone please help translate the same into a server-side git hook.

Outputting dirty state of git repo in command line issue

if i run this in command line with in git repo
git status 2> /dev/null | grep -iw 'modified'
the output i get
# modified: .rvmrc
so my assumption is that if i plug this in into if statement i will get true that will execute the line of code
but when i created function as part of the .bash_profile, and here is the code i have:
## Prompt ##
path="$white\w$reset"
folder_name="$white\W$reset"
git_branch="\$(git branch 2>/dev/null | grep -e '\* ' | sed 's/^..\(.*\)/$red(\1)$reset/')"
rvm="$green(\$(~/.rvm/bin/rvm-prompt i v p))$reset"
# Checks if working tree is dirty
function _git_st {
if [[ $(git status 2> /dev/null | grep -iw 'modified') ]]; then
echo "[x]"
fi
}
git_st="$yellow`_git_st`$reset"
PS1="$rvm $folder_name $git_branch $git_st \$ "
X is not echoed.... i am bit lost, and not sure what i am doing wrong.
this is the out put i get:
(ruby-1.9.2-p290) folder-git (master) $ git status
# On branch master
# Changes not staged for commit:
# (use "git add <file>..." to update what will be committed)
# (use "git checkout -- <file>..." to discard changes in working directory)
#
# modified: .rvmrc
#
no changes added to commit (use "git add" and/or "git commit -a")
(ruby-1.9.2-p290) folder-git (master) $
The reason for this is because you are using a variable git_st to set PS1 which will be evaluated at the time of setting PS1. What you can try is to invoke the function _git_st be called instead of using git_st i.e. try something on the lines of:
PS1="$rvm $folder_name $git_branch \$(_git_st) \$ ".
Also you might be interested to know that newer versions of git provide functions along with bash completion which provide such utilities. You can take a look at __git_ps1 function if it is available.
Hope this helps!
P.S.: Here is SO post which might provide some pointers for using __git_ps1. This is the quick search result from Google.

Resources