Skip to content

Instantly share code, notes, and snippets.

@62mkv
Last active October 29, 2022 14:55
Show Gist options
  • Save 62mkv/394357262058b75c71c9a31c75a08d2b to your computer and use it in GitHub Desktop.
Save 62mkv/394357262058b75c71c9a31c75a08d2b to your computer and use it in GitHub Desktop.
Git recipes

It's very annoying when you write some code at work, say, reproducible cases for OSS issues, but then commit those under work-related credentials and push on GH

To avoid this from happening, I've wrote such a pre-push hook and put it into .git-templates folder (see recipe on global hooks)

#!/bin/bash -e
#
# Git pre-push hook that blocks push if commits are authored or committed using non-personal credentials
#
# Source: https://github.com/git/git/blob/87c86dd14abe8db7d00b0df5661ef8cf147a72a3/templates/hooks--pre-push.sample
#

remote="$1"
url="$2"

z40=0000000000000000000000000000000000000000

# Only apply to repositories under "personal" folder
if [[ `pwd` == *"/IdeaProjects/personal/"* ]]; then 

   while read local_ref local_sha remote_ref remote_sha
   do
	if [ "$local_sha" = $z40 ]
	then
		# Handle delete
                exit 0
	else
		if [ "$remote_sha" = $z40 ]
		then
			# New branch, examine all commits
			range="$local_sha"
		else
			# Update to existing branch, examine new commits
			range="$remote_sha..$local_sha"
		fi

	fi

        if git rev-list "$range" --format=format:"%H %an %ae %cn %ce" --no-commit-header | grep -v "username [email protected] username [email protected]";
	then
		echo "Found wrongly authored commit in $local_ref, not pushing"
		exit 1
	fi
    done

fi

exit 0

In order to commit (and author) with correct credentials, add this to `.git/config" file per project:

[user]
	name = username
	email = [email protected]

BONUS: in order to re-write author name or email in existing commits, after updating git config, run this:

git rebase --root --exec "git commit --amend --no-edit --reset-author" 

(this will re-write all commits between current HEAD and the start of history; if you want to narrow this down, see this answer

Enabling "global" Git hooks (define once, use anywhere)

  1. Create a folder .git-templates under your home profile
  2. Under that folder, create folder hooks and define any hooks you want in it (like, pre-commit)
  3. Run git config --global init.templatedir C:/Users/username/.git-templates
  4. Whenever you run git init, those hooks from template directory will be copied over to the ".git" folder in this directory (see https://git-scm.com/docs/git-init#_template_directory)
  5. In order to enable such hooks in previously cloned repositories, just run git init in them again

Based on https://coderwall.com/p/jp7d5q/create-a-global-git-commit-hook

Say, you have a long history of commits with multiple cross-merges, and now you've finally ready with the "other" branch. Now, before the final integration, you want to have all those changes provided as single clean commit, no merges, no "forgot to add tests", no "typo fixed", etc.

The most "obvious" solution seems to be "interactive rebase". But.. this is unfortunately VERY cumbersome and sometimes even virtually impossible, especially when the branch history was riddled with conflicts and intermediate merges.

So, I eventually figured out another way of doing it:

  • say, you have a target branch (which is a trunk) and a feature branch, which has history you want to squash. NOTE: (important): latest target is already merged into feature.
  • NB: make sure some tag or remote head exists that points into feature head (let's say it's origin/feature)
  • checkout feature branch
  • execute git reset target --hard command (make sure you don't have anything un-committed!)
  • execute git merge --squash origin/feature command
  • execute git commit -a -m "new commit message for single commit"
  • congrats! now it's all neat and tidy. or you've just lost everything.. anyway..

Objective: when splitting mono-repo to multiple repositories, in new repository leave only relevant git history

Let's say we've had a monorepo with a root tree like this:

|
|-common
 |-event
   |-src
   |-.gradle
 |-file
   |-src
   |-.gradle
...   

etc.

And we need to split it in repositories like:

  1. event:
|-src
|-.gradle
  1. file:
|-src
|-.gradle

Steps:

  1. install https://github.com/newren/git-filter-repo
  1. clone the monorepo repository into some new folder, then, in that folder, issue git filter-repo --subdirectory-filter common/event/ --path-rename common/event:/
  2. check the history
  1. Go to .git/hooks folder, and create a pre-commit file (chmod +x if necessary) (see also other recipe on global hooks)

  2. Paste this content into it:

    #!/bin/bash -e
    #
    # Based on "Git pre-commit hook that blocks commits for files that contain swear words" by Dave Hall - http://davehall.com.au
    #
    # link to original hook: https://gist.github.com/skwashd/8732878
    
    ROOT_DIR=$(git rev-parse --show-toplevel)
    EXIT=0
    
    if [[ $(git diff --staged --diff-filter=ACM | grep -i NOCOMMIT | wc -l) -gt 0 ]];then
      echo "NOCOMMIT phrase spotted in staged diff; commit stopped"
      EXIT=1
    fi
    
    exit $EXIT
  3. Then, whenever a "NOCOMMIT" word is staged anywhere in your index, it should stop the commit from happening!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment