This is why I love Git (and Vim)! (also, is there a better solution?)
Nir Lanka ニル
Posted on January 17, 2020
Today I was pushing new commits to the remote pull-request branch which already had 5 commits. Then we realized it can't be tested with functionality in our main develop
branch since mine was too behind. Well, without merging from develop
to my-feature
.
The problem was not how to do this easily, as a simple git checkout my-feature; git merge develop;
and a bit of conflict resolution before git add .; git commit
should do the trick; but how to have a cleaner history. i.e. We wanted a single commit that can be tested and merged back to develop
.
And to top that, we wanted to squash all 6 commits in my-feature
into one, for ease of code-review.
Disclaimer: I don't like over-relying on rebase
, since it can be used to change history, which is (1) dishonest, and (2) dangerous. But in this case, we had to do it.
So I got to work of fixing this issue.
Now, I'm not a great Git hacker. I do know the concepts of git and bash and try to optimize the way I do things, but I am far from being able to hack out solutions like this without looking up stuff.
So I thought how I'd manually do this.
- Extract the changes
- Remove branch
- Add new branch
- Add changes
- Commit
- Force push
Fun with squash
(in Vim)
First I squashed the changes (which was redundant, as we later realize) with git rebase -i <hash-last-commit-in-develop-branch>
. At the rebase edit in Vim, I wanted to replace all but the first pick
commands with s
or squash
. I looked up and found cgn
which helps replace search items. I did these steps:
- Go to first line and search
pick
by typing/pick
. Then press Enter. - Type
cgn
and type in the new phrase to replace the old. Then press Esc. - Press
.
until all the adjacent lines I need are modified. :wq
Of course, this was totally unnecessary, since I'm using git diff
later, which I didn't plan at this stage.
This is when I realized I have to delete this new squashed commit and start over! (because I would still have to merge from develop
and it would create a merge commit and would be messy).
Save the changes
I'm not a huge fan of git stash
. I prefer something more tangible and reliable like git diff > ./diff
. Of course I may be ill-informed, but I find git stash
to be pretty limited and superficial, unlike git diff
.
So, to save the changes (which is now only in the current commit), I ran git diff HEAD^ HEAD > ../diffs/my-feature.diff
. This dumps all the changes (file updates, creates, deletes, everything) into a text file.
Re-apply changes
Let's remove the local branch copy: git branch -D my-feature
. We can force push later to replace the remote branch copy.
Now I re-created the branch new from develop:
git checkout develop
git pull
git checkout -b my-feature
Then I applied the diff back: git apply ../diffs/my-feature.diff
. That hit me in the face. Hard.
... lots of lines
error: patch failed: ...
... more lines
Of course, I should've expected this. This has conflicts! Hey, wouldn't it be great if applying a git diff could work like a branch merge? Well, I was in luck, as it turned out to not just be entirely possible, but also quite simple!
git apply ../diffs/my-feature.diff -3
So simple! Just add -3
. Then it applied the diff with conflicts saved the same way git merge
does. All I had to do then was to resolve conflicts, add
, commit
everything, and do git push origin my-feature --force
.
That was all!
So why do I love Git and Vim, again?
Well, it's the crazy simplicity. Everything in Git can be represented as text. And everything, no matter how separate they seem, follow the same rules. Look at how git diff
can work similar to git merge
! They are completely different areas!
Also, Vim. I love Sublime Text. I use it for quick advanced text-edits. I love its multi-cursor functionality. It's crazy. But Vim doesn't need a multi-cursor functionality! Instead, it makes the whole process simpler and more extensible! No limitations of simple GUI apps. You can do so much more with very little knowledge and concepts!
Bonus: my git aliases
~/.gitconfig
- [alias]
-
co = checkouti
br = branch
ci = commit
cm = commit -m
a = add
st = status
unstage = reset HEAD --
last = log -1 HEAD
visual = !gitk
stage = add
df = diff
rb = rebase
undo = reset HEAD~1
redo = reset \"HEAD@{1}\"
amend = commit --amend
I got these aliases from somewhere that I can't find right now. Sorry about not crediting the original writer. Thanks!
Thanks for reading through this ramble! I hope it had some exciting new stuff that would help you. I may have been silly in how I did this, in which case I'd love to hear about how you'd solve it. :)
Posted on January 17, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.