How to add Server-side Custom Hooks in Gitlab? - bash

What I am trying to do is that i want to add my hooks to repo in server, so that whoever has cloned the repo, passes through this before pushing to Gitlab server.
So far what i have done is, created pre-receive file in custom_hooks folder in /.git/custom_hooks and added some script to it.
Below is my pre-receive file.
#!/bin/bash
zero_commit="0000000000000000000000000000000000000000"
excludeExisting="--not --all"
while read oldrev newrev refname; do
# echo "payload"
echo $refname $oldrev $newrev
# branch or tag get deleted
if [ "$newrev" = "$zero_commit" ]; then
continue
fi
# Check for new branch or tag
if [ "$oldrev" = "$zero_commit" ]; then
span=`git rev-list $newrev $excludeExisting`
else
span=`git rev-list $oldrev..$newrev $excludeExisting`
fi
for COMMIT in $span;
do
for FILE in `git log -1 --name-only --pretty=format:'' $COMMIT`;
do
echo "rejecting all pushes"
exit 1
done
done
done
exit 0
Then I cloned the repo in my local Windows machine and tried pushing it. But it didnt create the intended effect. It still got pushed to server.
I'm new to Gitlab and Git Hooks. I don't know whether my pre-receive file is wrong or where I am going wrong. Please let me how to add hooks to server so that it validates/works for whoever cloned my repo.
Please help.
Thanks in advance.

See https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks which starts with:
Like many other Version Control Systems, Git has a way to fire off custom scripts when certain important actions occur. There are two groups of these hooks: client-side and server-side. Client-side hooks are triggered by operations such as committing and merging, while server-side hooks run on network operations such as receiving pushed commits. You can use these hooks for all sorts of reasons.
See also https://docs.gitlab.com/ee/administration/server_hooks.html

Related

Husky pre commit hook and squashing commits

I am using "husky": "^7.0.4".
My team squashes their commits before opening a PR.
I have a pre-commit file to automate this workflow. Every other time I run the commit function, the pre-commit flow works perfectly. So the 1st, 3rd, 5th, etc. works. The 2nd, 4th, 6th, etc time prints this error
fatal: cannot lock ref 'HEAD': is at 766hdjoXXX but expected 766e11XXX
I thought it might be because I wasn't changing the file, however when I tried changing something, that didn't work either(it succeeds and fails every other time regardless). Any idea what's wrong?
Here is the pre-commit file:
read -n1 -p "Do you want to squash commits? [n/Y]" SHOULD_SQUASH < /dev/tty
case $SHOULD_SQUASH in
n|N)
echo
echo Skipping squash, now linting files...
;;
y|Y)
[ -z "$SQUASH_BRANCH" ] && SQUASH_BRANCH=develop
branch=$(git symbolic-ref HEAD)
echo
echo Squashing all commits from $branch
git reset $(git merge-base $SQUASH_BRANCH $branch)
echo ------SUCCESS!------
echo Commits successfully squashed.
git add .
echo Added all files successfully.
;;
*)
echo
echo Skipping squash, now linting files...
;;
esac
npx pretty-quick --staged
npm run lint
The squash function is from a custom function, that works with no problem, we created that lives in .zshrc.
Pre-commit files in general should not use git reset and git add. It is possible to make this work sometimes, but you get odd effects, including the one you're seeing (and sometimes worse ones). A pre-commit script should limit itself to testing whether the commit is OK, and if so, exiting zero; if not, the script should exit nonzero, without attempting to make any changes.1
Instead of calling your script .git/pre-commit and invoking it with git commit, call it makecommit and invoke it as makecommit. Or, call it git-c and invoke it as git c. Have the script do its thing—including run npm lint—and if all looks good, have it run git commit. You won't need a pre-commit hook at all, but if you like, you can have one that reads as follows:
[ "$RUN_FROM_OUR_SCRIPT" = yes ] && exit 0
echo "don't run git commit directly, run our script instead"
exit 1
Then instead of just git commit, have your script do:
RUN_FROM_OUR_SCRIPT=yes git commit
which will set the variable that the pre-commit hook tests to make sure git commit was run from your script.
Note that you will no longer need to redirect the read from /dev/tty. (You probably should also consider using git reset --soft, and/or verifying the that index content matches the working tree content.)
1If you like to live dangerously, and really want to have a script that can update files, make sure that $GIT_INDEX_FILE is unset or set to .git/index, to make sure you have not been invoked as git commit --only.

git hook pre-push bash script validate tag

I'm trying to validate a git tag using the pre-push git hook but whenever I execute git push origin <tag-name> it takes the previous tag as the latest from /refs/tags.
To replicate the issue:
Step 1. git commit
step 2. git tag V1.0.0-US123-major
step 3. git push origin V1.0.0-US123-major
So when step 3 executes the pre-push script should take "V1.0.0-US123-major" tag and validates against the below regex. If the tag matches with regex then it is a valid tag else abort git push.
#!/bin/sh
read_tag="$(git describe --abbrev=0 --tags)"
if [[ $read_tag =~ (^v[0-9]{1}.[0-9]{1}.[0-9]{1}-[a-zA-Z]+-[a-zA-Z]+$) ]]; then
echo "matched"
else
echo "not matched"
exit 1
fi
My expectation is when I use git push origin 2.2.2.2, the pre-push script does not return exit1 rather it accepts the tag and pushing to origin which is not correct.
git push origin 2.2.2
latest tag: v5.5.5-abcd-tues
matched
Can someone help me with this, please?
Your pre-push hook is checking the current revision, not the tag you're pushing, because git describe describes HEAD if you don't specify otherwise.
When you use a pre-push hook, the references being pushed are passed in on standard input. Assuming the thing you want to check is the name of the remote reference (that is, the one that's going to end up on the server), then it could look something like this (using POSIX syntax):
#!/bin/sh
set -e
while read lref new rref old
do
case $rref in
refs/tags/*)
if echo "$rref" | \
grep -qsE '(^refs/tags/v[0-9]{1}.[0-9]{1}.[0-9]{1}-[a-zA-Z]+-[a-zA-Z]+$)'
then
echo "matched"
else
echo "not matched"
exit 1
fi;;
*)
;;
esac
done
Do note that while a pre-push hook can help the developer make good choices and avoid mistakes, it's not an effective control, because it can be trivially bypassed. If you need to restrict what gets pushed to the server, you need to do that either with a pre-receive hook, your server implementation, or a CI system. See the relevant Git FAQ entry for more.

How to run a script on every commit in a github repo? [duplicate]

Let's say I have two scripts in server /hooks folder:
First one initiates logging and writes essential information about push: (post-receive)
#!/bin/sh
read oldrev newrev refname
LOGFILE=post-receive.log
echo " push - Old SHA: $oldrev -> $newrev >> $LOGFILE
sh ./post-receive-logic >> $LOGFILE
Second one does actual deploying: (post-receive-logic)
#!/bin/sh
cd ~/proj
pm2 stop ~/proj/main.js
git --git-dir ~/proj/.git --work-tree ~/proj pull
npm install
pm2 restart ~/proj/main.js
echo "finished"
When I push a commit, second script never gets called: no changes in working tree, no server being killed and restarted, no output specific to second script.
If I call ./post-receive-logic by hand, everything goes fine, server stops, files being pulled, server starts again.
I tried call it without sh, like this:
./post-receive-logic >> $LOGFILE
but no luck.
What am I doing wrong?
Exercise: where is . (or $PWD) during the post-receive hook operation?
When you run it, it's whatever your $PWD is. What about when it's run automatically? (Have a look in the receiving system's bare git repository: your $LOGFILE output will be in that directory.)
(There's a missing close quote in the hook text in your posting, so presumably you hand-copied some part(s) of the script and perhaps there's something else missing. Also, be sure the hook has execute-permission. But my guess is that you're being bitten by the fact that git runs hooks with $PWD set to the .git directory, not the hook directory.)
(Side note: your hook is probably incomplete, as it only reads one oldrev newrev refname, but a git push can push many refs. Normally you should loop: while read oldrev newrev refname; do ...; done. If you have a pre-receive hook that rejects pushes that push more than one ref, though, this particular post-receive hook could be correct.)

Retain committed files through failed pre-commit hook

I have a pre-commit hook that runs some linting like so:
./gradlew app:ktlint --daemon
status=$?
if [ "$status" = 0 ]
then
echo "${green}Linting found no problems.${reset}"
exit 0
else
echo 1>&2 "${red}Linting found issues.${reset}"
echo "${yellow}Attempting to fix automatically...${reset}"
./gradlew app:ktlintFormat --daemon
if [ $? = 0 ]
then
echo "${green}Fixed all issues automatically. Committing automagically...! :)${reset}"
git add .
git commit -m "Automatic commit of linted files" --no-verify
exit 0
else
echo "${red}Could not fix all issues automatically, please review. :( ${reset}"
exit 1
fi
fi
The issue here is that if the ktlint task fails, but the automatic format manages to fix all the problems, i have no way of re-adding only the files that were included in the initial commit.
Perhaps this is best explained with an example:
I have 3 files, A,B,C
I commit A & B, but not C
ktlint fails the commit due to formatting
ktlintFormat manages to fix the issues
Current behaviour: Formatted files A & B but also C are added and committed automatically
Wanted behaviour: Only the formatted A & B files are added and committed automatically
Thanks in advance!
Pre-commit hooks run on the codebase right before the commit goes through. I'd suggest removing the git add/commit lines after the automatic fixes goes through, so the script exits with a zero (successful) status.
You lose the ability to add to the message but the pre-commit will act exactly as it should.
To compare to your example:
3 files, A,B,C
Commit A & B, but not C
ktlint fails the commit due to formatting
ktlintFormat manages to fix the issues
The commit continues, with A & B.
✌️
The solution was to keep track of which files were committed beforehand and then add those manually after the automatic lint formatting took place.
echo "${yellow}Running linting...${reset}"
#store the result of the commit in a variable and split into array items with newline
committedFiles=$(git diff --name-only --cached)
files=$(echo $committedFiles | tr ";" "\\n")
[...]
#after ktlintFormat runs and succeeds
echo "${green}Fixed all issues automatically. Committing..! :)${reset}"
#replay items in the commits array and add only those files
for file in $files
do
git add $file
done
git commit -m "Automatic commit of linted files" --no-verify
exit 0
[...]

git - bash script to automatically reserve merge conflict

My team routinely goes through merge conflicts because of a versioning file being updated on every build. The file is version.txt.
This is where I've got to so far. I only want it to resolve the version.txt conflict by accepting either the current branch version or the incoming branch. When I use the below script, my PRs show as empty which I assume means it has discarded all local changes by using the ours strategy.
I would like the script to keep all local changes–except for the ones causing the conflict, resolve the conflict and push to the current feature branch. What can I do to achieve this?
#!/bin/bash
set -e
FEATURE_BRANCH=$1
DESTINATION_BRANCH=$2
read -p "Would you like to accept incoming or outgoing changes? {in/out}" OP
if [ $OP = "in" ]; then
MERGE_STRATEGY="Xtheirs"
else
MERGE_STRATEGY="Xours"
fi
echo "Pulling current branch"
git pull
echo "Checking out current branch"
git checkout $FEATURE_BRANCH
echo "Pulling destination branch"
git pull origin $DESTINATION_BRANCH -${MERGE_STRATEGY} <<-EOF
:wq #this part is necessary because vim text editor is automatically opened to display a merge message.
EOF
echo "Conflict resolved, pushing back to feature branch"
git push origin $FEATURE_BRANCH
echo "PR ready for merge"

Resources