GitCheat
In my previous post, I introduced a long-existing tool that helps you migrate your workflow from Perforce to Git. Here, I elaborate more on those workflows that Git makes possible or does a better job at. These are things I wish I had known earlier.
Reflecting on the Past
Having a good view of a project's history is essential to developers. There is a well-known git log command that shows you some information about every commit.
But you can customise the output of the git log. Running
git log --pretty=format:"%C(Red)%d %C(Yellow)%h %C(Cyan)%ch %C(Green)%cn %C(White)%s" --graph --all
will give you the following:
To make it easier to use, you can add an alias for this better log:
git config --global alias.blog 'log --pretty=format:"%C(Yellow)%h %C(Cyan)%ch %C(Green)%cn %C(White)%s" --graph --all'
Later, you can invoke that by running git blog. git log can also be useful for debugging. Running
git log -S "expression"
will show you the commit that changed the expression's occurrences or
git log -L :function:file
for commits that changed inside a function.
Shaping the Past
Imagine you were working on a feature and wanted to integrate it into the main branch. Your history will look like this:
We have two options at this point. One is to merge these two branches and another to rebase. Let's go through the first option quickly. Running the following command for merge:
git checkout main
git merge feat1
It is likely that we run into a conflict. Because work has been done on the main branch while we were on our feature branch.
vim has a good merge tool and we can set it up as default for git.
git config --global merge.tool vimdiff
After dealing with conflicts and committing, our history will look like this:
The second option is to rebase. In other words, ask Git to take whatever we have done in our feature branch and rebase it on the main. Some repositories prefer this approach to have a clear view of the work done. Also, if you are working with an older VCS through a bridge to Git as we did in my previous post, you may be obligated to rebase and keep a linear history.
Recommended by LinkedIn
The commands are as follows:
git checkout feat
git rebase main
git checkout main
git merge feat
On our feature branch, we rebase on the main. It does not change the main branch yet, but alters the feature branch in a way that looks like we did our work on the main:
Then we will switch to the main branch and do a fast-forward merge. We will end up with a history like this:
If we push our feature branch to a remote and some colleagues start working on that branch, then rebasing can put them in much trouble. You should not rebase what you have pushed.
Changing the Past
Let's say you have committed a large binary file that was unnecessary to put under version control. From now on whoever wants to clone your project has to download that file. Even if you remove it from git:
git rm file.big
It is still in your history and will be downloaded by each cloning. Imagine another scenario where you want to publish your project but don't want your email to be in the commit data. Or what if your project has two parts, say front-end and back-end, and you want to separate them into two different projects? While preserving history for each.
In all of these scenarios, you have to rewrite the past, and the tool for doing that is filter-branch. The following command tells Git to start from a specific commit and remove a file from each one.
git filter-branch --index-filter 'git rm --ignore-unmatch --cached file.big' -- abc123^..
The following command changes the author and committer email to something else:
git filter-branch --commit-filter '
GIT_AUTHOR_NAME="New User";
GIT_AUTHOR_EMAIL="new.user@mail.com";
GIT_COMMITTER_NAME="New User";
GIT_COMMITTER_EMAIL="new.user@mail.com";
git commit-tree "$@";' HEAD
Lastly, the following separates a subdirectory into another git repository:
git filter-branch --subdirectory-filter module HEAD
Sharing the Past
In the scenario of my last post, where you were working locally with Git and were pushing to a Perforce repository, you can not have another remote and share work with colleagues through that. Pushing to Perforce changes each commit's hash by adding the change-list number at the end of the commit message. You will end up with rebasing a pushed work.
You can share your half-done work with colleagues via patches. To do that, switch to your feature branch and run the following:
git format-patch origin/main # or remote/p4/master in our scenario
That gives you a patch file. Your colleague can apply the patch by running:
git am -i 0001-commit-message.patch
The -i option brings the interactive menu from which your colleague can choose what to apply and what to ignore.
Keep Diaries of the Past (Bonus)
If you are planning to move from another VCS to Git, you may be in need of a solution to host your Git server. There is an open-source solution called Gitea. In this Docker-Compose file, I have set up a reverse proxy for personal Git hosting. There is also a link in my Dot Bash History repository that teaches how to set up an SSL certificate for your home server without a public IP address.
version: '3.8'
services:
nginxproxymanager:
image: 'jc21/nginx-proxy-manager:latest'
container_name: nginxproxymanager
restart: always
ports:
- '80:80'
- '81:81'
- '443:443'
volumes:
- ./nginx/data:/data
- ./nginx/letsencrypt:/etc/letsencrypt
gitea:
image: 'gitea/gitea:latest'
container_name: gitea
environment:
- USER_UID=1000
- USER_GID=1000
- GITEA__database__DB_TYPE=postgres
- GITEA__database__HOST=postgres:5432
- GITEA__database__NAME=gitea
- GITEA__database__USER=gitea
- GITEA__database__PASSWD=gitea
#- GITEA__service__DISABLE_REGISTRATION=true
#- GITEA__admin__DISABLE_REGULAR_ORG_CREATION=true
#- GITEA__openid__ENABLE_OPENID_SIGNUP=false
restart: always
ports:
- '2222:22'
volumes:
- ./gitea:/data
- /etc/timezone:/etc/timezone:ro
- /etc/localtime:/etc/localtime:ro
depends_on:
- postgres
postgres:
image: 'postgres:latest'
container_name: postgres
restart: always
environment:
- POSTGRES_USER=gitea
- POSTGRES_PASSWORD=gitea
- POSTGRES_DB=gitea
volumes:
- ./postgres:/var/lib/postgresql/data