Some other Git features that I find super useful, which past colleagues of mine haven't known about (which maybe suggests they are not universally known).
git rev-parse branch-name: gets the hash of a branch name, useful for verifying two branches are at the same place (so "git rev-parse HEAD" gets the current hash)
The ^ and ~ notations for navigating commits: my_branch^ points to the commit 1 before my_branch, and can be repeated multiple times, ie. my_branch^^^ to get the commit 3 from the top. Likewise the tilda notation allows one to jump a fixed number of commits up: my_branch~10 is the commit 10 above my_branch.
git rebase -i HEAD~10: interactively rebase the last 10 commits. Easy way to re-order, squash, drop, and/or edit the commit messages of recent history in a single git command.
This one is interesting, because if I recall correctly, if you are using the single symbol they more or less mean the same thing. You can do ~~~ or ^^^ and get to the same spot
The difference is in their numerical notion. ~N means go to my Nth first parent. Where ^N means my Nth parent, which is really only valuable/useful on merge commits (Or more specifically octopus merges that merge more than two commits)
This is why ~ and ^ in the single form mean the same thing.
So, in one mental model of the DAG you can think of ^ as horizontal navigation (Right) and ~ as vertical (Down)
> git rev-parse branch-name: gets the hash of a branch name, useful for verifying two branches are at the same place
I believe the same thing can be done by running git log --oneline -n1 branch-name (though you will also get the commit message title in addition to the sha).
> my_branch^ points to the commit 1 before my_branch, and can be repeated multiple times, ie. my_branch^^^ to get the commit 3 from the top.
The ^ notation only references the first parent of the commit. You would have to do something like commitish^2 to reference
the second parent (though this would only be an issue for merge commits or any commit that has more than one parent).
My god, we have to use git in school and no one knows about rebasing. A group member made commits with profanity in messages (ha-ha, so funny, now how about some useful messages though?) and it turned out at the end we had to submit the repo. Everyone was panicking until I told them I could change the message in literally half a minute.
Rebasing cleans up my commits so much. It is nice being able to have a chain of nice, logical commits even in the middle of a hairy refactor.
For me, I found it very useful to stop using tracking branches. This means I move all of my branch pointers manually and once you get used to it, you’ll begin to find rebases and cherry-picks more intuitive. In fact, it’s made me really distaste merge commits in general.
While a lot of git commands can compete for the title of the most useful, I believe the combination of git reflog and git reset is what can transform the git user from someone who lives in fear of their tool to someone who loves it.
Know reflog and you stop being afraid of losing your work if you mess up, know reflog and reset and you stop being afraid of not being able to fix things if you mess up.
git reset --hard can be pretty destructive when applied at the wrong moment. When reverting changes in selected files, my preferred way is to use git checkout selectively.
git diff # see diff
git diff master..develop # see diff between branches
git show <commit> # see changes in specific commit
git log -p <file> # see log for specific file with diff
git checkout - # checkout in previous branch
git bisect is the command everyone should learn. With a single command (such as a test you just wrote) you can have git do a binary search across any number of commits and find exactly when the bug occurred.
git bisect and got blame are underrated tools, that in my opinion get lost in the conversation about should-you-squash-or-not. They really have diminishing returns when you start squashing huge feature branches into a single commit.
A git alias I use is "git preb", which stands for "pull with rebase". It rebases my commits on top of origin using a merge algorithm, which is smarter than the default which uses patches and often causes merge conflicts. Its definition is - iirc, typing this on my mobile - "pull --rebase -s recursive". Now I don't push any useless merge commits like "Merge origin/develop into develop".
For IDE files a global ignore file is useful, then one doesn't have to repeat this for all repos and not all contributors need to add their IDE's files to a shared file. For local there is also .git/info/exclude per repo.
Yes, that’s good advice. I was referring to the concept of gitignore in general. At least when I learnt git from some tutorials, gitignore was treated like an advanced topic and I was fumbling with git for a while with all these artefacts creeping into my repo until I learnt of this simple solution
Though it would probably be better to tell beginners to run git stash save first. Then they wouldn't lose changes they made to the files before running a hard reset.
Ideally disable force pushes on master branches of a repo to prevent people who screw up to also also break upstream. You can still lose some work, but at least not the complete repo.
Beginners use this command all of the time when they back themselves against a wall and can’t get out. It’s nice to have an escape ladder when you’re beginning.
"Someone told me git reset --hard. Oh shit!!! My extant work is all gone!"
"Hmm, how to start from scratch? I guess I could save a few files, delete the workspace with good old trusty rm -rf, clone again, and restore those few files."
If you can't see the difference between those two...
Yes, the Git power user will know how to "start from scratch" without really starting from scratch and without losing extant work they'd rather save. And beginner users can do the second of the above. But teach a beginner to use git reset --hard and they'll hate Git and they'll hate you.
git rev-parse branch-name: gets the hash of a branch name, useful for verifying two branches are at the same place (so "git rev-parse HEAD" gets the current hash)
The ^ and ~ notations for navigating commits: my_branch^ points to the commit 1 before my_branch, and can be repeated multiple times, ie. my_branch^^^ to get the commit 3 from the top. Likewise the tilda notation allows one to jump a fixed number of commits up: my_branch~10 is the commit 10 above my_branch.
git rebase -i HEAD~10: interactively rebase the last 10 commits. Easy way to re-order, squash, drop, and/or edit the commit messages of recent history in a single git command.