Things You Want to Do in Git and How to Do Them
Subscribe with RSS
Git is a very powerful tool, but infamously it's also a very confusing one. And that has additional effects on how developers use it. New developers often struggle with how long their commits should be, but part of that is just that they're not comfortable with git's tools, because they naturally force you into certain sizes due to convenience.
When you make a commit, think--will I or someone else likely have to
git revert it? In that case, I should make the change as isolating as possible, so that the
revert is as peaceful as possible.
Am I about to make changes that might blow up and force me to
git reset to an earlier state? Well, maybe I should commit my peaceful changes right now so they don't end up as collateral damage.
Just keep thinking about how you'll use git's tools when you inevitably fuck up, and how you can make your commits as convenient as possible for your future self, and that will guide you on the proper length and breadth of your commits.
Now, many articles try to instill in readers a fundamental understanding of how git works, but if most people are like me that just causes my eyes to glaze over as a quick google search turns into a lecture. I think you'll just get it as you use git more. At first, you just have things you want to do, and no idea how to do them, because git's commands are seriously unintuitive once you do most things (like, why does checkout do 3 completely different tasks?)
Some General Tips
Just copy the first 5-6 characters; you don't need the whole thing, just enough to make it unique.
Most commands can work on a range with
<hash a>..<hash b>. For example,
git cherry-pick asd341..fa3745af.
... does something different which is almost certainly not what you want.
Things Happen in the Active Branch
This is obvious if you ever think about how git is implemented, but at first, it may not be obvious what
git merge master does. Is it merging
master to the current branch, or merging my current branch to
master? It's the former.
git merge master will merge master onto the current branch.
git cherry-pick <hash> will apply the commit onto the current branch.
Basically, things happen to the current branch.
Instead of hashes, you can do things relatively.
HEAD~1 refers to the commit just before the most recent one, for instance. In practice, I personally tend to just use commit hashes, but can it be useful sometimes.
Branches as Backups
I didn't get this at first, but branches are really very isolating. If you're afraid you're going to break something horribly,
git checkout -b branchname-backup/
git branch branchname-backup.
Now, regardless of how much you destroy your current branch with whatever black magic you're pasting into your command line from Stack Overflow, you'll always have that frozen snapshot of your repo waiting for you when you inevitably give up.
It's really very liberating, especially early on, because with the like 5 different ways to undo things with varying degrees of severity it can be daunting to actually code without fear like git's supposed to empower you to do.
The basic cycle
git pull master to get up to date
git checkout -b myname-feature to get a branch of your own
git add <files> that you changed (and yes, you can
git add . to add everything in the current directory, but be careful)
git commit and if you don't write anything, the commit will be aborted (also, you can
git commit -m "multiline commits" -m "without text editors" -m "wow!")
git push -u origin myname-feature the first time,
git push after that
git branch -D myname-feature when it gets merged into master and you don't need it cluttering your autocomplete anymore
Woops forgot a file
git add <missing_file> git commit --amend
You can also add
--no-edit if you don't want to change the commit message (or just don't change it when it prompts you)
Added too many files
git reset --soft HEAD~1 git reset HEAD <badfile> git commit
Now we're getting into WTF territory. The first reset will take the commit, and basically return it to the state just before you committed (so they've been
added, but not committed). The second reset will "unstage" it, or "un-add" it. Then you just commit again, minus the file you didn't want.
Everything is on fire please go back
Make everything go away
git reset --hard <commit hash>
Will remove all changes after that hash and remove those commits from history. Basically it's like you never made those bad changes. They don't exist anymore. If you want to see them again, you can't (which in practice isn't a problem much of the time).
There's usually a bunch of warnings when people talk about it, but let's be honest, most of the time this is what you want because you know everything after that commit is garbage.
But I still want some of the changes
git reset <commit hash> implicitly is --mixed
Will also remove the changes and history, but the changed files will still be on-disk, but not staged unlike soft, so you get another chance to pick and choose what you permanently delete.
i.e you might do
git reset 1a34abd git add <one_good_file> git commit -m "everything else is garbage though"
When do I use which reset variant?
--hard when you just want everything to go away
--mixed when the things you want are few
--soft when you want most things, just excluding a few files
Mixed and soft are pretty interchangeable, it's just convenience.
My coworker broke my stuff on master by accident
get revert <bad commit hash>
Unlike the above, this makes a new commit that undoes the bad commit. Very common when for undoing bad things on a shared branch.
Which one should I use?
Reset is cleaner. It's like the bad commits never happened. Revert is safer. Everything is still in history, and it's additive, so it's nicer for everyone else on the shared branch.
In general, if you're on a feature branch on your own, reset is probably what you want. If you're on a shared branch, you should do revert.
I just want this one file to go back
git checkout <happy time hash> -- file git commit
This is usually how you want to do it. Note that this is additive, we're not removing anything.
I need to update my feature branch but don't want it to be ugly
This is a fairly common pattern at larger companies with a mono-repo. You want to keep up to date with master/main, but you don't want your history to look like
commit c1a3647asdfasdfasdfa3fdf43c0f5fdac43e3768 (HEAD -> master) Merge: 728f23c 1b0a8b3 Author: me Date: Today 1:00 pm I added a button <insert 1000 random commits from elsewhere in the monorepo> commit 54aa6fc2919735cfcc10c4f6c83c9232c9788094 Author: me Date: Today 12:50 pm I fixed my config files
git merge master, you do
git rebase master. Then it'll look like
commit c1a3647asdfasdfasdfa3fdf43c0f5fdac43e3768 (HEAD -> master) Merge: 728f23c 1b0a8b3 Author: me Date: Today 1:00 pm I added a button commit 54aa6fc2919735cfcc10c4f6c83c9232c9788094 Author: me Date: Today 12:50 pm I fixed my config files <insert 1000 random commits from elsewhere in the monorepo>
See, it "rewrote" history, as if all those random commits elsewhere in your company happened before you branched off. Nice and clean. But, one gotcha. To update your remote branch you need to do
git push -f
-f is a little scary, but otherwise git will be incredibly confused and try to have you pull from your remote branch. This is, naturally, a workflow for a feature branch with you alone. Don't
-f on a shared branch.
I want to transplant some commits from another branch to this branch
git cherry-pick <hash>
It will selectively apply the commits (or commits, if you use a range) to the current branch. Basically, if don't want to merge everything from another branch, just some things.
I just want another file from another branch
git checkout <branch_name> -- file1 file2
This will add those files (and stage them! You just have to commit afterwards). No commit history, which may or may not be what you want. Also useful for when you committed multiple files (or the file has had a lot of commits over the ages) so you can't really use cherrypick.
I branched off a branch that got merged into master and I want my branch to be off of master now
Okay, that title is a little confusing. Here's the situation: I branched off of master to add profile images, let's say it's
stu2b50-profile-images. I submitted it as a PR, but I wanted to keep working on a vignette effect (but want to keep my PRs short), so I
git checkout -b stu2b50-profile-images-vignette and keep working on it.
stu2b50-profile-images gets squashed and merged into master. But now I want
stu2b50-profile-images-vignette to depend not on the old branch, but master, with its squashed commit, so I can eventually submit for review peacefully.
Here's what I'm going to do
<on master> git checkout -b stu2b50-pfimage-vignette //the new home for my branch git rebase --onto stu2b50-pfimage-vignette stu2b50-profile-images stu2b50-profile-images-vignette
You can see the command as
git rebase —-onto <place-to-put-it> <last-change-that-should-NOT-move> <change to move>
Of course, you can play with what branch names you change. For instance, to keep the original branch name
<on stu2b50-profile-images-vignette> git branch temp git checkout master git branch -D stu2b50-profile-images-vignette git checkout -b stu2b50-profile-images-vignette git rebase --onto-stu2b50-profile-images-vignette stu2b50-profile-images temp
stu2b50-profile-images-vignette is a brand new branch branching off of master with my new vignette effect.
I want to do <complicated thing>
You can probably achieve it by making some temporary branches and
git rebase --interactive. Just leaving this here so you know what to google; it's probably
git rebase, since most "complicated things" involve a messed up commit history.