This is why I love Git (and Vim)! (also, is there a better solution?)
Tags: git, hack, quicknote, storytime
Published: 2020-01-17 07:00:49

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.

1. Extract the changes
2. Remove branch
3. Add new branch
4. Add changes
5. Commit
6. 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:

1. Go to first line and search `pick` by typing `/pick`. Then press Enter.
2. Type `cgn` and type in the new phrase to replace the old. Then press Esc.
3. Press `.` until all the adjacent lines I need are modified.
4. `: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: 

```sh
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. :)