Useful Git

Useful Git

(20 yrs old)

I’ve been using Git and Github for years now. I’ve only been using it myself for solo projects, just to keep version control if I mess something up. All I’d ever used were git add, git commit, git push.

A couple days ago, I stumbled upon Thorsten Ball’s How I use Git and realized I’d never heard of most of the stuff there. As I was reading through it, I realized a lot of it would be helpful for me. They would solve a lot of the problems I had had.

I had signed up for Boot.dev to learn Go and loved the experience. They also had a 2-part Git course so I thought it would be a good idea to take a Sunday and learn all the good stuff about Git. Here is what I learned.

git switch

A more modern and intuitive way of switching between branches git switch <branch>, or creating them and switching to them git switch -c <branch>. I’m still used to using git checkout <branch> though.

git merge

Until now, I manually used Github UI to create a pull request any time I wanted to merge a branch, for solo projects. I learned you can go to main and use git merge <branch> to merge that branch (e.g. a feature branch) into main. This combines the latest commit in main and your branch and creates a new commit.

A good practice is to first use git merge main into your branch to make sure there aren’t any conflicts, or resolve them if there are, and then merge to main. Or make a pull request.

git rebase

Instead of merging a branch into main, you can rebase the branch onto main. This creates a linear history of main, as if you made the commits after the latest commit of main. This doesn’t create a new commit, unlike merging.

Again, a good practice is to merge main into your branch before rebasing to fix conflicts.

conflicts: —ours and —theirs

When you have conflicts, you can resolve them manually, or choose which branch’s version you want to keep. For example, if you are on main and want to merge dev and see there’s a conflict in file.txt, you can keep the main version by using git checkout --ours file.txt. If you want to keep the version in dev, the branch you are merging, you would write git checkout --theirs file.txt. you would then have to commit the change.

git stash

Sometimes, you’re in the middle of your work and have some un-committed changes but have to go work on something else—for example a quick bug fix you just found.

In this case, you would just simply use git stash. This saves your changes for later when you come back, just write git stash pop. All your changes have come back and you can continue working.

git reset

Any time you want to go back a couple commits, you can use git reset <commit_hash>. There are 2 options you normally use with git reset.

—soft

git reset --soft goes back to the indicated commit but keeps the changes in the workspace.

—hard

git reset --hard goes back to the indicated commit but discards all the changes after.

git revert

If you want to undo a commit, instead of resetting, which rewrites the history of the repo, you can use git revert which creates a commit that un-does the indicated commit. This way, you have the history untouched. e.g. git revert <commit_hash>

git cherry-pick

If you want to replay a commit from a branch on another branch, you can use git cherry-pick. eg. if you want to take a quick bug fix from your dev branch and apply it to main, copy the commit hash in your dev branch, switch to main and do git cherry-pick <commit_hash>

git bisect

This is the coolest thing I learned.

Imagine you’ve been working on a repo for a long time and have made a lot of commits. Suddenly you realize something that was working before isn’t working anymore. You’ve introduced a bug but you have no idea when. You want to find the commit that introduced it.

Well, git bisect allows you to use the magic of Binary Search to find the bug commit in O(log n).

  1. git bisect start

You indicate the good commit (the last commit when it was working), and the bad commit (the one you know it’s not working, e.g the latest one.)

  1. git bisect good <commit_hash>
  2. git bisect bad <commit_hash>

Then, git checks out to different commits (in binary search order) where you perform the test in question to see if there’s a bug or not. You indicate if it’s a good or bad commit.

  1. git bisect good or git bisect bad

You repeat this till git finds the commit that introduced the bug. And then you finish the process.

  1. git bisect reset

What’s even more interesting is that if you have an automated test script, you can make git run it at each commit automatically until it finds the commit in question. e.g. after starting and indicating the good and bad commits, you run git bisect run test.sh. The test should have code 0 for success and 1 for failure.

squashing

Squashing is more common in companies to keep a clean main history. When a developer makes a pull request with all of their commits, they can “squash merge” it. This will represent the changes as a single commit, rather than all the commits that were made on that branch.

worktree

If you want to have different branches of the repo open at the same time so you can test something, or for whatever other reason, instead of cloning the entire repo, you can add a worktree with git worktree add <path> <branch>. This is just a pointer to the repo, so it won’t take the amount of space as the repo. And then once you’re done, you can remove it: git remove worktree <path>

tags

For important commits, like a version release, you could add a tag to make it stand out like: git tag -a v1.0 -m "Release version 1.0". And then you would have to push the tags to your Github/remote repo as well: git push origin --tags

git commit —amend

If you want to change your last commit message, or add/remove files to it and haven’t pushed it to remote, you can use git commit --amend to do that. It basically rewrites your last commit.