{
  "$type": "site.standard.document",
  "canonicalUrl": "https://rednafi.com/misc/on-rebasing/",
  "description": "Master git rebase for cleaner commit history. Learn interactive rebasing, squashing commits, and rebasing feature branches onto main with practical examples.",
  "path": "/misc/on-rebasing/",
  "publishedAt": "2024-06-18T00:00:00.000Z",
  "site": "at://did:plc:fgtm2c26vfcj74rfmeggbyqj/site.standard.publication/3mnl6f7ob462z",
  "tags": [
    "git",
    "Shell",
    "Unix",
    "GitHub"
  ],
  "textContent": "People tend to get pretty passionate about Git workflows on different online forums. Some\nlike to rebase, while others prefer to keep the disorganized records. Some dislike the extra\nmerge commit, while others love to preserve all the historical artifacts. There's merit to\nboth sides of the discussion. That being said, I kind of like rebasing because I'm a messy\ncommitter who:\n\n- Usually doesn't care for keeping [atomic commits].\n- Creates a lot of short commits with messages like \"fix\" or \"wip\".\n- Likes to clean up the untidy commits before sending the branch for peer review.\n- Prefers a linear history over a forked one so that git log --oneline --graph tells a\n  nice story.\n\nGit rebase allows me to squash my disordered commits into a neat little one, which bundles\nall the changes with passing tests and documentation. Sure, a similar result can be emulated\nusing git merge --squash feat_branch or GitHub's squash-merge feature, but to me, rebasing\nfeels cleaner. Plus, over time, I've subconsciously picked up the tricks to work my way\naround rebase-related gotchas.\n\nJulia Evans explores the [pros and cons of rebasing] in detail. Also, squashing commits is\njust one of the many things that you can do with the rebase command. Here, I just wanted to\ndocument my daily rebasing workflow where I mostly rename, squash, or fixup commits.\n\nA few assumptions\n\nBroadly speaking, there are two common types of rebasing: rebasing a feature branch onto the\nmain branch and interactive rebasing on the feature branch itself. The workflow assumes a\nusual web service development cadence where:\n\n- You'll be working on a feature branch that's forked off of a main branch.\n- The main branch is protected, and you can't directly push your changes to it.\n- Once you're done with your feature work, you'll need to create a pull request against the\n  main branch.\n- After your PR is reviewed and merged onto the main branch, CI automatically deploys it to\n  some staging environment.\n\nI'm aware this approach doesn't work for some niches in software development, but it's the\none I'm most familiar with, so I'll go with it.\n\nRebasing a feature branch onto the main branch\n\nLet's say I want to start working on a new feature. Here's how I usually go about it:\n\n1. Pull in the latest main with git pull.\n2. Fork off a new branch via git switch -c feat_branch.\n3. Do the work in feat_branch, and before sending the PR, do interactive rebasing if\n   necessary, and then rebase the feat_branch onto the latest changes of main with:\n\n    \n\n4. Push the changes to the remote repository with git push origin HEAD and send a PR\n   against main for review.\n\n    Here, ...origin HEAD instructs git to push the current branch that HEAD is pointing\n    to.\n\nThe 3rd step is where I often do interactive rebasing before sending the PR to make my work\npresentable. The next section will explain that in detail.\n\nOccasionally, the 4th step doesn't go as expected, and merge conflicts occur when I run\ngit rebase main from feat_branch. In those cases, I use my editor (VSCode) to fix the\nconflict, add the changes with git add ., and run git rebase --continue. This completes\nthe rebase operation, and we're ready to push it to the remote.\n\nRebasing interactively on the feature branch\n\nThis is an extension of the 3rd step of the previous section. Sometimes, while working on a\nfeature, I quickly make many messy commits and push them to the remote branch. This happens\nquite frequently when I'm prototyping on a feature or updating something regarding GitHub\nActions. In these cases, I tend to make quick changes, commit with a message like \"fix\" or\n\"ci\" and push to remote to see if the CI is passing. However, once I'm done, the commit log\non that branch looks like this:\n\nThis command instructs git to show only the commits that exist on feat_branch but not on\nmain. I learned recently that in git's context, @ indicates the current branch. Neat,\nthis means I won't need to remember the branch name or do a git branch and then copy the\nname of the current branch. Running the command returns:\n\nI'm not too proud of the state of this feat_branch and prefer to tidy things up before\nmaking a PR against main. One common thing I do is squash all these commits into one and\nthen add a proper commit message. Interactive rebasing allows me to do that. Let's say you\nwant to interactively rebase the 5 commits listed above and squash them. To do so, you can\nrun the following command from the feat_branch:\n\nThis will open a file named git-rebase-todo in your default git editor (set via git\nconfig) that looks like this:\n\nNotice that the file has quite a bit of instructions that are commented out. You can perform\nactions like pick, reword, edit, fixup, etc. I usually use squash and edit the\ngit-rebase-todo file like this:\n\nNow, if you close the previous file, git will automatically open another file like the\nfollowing:\n\nAfter the first comment, you can put in the message for all the combined commits:\n\nIf you close this file, you'll see a message on your console indicating that the rebase has\nbeen successful:\n\nNow running git log will show that the messy commit has been squashed into one.\n\nThis displays:\n\nThis is just one of the many things you can do during interactive rebasing. While I do this\nmost commonly, sometimes I also drop unnecessary commits to tidy up things and group\nmultiple commits instead of just squashing everything into one commit. All of these actions\ncan be done in a similar manner to squashing commits as mentioned above.\n\nSometimes, I don't know how many commits I'll need to interactively rebase. In those cases,\nI can get the number of all the new commits on a feature branch by counting the entries in\ngit log as follows:\n\nThen you can use the number from the output of the previous command to rebase n number of\ncommits:\n\nAnother thing you can do is split a single commit into multiple commits. This is quite a bit\nmore involved and I rarely do it during interactive rebasing.\n\nOne last thing I learned recently is that you can run your tests or any arbitrary command\nduring interactive rebasing. To do so, start your rebase session with --exec cmd as\nfollows:\n\nIn the git-rebase-todo file this time, you'll see that the command is run after each\ncommit as follows:\n\nYou can edit this file to run the exec command after any commit you want to. The commands\nwill run once you save and close this file. This is a neat way to run your test suite and\nmake sure they pass in the intermediate commits.\n\nFin!\n\nFurther reading\n\n- [Hackernews discussion on rebasing]\n\n\n\n\n[atomic commits]:\n    https://suchdevblog.com/lessons/AtomicGitCommits.html#why-should-you-write-atomic-git-commits\n\n[pros and cons of rebasing]:\n    https://jvns.ca/blog/2023/11/06/rebasing-what-can-go-wrong-/\n\n[hackernews discussion on rebasing]:\n    https://news.ycombinator.com/item?id=40742628",
  "title": "I kind of like rebasing"
}