Test push conflicts on git push via Pre-Receive Hook - bash

I'm making a Pre-Receive Hook on BitBucket that is supposed to confirm that all pushes made in a branch are up-to-date with parent Branches.
I mean, in a temporal evolution, we have several branches creations:
Branch creation during time
With the above example os 3 branches, Dev, Feature1, and my Local, i want to, before making push of Local to remote/origins/Feature1, make git merge from the latest Feature1 with the recent on-push Local Code. In this way, i can confirm that, whoever is making the push, is using the latest version of feature1, and there will be no conflict.
If it were any conflict, i would return 1, to avoid making the push! and obligate the Developer to pull from Feature before push is code.
This is my script on Pre-Receive Hook.
while read from_ref to_ref ref_name; do
echo "Ref update:"
echo " Old value: $from_ref"
echo " New value: $to_ref"
echo " Ref name: $ref_name"
echo " Diff:"
git clone --progress -v $GIT_URL $CLONE_DIR1
cd $CLONE_DIR1
git checkout -b test remotes/origin/Feature1
git merge --no-commit -m "Merging feature with local on-push code" $ref_name
(....)
done
I've tried with ref_name, to_ref, and having no success.
Anyone can help me?
How can I access the recent pushed code, and merge by parent branch with this code?

This seems like a very odd thing to do, and it is probably doomed to failure. It will certainly be complicated and you will want to change your test behavior based on which ref(s) are being updated and whether these update add merge commit(s).
That said, there are some special rules for pre-receive and update hooks, and if you obey them you will get somewhat further:
Do not chdir or cd away from the current directory. Or, if you do, make sure you chdir back, but usually it's not too difficult to make sure that operations that must run in another directory, run as a separate process: either a sub-shell, or another script.
Remove $GIT_DIR from the environment before attempting git commands that need to use a different repository. The reason is that the hook is run in the top level directory with $GIT_DIR set to either .git (non-bare repo) or . (bare repository).
Putting those two together, you might move all your verifier code into a separate script and do something like this:
exitstatus=0
while read from_ref to_ref ref_name; do
... maybe some setup code here to see if $ref_name
is being created or destroyed ...'
case "$ref_name" in
... add cases as needed to choose action based on ref ...
if (unset GIT_DIR; /path/to/check_script arg1 arg2 ...); then
echo "push being rejected because ..."
exitstatus=1
fi
...
esac
...
done
exit $exitstatus
There is still one very big problem here. You want check_script to be able to access any proposed new commits that would become reachable from $ref_name if the hook script exits 0 so that the proposed update to it is allowed. That update has not yet occurred: $ref_name still points to the old SHA-1 $from_ref. Meanwhile, the new SHA-1 in $to_ref might not have any name pointing to it (though it does still exist in the underlying repository).
Among other things, if $to_ref points to new commits (the usual case), any clone you make at this point, via normal git operations, will not contain those commits, so you will not be able to use them.
There are two obvious ways to handle this:
Make a new (temporary) reference that points to $to_ref. You can then see the proposed commits in the clone.
Don't use a clone. Copy the repository some other way, or use the original repository itself directly, e.g., as an "alternate", or by creating a temporary work tree directory and pointing $GIT_WORK_TREE there, or using some of the new git worktree features that have appeared in git 2.6+. (If you choose the manual temporary work-tree method, be sure to think about the normal shared $GIT_INDEX_FILE as well.)
Remember also to check for forced pushes that remove commits from a branch, or even remove-some-and-add-others all in one push.

UPDATE:
This question is resolved for me.
The final code is this:
#!/bin/bash
DIR=xpto/external-hooks/code_review
CLONE_DIR=$DIR/test_conflict_push-$(date +%s)
GIT_URL=myGitUrl
exitStatus=0
read oldrev newrev refname
feature_branch=${refname##refs/heads/}
echo "Feature branch-> $feature_branch"
#Clone feature branch from remote repo to be update via merged.
git clone --progress -v $GIT_URL $CLONE_DIR
currentDir=$PWD
cd $CLONE_DIR
#create branch named 'latest' to put new and modify files
git checkout -b latest remotes/origin/$feature_branch
#go back to PWD otherwise cant make git diff
cd $currentDir
# Get the file names, without directory, of the files that have been modified
# between the new revision and the old revision
echo "Getting files"
files=`git diff --name-only ${oldrev} ${newrev}`
echo "Files -> $files"
# Get a list of all objects in the new revision
echo "Getting objects"
objects=`git ls-tree --full-name -r ${newrev}`
echo "objects -> $objects"
# Iterate over each of these files
for file in ${files}; do
# Search for the file name in the list of all objects
object=`echo -e "${objects}" | egrep "(\s)${file}\$" | awk '{ print $3 }'`
# If it's not present, then continue to the the next itteration
if [ -z ${object} ];
then
continue;
fi
# Otherwise, create all the necessary sub directories in the new temp directory
mkdir -p "${CLONE_DIR}/`dirname ${file}`" &>/dev/null
# and output the object content into it's original file name
git cat-file blob ${object} > ${CLONE_DIR}/${file}
done;
echo "Ready for start merging."
cd $CLONE_DIR
#add new files to branch
echo $(git add .)
#commit added and modify files to branch
echo $(git commit -a -m "Merge latest to original feature")
#get generated commit id
echo $(git log -1)
#create branch named 'merged' to merge above commited files
echo $(git checkout -b merged remotes/origin/$feature_branch)
#merge only occurs for madded and modify files!
echo "Merging committed files to 'merged' branch with from 'latest' branch."
mergeResult=$(git merge --no-commit latest)
echo "Merge Result -> $mergeResult"
##to lower case
if [[ "${mergeResult,,}" == *"conflict"* ]]
then
echo "Merge contains conflicts."
echo "Update your $feature_branch branch!"
exitStatus=1
else
echo "Merge don't contains conflict."
echo "Push to $feature_branch can proceed."
exitStatus=0
fi
#remove temporary branches
echo $(git checkout master)
echo $(git branch -D latest)
echo $(git branch -D merged)
#delete temporary clone dir
rm -rf $CLONE_DIR
exit $exitStatus
Many Thanks.

Related

Why all the script in my git hooks (pre-commit, post-commit, pre-receive, pre-push etc) do not run?

Why all the script in my git hooks (pre-commit, post-commit, pre-receive, pre-push etc) do not run?
Note:
this question is not a duplicate;
I have try the answer to each of the other questions but none of them work.
I did chmod +x, added the path to hook. rename script, neither of them solve my issue.
Inside my git
branches config description HEAD hooks info objects refs
Inside hooks:
applypatch-msg.sample fsmonitor-watchman.sample post-update.sample pre-commit prepare-commit-msg.sample pre-rebase.sample update.sample
commit-msg post-merge.sh pre-applypatch.sample pre-commit.sample pre-push pre-receive
I run them manually and they are all working fine.:
$ bash pre-commit
You are about to commit to master
Do you really want to do this? [y/n] y
pre-commit script
#!/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 "pre-commit".
echo "You are about to commit" $(git diff --cached --name-only --diff-filter=ACM)
echo "to" $(git branch --show-current)
while : ; do
read -p "Do you really want to do this? [y/n] " RESPONSE < /dev/tty
case "${RESPONSE}" in
[Yy]* ) exit 0; break;;
[Nn]* ) exit 1;;
esac
done
But when i git commit and git push to the repository none of the scripts work.
$git commit -m "Test hooks"
[master] Test hooks 1 file
changed, 1 insertion(+)
My git version is 2.39.1
I created the repository on a VM with Ubuntu 18.04.6 LTS installed
Here was the procedure fro creating the repo.
mkdir project1.git
cd project1.git
git init --bare
After the creation i clone the repo to my local computer (windows).
Clone the git repository
git clone git#{ip}:/home/git/git_repositories/project1.git/
But I want use the scripts in project1.git/hooks to make this work.
A pre-commit hook for instance would not work in a bare repository (which has no working tree).
It would work only in your cloned repository, on Windows, where you can create a myClonedRepo/.git/hook/pre-commit script (no extension, no .sh), which will be run before each commit.
From the comments:
all users could create/clone their repositories from a shared Git template repository. The article "Creating a Custom Git Template" gives an illustration of that approach, but means that:
every user must access the same shared folder
they need to activate the init.templateDir config setting in their global Git configuration
any check which must be enforced for all the team members, especially for a distributed team, is best managed by server-side hooks instead.

Backup using GIT - add, commit and push everything including other GIT repositories

I want to build a backup system. I have a server (an old pc) and I boot it up via magic packets. For this purpose, I've written a batch Script doing this. On the server is a git server running with a couple of repositories. I can easily push to it.
But here's the problem:
My project folder contains itself a couple of git repositories. If I now add all files with git add . an Error is thrown, that I should add the submodules. This is the post talking about it Automatically Add All Submodules to a Repo. I've modified the shell script, to work with spaces, and to automatically commit and push everything while executing.
cd "D:\Projekts"
find . -type d | while read x ; do
if [ -d "${x}/.git" ] ; then
cd "${x}"
origin="$(git config --get remote.origin.url)"
cd - 1>/dev/null
echo ""
echo git submodule add "${origin}" "${x}"
git submodule add "${origin}" "${x}"
fi
done
echo "done adding all submodules"
git add .
git commit -am "new backup"
git push
echo "done pushing"
And this doesn't throw an error so far. But still the folders containing all those repositories are empty, if I clone the repository.
sidenote: I add the remote to the backup repository like this: $ git remote add --mirror=fetch origin git#<ip>:backup.git
Thanks in advance for your time,
Hellow2 :)

Consider only files being pushed in pre-push hook git

I want to run lint on code for pre-push without considering the local changed files the user has. for example change in file A is being pushed and the same got changed in the local changes I want to consider the code being pushed by user.
How to implement this using hit-hooks.
Alternate ways I tried:
Limiting the user to reset the changes in pre-push - The functionality is limited in this case
There are several ways to get files from git storage and write them to disk, but I don't know of a direct command to say straight away "checkout files A, B and C from commit xxx to that directory on disk".
The simplest way is probably to use git worktree add (but this checks out all files, not just the ones you want) :
git worktree add /tmp/myhook.xyz <commit-sha>
The most direct way is to use git --work-tree=... (or GIT_WORK_TREE=...) to target some other directory on disk :
git --work-tree=/tmp/myhook.xyz checkout <commit-sha> -- file1 file2 path/to/file3
How to use this in a pre-push hook:
for each pushed reference, you can :
compare the local commit and remote commit to list files that were modified,
use the above trick to checkout the files from local commit in a specific destination on disk :
# pre-push:
#!/bin/bash
zero=$(git hash-object --stdin </dev/null | tr '[0-9a-f]' '0')
list_modified_files () {
local $local_commit=$1
local $remote_commit=$2
if [ "$local_commit" = "$zero" ]; then
return
fi
if [ "$remote_oid" = "$zero" ]; then
git ls-tree -r --name-only $local_oid
else
git diff --no-renames --diff-filter=AM --name-status $remote_oid $local_oid
fi
}
while read local_ref local_oid remote_ref remote_oid
do
echo "'$local_ref' '$local_oid' '$remote_ref' '$remote_oid'"
tmpdir=$(mktemp -d /tmp/myprepushhook.XXXXXX)
list_modified_files | xargs -r git --work-tree "$tmpdir" checkout "$local_oid" --
# run linter on files in $tmpdir ...
rm -rf "$tmpdir"
done

How to extract tag name from `git describe --all` regardless of whether it is annotated or lightweight

Just a quick question:
git describe returns v0.1.0.
git describe --all returns tags/v0.1.0.
I need to extract v0.1.0 regardless of whether it has a tags/ prefix. How do I do this?
# This should echo "false"
if [ $(git describe --all) = "v0.1.0" ]
then
echo "true"
else
echo "false"
fi
The --all option means consider all tags and all branches and all other refs. As such, it always produces tags/string or heads/name or, e.g., remotes/origin/next (or fails; see below).
If you expect the result to always be a tag, just check that it starts with tags/.
If you want to force the result to always be a tag, even if that's a lightweight tag, but never a branch name, use --tags instead of --all.
Note, however, that --tags can fail, if no tag works as a descriptive entity. This is even true for --all, even if there are tags. For instance, an orphan commit created on a detached HEAD when there are no branch names will do the trick:
$ mkdir ttag
$ cd ttag
$ git init
Initialized empty Git repository in ...
$ echo test > README
$ git add README
$ git commit -m initial
[master (root-commit) 7e9de28] initial
1 file changed, 1 insertion(+)
create mode 100644 README
$ git checkout --detach
HEAD is now at 7e9de28 initial
$ git branch -d master
Deleted branch master (was 7e9de28).
$ git describe --all
fatal: No names found, cannot describe anything.
Here's a different example to show how --all can fail even if there are tags:
$ cd .../git
$ git describe
v2.33.1-835-ge9e5ba39a7
$ git checkout --orphan dummy
Switched to a new branch 'dummy'
$ git commit -m for-describe-error
[dummy (root-commit) 06145997e1] for-describe-error
3989 files changed, 1384670 insertions(+)
create mode 100644 .cirrus.yml
create mode 100644 .clang-format
create mode 100644 .editorconfig
[tons of output snipped]
$ git describe --all
heads/dummy
HEAD is now at 06145997e1 for-describe-error
$ git describe --all
heads/dummy
$ git describe --all
fatal: No tags can describe '06145997e15516ce13b24d94ca70ecb7cec8863f'.
Try --always, or create some tags.
$ git checkout master
Warning: you are leaving 1 commit behind, not connected to
[snip warning I didn't really need since that was the idea]
To force git describe not to fail, use --always:
$ git describe --always
7e9de28
It's not really clear to me what your purpose in using --all here was, but if you just want to know if the current commit has some tag, whether the tag is lightweight or annotated, use git describe --tags --exact-match. This either:
fails, because no tag matches; or
succeeds and prints the tag name of the matching tag.
By redirecting stderr away and checking the status, you can tell if there was a tag:
if tag=$(git describe --tags --exact-match 2>/dev/null); then
... this commit is tagged ...
else
... this commit is not tagged ...
fi
and that seems to fit the text of your question, but makes me wonder why you were using --all in the first place.
Note: to check whether a variable $v holds something that starts with the given prefix, the case statement is actually the short and sweet method:
case "$v" in
foo*) echo "prefixed with foo";;
bar*) echo "prefixed with bar";;
*) echo "not prefixed with foo or bar";;
esac
This works for a variable that might hold tags/* or heads/*.

How to run a script on every commit

I want to run a script locally whenever someone in RepoA, makes a commit on a branch
currentBranch=$(git rev-parse --abbrev-ref HEAD)
if [[ $currentBranch = *"my-"* ]]; then
echo "contains my"
else
echo "Hold on there! you need to rename your branch before you commit!
"
fi
I have this running so far which works, whenever I run npm run test:script it runs > "test:script": "./branchname.sh"
however, I have some issues. one how can I run this every time someone commits?
I have tried putting it in my package.json
"pre-commit": [
"lint",
"test:script"
],
but it doesn't run on every commit
also, how can I get the commit itself to abort if the script fails, i.e. jump into the else block
You can take advantage of git hooks. There is a bunch of files you can find in your .git/hooks folder whitin your project folder.
Here the full documentation: https://git-scm.com/book/gr/v2/Customizing-Git-Git-Hooks
Note that you need to give exec permissions to hook files.
Hooks can be basically bash files, so you can abort the commit exiting with a value != 0.
Here an example: https://github.com/xyzale/my-stuff/blob/master/git/hooks/pre-commit
In order to share the hooks with your collaborators you can add a hooks/ folder to your repository and either symlinking it to .git/hooks or editing the git config about the hooks location through the command
git config core.hooksPath hooks

Resources