Things You Want to Do in Git and How to Do Them

Subscribe with RSS

Preface

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

Commit Hashes

Just copy the first 5-6 characters; you don't need the whole thing, just enough to make it unique.

Commit ranges

Most commands can work on a range with <hash a>..<hash b>. For example, git cherry-pick asd341..fa3745af.

Note the .., ... 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.

HEAD

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.

Undo

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

Instead of 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.

Redo

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.

Eventually, 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

Now 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.