Cover Image: Black and Silver Laptop Computer on Round Brown Wooden Table by Christina Morillo
These days, most developers are using some form of version control, and the most popular version control system used today is git.
I've written a few handy git articles:
- 10 Git Tricks to Save Your Time and Sanity
- Git Bisect is Easy
- A Quick Guide to Hunky Git
- 10 More Git Tricks That You Should Know
This one will be a continuation of those articles, with more focus on pragmatism. In the previous articles, I was sharing interesting things that you could do with git. In this article, I'm going to focus on things that every developer should know how to do.
1. Add a change to the last commit
Modifying the last commit is handy for anyone who has forgotten to stage a change (which is everyone who has ever used git).
This is really simple. All you have to do is stage the file you forgot to stage and then use the git commit --amend
command.
Say I forgot to stage my changes to the README file, this is what I'd do:
git add README.md
git commit --amend
I'd be prompted to modify the commit message and then: Tada! The README.md changes have been added to the last commit.
2. Split the last commit into multiple commits
Say you have a different problem. If you accidentally staged something and then made a commit when you intended to make two commits.
All you need to do is walk back by one commit using the git reset
command:
git reset HEAD~1
Then use git add
to stage your changes (I recommend using git add --patch
, I wrote an article on it, if you're not familiar), and commit:
git add --patch
git commit -m "This is the first commit"
Then repeat the process as many times as needed:
git add --patch
git commit -m "This is the second commit"
One commit has become many. π₯
3. Squash some previous commits into one commit
Every developer should know how to squash commits. This is something you will probably be asked to do if you make a contribution to Open Source and you've made a few redundant or poorly-worded commit messages.
I'm going to share a couple of approaches, one that uses git rebase
, and one that doesn't.
Without git rebase
, you can easily squash the last few commits into one. First, you'll want to use the git reset
command to move back through the commits you'd like to squash.
You can do this by passing HEAD~x
to the git rebase
command, where x
is the number of commits you'd like to squash together.
For example, if you want to squash the previous three commits into one commit, you'd run the following command:
git reset HEAD~3
If you were to check the log, you'd notice that you are now three commits behind your previous position.
A quick git log
will reveal that all of the changes from the previous three commits still exist on your machine, but are now unstaged.
To squash them together into one commit, all you need to do is stage them and commit them:
git commit -am "Some commit message"
Congratulations! π You've squashed three commits into one!
Alternatively, you can use git rebase
with the interactive flag. That would look something like this:
git rebase -i master
Replace master with the branch where you will eventually merge.
git rebase -i
will drop you into a text editor that looks something like this:
pick 1eabc17a8 Add data-attributes to container elements
pick 90c58b487 Fix react-dom warning
Rebase 5f5e140ea..90c58b487 onto 5f5e140ea (2 commands)
Commands:
p, pick <commit> = use commit
r, reword <commit> = use commit, but edit the commit message
e, edit <commit> = use commit, but stop for amending
s, squash <commit> = use commit, but meld into previous commit
f, fixup <commit> = like "squash", but discard this commit's log message
x, exec <command> = run command (the rest of the line) using shell
b, break = stop here (continue rebase later with 'git rebase --continue')
d, drop <commit> = remove commit
l, label <label> = label current HEAD with a name
t, reset <label> = reset HEAD to a label
m, merge [-C <commit> | -c <commit>] <label> [# <oneline>]
. create a merge commit using the original merge commit's
. message (or the oneline, if no original merge commit was
. specified). Use -c <commit> to reword the commit message.
These lines can be re-ordered; they are executed from top to bottom.
If you remove a line here THAT COMMIT WILL BE LOST.
However, if you remove everything, the rebase will be aborted.
Note that empty commits are commented out
Now, this can be a little intimidating if you've never performed a rebase before, but it's actually very simple.
To squash these two commits, simply replace the word pick
on the second line with squash
:
pick 1eabc17a8 Add data-attributes for styling checkout
squash 90c58b487 Fix react-dom warning; non-critical, but annoying
Save the file (if you're in Vim, this is done with :wq
), and you'll be dropped into a text editor to write a commit message. When you're happy, save the commit and you're done!
Woohoo! π€ Now you've seen two different ways to squash commits.
You might be wondering why you'd choose to use rebase
when the first method was so easy, that's understandable. Using git rebase
gives you a lot more control over big changes, and I recommend getting comfortable with it for those problems.
4. Get the most recent changes from another branch
When you work with feature branches, this is something you might be doing multiple times a day!
There are two ways to do this too.
The most common method for doing this is the git merge
command. All you have to do with run the git merge
command with the name of the branch you'd like to pull in changes from. For example, if you're getting the most recent changes from master:
git merge master
This creates a new commit, which is referred to as a merge commit. A merge commit is basically just an indication that a merge happened, and there is nothing wrong with having these commits in your git history (in my opinion).
However, some people dislike the presence of merge commits in their repository's history. For those people, you can use git rebase
to catch up with another branch. This is typically how I like to get my feature branches caught up with master:
git rebase master
As long as nothing funky has happened, this will painlessly replay your changes on top of the master branch, catching you up without creating a merge commit.
π€
There's more...
I'm writing a lot of articles these days, I run a podcast, and I've started sending out a newsletter digest about all of the awesome stories I'm hearing.
You can also follow me on Twitter, where I make silly memes and talk about being a developer.
Top comments (4)
Thanks for raising this topic. People typically underestimate git's flexibility.
Let me add few notes.
git add -p
+git commit
may be replaced with singlegit commit -p
. Actually-p
is applicable to most file-level commands likeadd
,commit
,checkout
andstash save
.As for
git rebase master
: if we are talking about "re-apply my comment on most recent code" then it would probably more handygit pull --rebase origin master
: it's like 2-in-1:git fetch
+git rebase
.And it were not noted here but I believe it's also handy:
git log
with keys-S
and-G
may really help to find out "when it has been introduced?" or "where it has been removed?".annotate
works on per-line basis that is not granular enough.Another thing is
git bisect
: once you have some shell command that fails or succeeds and you'd like to find out commit that "broke things" it's fast way to go.For long, I was merge-averse on feature branches, and used rebase too. But it gets messy once others start interacting with your branches, since after the rebase you need a forced push. That ruins others' state.
So recently I stick with merge commits as you advise.
If you only rebase, no modification to the commits, everyone can rebase to stay up-to-date. Merge conflicts still happen, but git will understand you already have other commits.
But yes, a shared feature branch should be treated as a shared branch.
I love
git commit --amend
Thanks