Learning Git Concept by Hacking Github

dhanushka madushan
6 min readJun 24, 2019

--

Did you see the Git contribution graph of my Github account in 2016? Check this out here. It prints my name. This blog post is to tell you how I did it along with some underlying Git concepts that might help you as well.

Github provides a way to represent your daily commitment as a graph. Github introduces this contribution graph as a measurement of daily contribution. For me, I take this feature as a motivation factor to do more commits. Lot’s of contribution doesn’t mean that those contributions are worth. But somehow it gives some idea about the contribution you have done to the open-source community.

How Git Internally works

As programmers, we use Git to save the last successful state of a code. Git has a simple way of keeping and versioning history in the Git repository. Git keeps data as a combination of the following objects.

  • Blob: This object is used to store the content of the single file
  • Tree: Reference to another blog or sub-tree
  • Commit: Commit data (Author, Committer, etc.) and reference to a tree object
  • Tag: Reference to Commit object.

Blob is nothing more than a file that you have changed. Whenever you commit content into a Git repository, it will compress the changed files into a Blob-type object. then it generates a hash value for it. While Git also creates another object called Tree and keep a reference to the Blob object. If there are many files that changed, then Git generates many Blob and hash values for each of the changed files. It also generates another object called a tree, to track all changed files. Once you did the commit, It generates another Object called Commit Object. This commit object will keep a reference to the tree Object.

Once you change another file after the first commit, Git will again check for the change file and create new Blobs and Trees for changed files. Then it creates a new Commit Object that points to the Tree object. It also keeps is a reference to the previous commit by using a field called the parent.

Here’s an example.

In this example, we have two files (text1.txt and text2.txt). Commits are represented in green color. tree and blob represented in yellow and purple colors respectively. For the first commit(34c32) there is a tree(3a2d32) associated with it to track files. In this commit there are two files are available(4390a9 and 45c321). For the next commit, we are changing the text2.txt file. Since its content changed, it generate a new blob with a new hash id(53a234). The new commit has a new tree(34c32) and the commit also pointing to the previous commit by parent commit hash.

If you read the Commit Object content with the following command, Content would be something like follows.

git cat-file -p 1658642a6c164700c880d499da0b874c18829883

Here “1658642a6c164700c880d499da0b874c18829883” is the commit hash id.

tree 03de692bf6a38ac9c98bac37dc27534fbaf020b6
parent 5436b80815fde902030d71f08957f68a366dd91f
author Dhanushka Madushan <dhanu@gmail.com> 1559998289 +0530
committer Dhanushka Madushan <dhanu@gmail.com> 1559998289 +0530
Initial commit

Here, Git maintains the author name and the time of commit marked. As you can see, there are two timestamps in the Commit object.

  • Author Date: This is the timestamp when you commit the message. Once the author date is set, it will not change ever again.
  • Commit Date: This date could be changed when you rebase the commit.

How to draw a pixel

Image is a composition of many pixels. Let’s see, how to draw a pixel in a contribution graph. The first thing you need to do is select a git repository. This should not be a forked repository. Github doesn’t count contributions for the forked repository unless changes are merged into the original repository. Thus, remember to use the original repository. Github measures contribution based on the following facts.

  • Committing to a repository’s default branch or gh-pages branch
  • Opening an issue
  • Proposing a pull request
  • Submitting a pull request review
  • Co-authoring commits in a repository’s default branch or gh-pages branch

Here I used the first method since I can change the history by changing commits. First, I created a new file in the repository and added some random words. Then it commits. Then I took the last commit hash id by running git logthe command. Now I can put whatever date into the commit message by running the following command. It replaces commit hash with previously extracted commit hash.

git filter-branch -f --env-filter \
'if [ $GIT_COMMIT = 943f032ac12f386ae6e9e9d14f9e1a4e269a16a4 ]
then
export GIT_AUTHOR_DATE=“Fri Jun 03 21:38:53 2016 -0800"
export GIT_COMMITTER_DATE="Fri Jun 03 21:38:53 2016 -0800"
fi'

When git log executed again, we can see the relevant commit message change into the author and commit date. Now, these changes can be pushed into the remote branch. Since we change the commit history, Changes cannot be a push with git push origin master. Changes should be a push by force with "-f" option. Thus, the command would be git push origin master -f

Warning: Once you push your changes to the Github, contribution graph draw according to the whole commit history. Always remember that you cannot remove contribution from the commit history. Which means, you can only add contribution to the Github. You cannot delete the contribution. Contribution will be permanent.

Drawing on already contributed graph

Github contribution graph represents a contribution by color scale from dark green to light green. Dark green means higher contribution and light green mean lower contribution. Color scale generated dynamically, based on the number of contributions done for the selected one-year period. In this year's contribution graph, I have around 2000 contributions. To print a dark dot in my contribution graph, I have needed at least 40 contributions for a single day. This means I need to run the above script 40 times. So I have created a script to generate many commits with the following script. This script will generate 100 commit messages.

#!/bin/bash
# Basic while loop
counter=1
while [ $counter -le 100 ]
do
echo $counter>>file.txt
echo $counter
git add file.txt
git commit -m "commit $counter"
((counter++))
done
echo All done

I ran the following script to generate many commit massage for the same day. The number of commits per day can be changed with modcommit variables in the script. Ten commits for ten days means 100 commits. This script read the previous 100 commits and change commit timestamp history as it was defined in arr[i]the array.

modcommit=10
arr[0]="Mon Jul 16 21:38:53 2018 -0800"
arr[1]="Fri Jul 20 21:38:53 2018 -0800"
arr[2]="Thu Jul 19 21:38:53 2018 -0800"
arr[3]="Wed Jul 18 21:38:53 2018 -0800"
arr[4]="Fri Jul 27 21:38:53 2018 -0800"
arr[5]="Mon Jul 23 21:38:53 2018 -0800"
arr[6]="Mon Jul 30 21:38:53 2018 -0800"
arr[7]="Fri Sep 28 21:38:53 2018 -0800"
arr[8]="Thu Sep 27 21:38:53 2018 -0800"
arr[9]="Wed Sep 26 21:38:53 2018 -0800"
fix_commit_date() {
echo "commit id $1"
echo "date $2"
git filter-branch --env-filter \
"if [ \$GIT_COMMIT = ${1} ]
then
export GIT_AUTHOR_DATE=\"${2}\"
export GIT_COMMITTER_DATE=\"${2}\"
fi" -f
}
rm commit.txt
git log --pretty=%H>commit.txt
COUNTCOMMITS=$(awk 'END {print NR}' commit.txt)
echo $COUNTCOMMITS
input="commit.txt"
count=0
while IFS= read -r line
do
dateid=$((count / modcommit))
AUDATE=${arr[dateid]}
echo "$line"
DATE2="$( echo "${AUDATE}")"
fix_commit_date $line "$AUDATE"
((count++))
if [[ "$count" == '100' ]]; then
break
fi
echo $TODO
$($TODO)
echo ""

You can change this script as you want to add many contributions for an array of days. If you are trying out this, I recommend doing it for the older year contribution graph. If you add a contribution to the graph, it would be permanent. Hope this article may also help you to understand the underlying Git concept. Join me on Twitter to read more interesting tech stuff. See you in the next article. Cheers :)

--

--