This part of the series will be about
Before we proceed any further, I must mention that some of the tricks used here might be dangerous in some cases. For instance, if you’ve already published a branch to the remote repository, using rebase may rewrite the history of the shared branch which can cause a lot of problems. You should always be diligent and take great care before you take action!
Keeping your local branches up to date with the remote ones is mandatory. A live project can change at a really fast pace. The codebase you were familiar with one day, might become mostly obsolete by the next. To get a good overview of what has changed since last time we can check the Log view.
It seems like the project has progressed quite a bit since we last worked on it. However, we might not see the latest updates. We should definitely fetch all the remote branches in case there are any new ones. To fetch all remotes, simply click on the Fetch All Remotes button in the sidebar (it’s the one with the icon).
Now that we have all the branches downloaded, we should check if our local branches are up to date with the remote ones. Let’s compare the master branches! Check out the local branch and then right-click on the remote one and select Show Diff with Working Tree.
We can see which files differ between our two branches. If we’re more interested in the differences between commits than the differences between files, we can right-click on the remote branch and select Compare with Current (in this case let current be our local master branch).
Oops! Looks like our local branch is behind. We should definitely update it. But what if we already have some work in progress that we don’t want to lose? Let’s say we’ve just started working on a new feature but don’t have any commits yet, only a bunch of half-ready solutions. This is a perfect time to get familiar with stashing in IntelliJ!
Let’s take a look at the Local Changes view first to see all our changes! Click on the Local Changes tab in the Git tool window.
Keep checking this view as we’ll stash our changes. Click on VCS → Git → Stash Changes…
We’ll set a stash message and hit the Create Stash button. Let’s take a look at the Local Changes view.
Well, it’s completely empty but fear not, because our changes have been safely tucked away. In fact, we can observe what we have stashed. We only need to open VCS → Git → UnStash Changes… Don’t worry the unstashing won’t take place immediately.
Our stash seems fine but you don’t have to believe me. Just look at the stash content by clicking on View and then select whichever file you’d like and right-click on it and choose Show Diff.
That was a whole lot of explaining and experimenting but let’s not forget what we set out to do: update the local branch while keeping our changes. While our changes are stashed, we can confidently hit the Update Project… button in the top right corner. It doesn’t really matter if we choose to merge or to rebase at the moment, provided we don’t have any commits on our local branch.
Now if we check the differences between our local branch and the remote one, we won’t see any. To do this, use Compare with Current and Show Diff with Working Tree as before.
Awesome, we’re totally in sync with the remote branch. Now, let’s apply our unstashed local changes to the branch! Once more, open VCS → Git → UnStash Changes…
We won’t need this stash furthermore, so check the Pop stash option and then hit the Pop stash button! We can see in the Log view that our changes were applied and the stash is empty under the UnStash Changes dialog.
Let’s take a closer look at the advanced possibilities of stashing in IntelliJ. There’s not much to talk about really, most of the stash features don’t show up in IntelliJ but the more popular ones can be found. First off, there’s the Keep index option when stashing changes.
What keep index does is it stashes all changes in the working directory. At the same time, the changes already staged (added to the staging area) remain in the staging area (also known as the index). Go ahead and right-click on the relevant files and select Git → Add. Now that our files are in the index, we are able to stash them and keep the index.
Hit the Create Stash button and let’s examine our stashes in the UnStash Changes dialog and the changes in the working directory. In the UnStash Changes dialog, click on the related stash and then on View. Right-click on any file and select Show Diff.
The two versions of the file are identical since the stashed changes were also left in the index. We can check this by observing the local changes of the file.
Now, we can see that we definitely have some changes compared to the local repository.
The “keep index” option might come in handy when you’d like to iteratively make small changes to files and save them piece by piece without making a commit. Let’s say we have implemented a feature but then we’re struck by the urge to refactor the code. We could stash our changes with the keep index to save a working version of the feature and start modifying it bit by bit. If we did not succeed in making a better version of the code, we can invalidate all changes in the working directory and apply our stashed changes.
Say we’ve been working all night and day on a new feature and made tons of commits and significant progress on our branch. However, it seems like we were forgetful and missed the rule of always keeping your branch up to date with the remote. Let’s investigate a bit before we take action. First, hit the Fetch All Remotes button in the sidebar of the Log view. Then right-click on the remote branch and select Compare with Current (make sure that the related local branch is checked out).
It seems that the remote branch has some new commits. But what about our local branch? Hit the Swap Branches button to invert the comparing.
Turns out we have a new commit here as well. We should update our local branch but keep our new progress intact. Don’t worry, we’ll easily solve this problem by using the equivalent of the git pull --rebase command in IntelliJ.
Click on the Update Project… button in the top right corner (or under VCS → Update Project) and choose the rebase option.
We can make additional changes now or push our local changes.
Rebasing can be an intimidating experience if you don’t know what you’re doing. It’s a double edged sword because it can be as helpful as destructive if you don’t take care. There’s a great article I’ve read about rebasing and the relation between cherry-pick and rebase. You can find it here: http://hades.github.io/2010/03/git-your-friend-not-foe-vol-4-rebasing/
Sometimes rebasing a branch onto another is a lot harder than in the previous example. If there are conflicting changes between the branches, you should make sure to resolve those conflicts. Let’s take a look at how painful it is to resolve conflicts using only the CLI and a simple text editor.
palfi@Wanari-PC MINGW64 /d/Development/git-in-idea-basics (master) $ git pull --rebase First, rewinding head to replay your work on top of it... Applying: Causing conflicts all around Using index info to reconstruct a base tree... M src/main/scala/com/wanari/git_in_idea_basics/Main.scala Falling back to patching base and 3-way merge... Auto-merging src/main/scala/com/wanari/git_in_idea_basics/Main.scala CONFLICT (content): Merge conflict in src/main/scala/com/wanari/git_in_idea_basics/Main.scala error: Failed to merge in the changes. hint: Use 'git am --show-current-patch' to see the failed patch Patch failed at 0001 Causing conflicts all around Resolve all conflicts manually, mark them as resolved with "git add/rm <conflicted_files>", then run "git rebase --continue". You can instead skip this commit: run "git rebase --skip". To abort and get back to the state before "git rebase", run "git rebase --abort".
The error shows that the conflict is in our Main.scala file. Let’s open that in a notepad.
I know, it looks weird. Fortunately, we only have this one conflict to take care of.
palfi@Wanari-PC MINGW64 /d/Development/git-in-idea-basics (master|REBASE 1/1) $ git add . palfi@Wanari-PC MINGW64 /d/Development/git-in-idea-basics (master|REBASE 1/1) $ git rebase --continue Applying: Causing conflicts all around
That wasn’t hard to do, was it? Of course, but what if we had more conflicts in the file or in other ones? Things could get out of hand real quick. Next, we’ll try resolving conflicts in IntelliJ.
But first, let me make an even bigger mess than before…
Soooooooo. Let’s hit that Update Project… button and use the rebase alternative. As soon as the rebasing bumps into a conflict, the Conflicts window will pop up.
We have a few choices here. We’re not gonna blindly accept any version but rather resolve the conflicts manually. Click the Merge… button.
This GUI is so much more convenient than using a simple text editor. Both versions of the file are on the screen and we can instantly see the end result in the middle. We can choose to add a change by clicking on the arrow button or we can decline it by clicking on the X button.
I’ve chosen to add both the first changes then decline both the second and then add one, decline one. Here is the result:
We can hit the Apply button now. Resolve all other conflicts that may arise and when each of them has been handled, IntelliJ will finish the rebasing.
“To err is human”, as the saying goes. Anyone can make a mistake but to correct that mistake is a skill that has to be learned. Let’s start with something simple.
Sometimes you’d make commits and then realize you’d forgotten to add a file, made a typo in the commit message or anything other than adding a file unintentionally. In this case, you’d be wise to amend said commit.
For botched commit messages, there’s a shortcut. If we take a look at the Log view, we can see that I made a typo in my last commit.
Simply right-click on the latest commit and select the Edit Commit Message… option.
Correct the mistake in the popup and hit the OK button and that’s that.
But what if I forgot to add a file to the commit. Just hit the Commit… button in the top right corner as usual.
Add the missing file, check the Amend commit option and then click on the Commit button. Take a look at the right side of the Log view to make sure we have all the files we need.
Next, we’ll get to know a very powerful Git tool. The interactive rebase has many helpful usages, including:
Changing commit messages in IntelliJ is pretty easy as we’ve seen before, so we won’t waste more time on that.
I’ve already made some commits that need some modifications before finalizing.
The “Refactor length in Vec2” and “Add length to Vec2” commits would be better as one single commit. First, we should reorder them, then squash these commits. Let’s right-click on the “Add length to Vec2” commit and select Interactively Rebase from Here…
We’re gonna move the “Refactor length in Vec2” commit one place above by a simple drag-n-drop action.
Under the Squash options, we’ll choose Fixup. Both of these actions combine the selected commit with the previous one. The only difference is that squash gives us the option to include the commit message of the squashed commits while fixup totally ignores the messages from the squashed commits. Just for clarity, a simple Squash looks like this:
But let’s choose Fixup for now (you can click on the blue Reset button to start with a clean slate).
Let’s hit Start Rebasing.
We were successful.
Let’s say we’ve made a mistake in an earlier commit that we’d like to remove. We’re gonna select the third latest commit on the branch and start an interactive rebase from there.
We’re gonna select the first commit and mark it to edit by clicking on the Stop to Edit button (the one with the pause icon). If you’ve added a commit by mistake, you can mark them to pick, rather than edit by clicking on the Pick button (the one with the undo style icon).
The rebasing will stop at the requested commit and we can edit it now. Take note of the popup notification in the bottom right corner and the yellow (the color might be different for you) HEAD marker in the Log view.
When we’ve made the necessary modifications, we should amend commit the changes to make them final. Push the Commit… button in the top right corner.
Make sure the Amend commit option is checked then click on Commit. Check the Log view for the result.
Click on Continue in the notification and we’re done with the rebase. If you don’t see the notification, opening up the Git Branches view in the bottom right corner will show all the possible rebase actions.
There are times when you’ve accidentally added files to a commit that do not belong there. The most obvious solution is to reset your commit and exclude the relevant files.
Let’s say we have the following files committed from which the Main.scala is unnecessary in this particular commit.
We’ll reset the HEAD to the parent commit and recommit without the Main.scala file. To do this, right-click on the parent commit (Merge pull request #7 in this case) and select Reset Current Branch to Here.
We’d only like to make the commit non-existent but keep the changes. In this case, choose the Soft or the Mixed option (doesn’t matter which one now) then click on Reset.
We can see in the Log view that the commit has vanished but the changes still exist as can be seen in the Local Changes view.
Now, let’s make a simple commit without the Main.scala file.
Uncheck the file, then hit Commit. Take a look at our committed files in the Log view!
Only the desired files are committed and the remaining changes are still in the working directory as the Local Changes view shows us.
If you’ve already published some changes to the remote repository, reseting those commits is not an option to unmake them. Instead, you should opt for a revert commit.
Let’s say we have the following history on one of our branches.
We’d already committed the Add debug printlns to Point3 commit but we’d like to revert it. Right-click on the commit and select Revert commit.
Click on Commit and Push… and Push in the next dialog. The changes have been reverted.
Thanks reading this little blogpost about using Git in IntelliJ. I hope you’ve found the CLI vs IDE comparison convincing and the tricks useful. Stay tuned for future posts!