HACKER Q&A
📣 nineplay

What is your Git commit/push flow?


I've long been in the practice of "commit early, commit often". If one use case works I commit, if the unit tests pass I commit. The code may be a mess, the variables may have names like 'foo' and 'bar' but I commit to have a last known good state. If I start mass refactoring and break the unit tests, I can revert everything and start over.

I also push often because I'm forever aware disks can fail. I'm not leaving a day's worth of work on my local drive and hoping it's there the next morning.

I've become increasingly aware that my coworkers have nice clean commit histories. When I look at their PRs, there are 2-4 commits and each is a clean, completely functioning feature. No "fix misspellings and whitespace" comments.

What flow do you follow?


  👤 jawns Accepted Answer ✓
Here's how I address this problem.

When I'm developing, but before I create a PR, I'll create a bunch of stream-of-consciousness commits. This is stuff like "Fix typo" or "Minor formatting changes" mixed in with actual functional changes.

Right before I create the PR, or push up a shared branch, I do an interactive rebase (git rebase -i).

This allows me to organize my commits. I can squash commits, amend commits, move commits around, rewrite the commit messages, etc.

Eventually I end up with the 2-4 clean commits that your coworkers have. Often I design my commits around "cherry-pick" suitability. The commit might not be able to stand on its own in a PR, but does it represent some reasonably contained portion of the work that could be cherry-picked onto another branch if needed?

Granted, all of the advice above requires you to adhere to a "prefer rebase over merge" workflow, and that has some potential pitfalls, e.g. you need to be aware of the Golden Rule of Rebasing:

https://www.atlassian.com/git/tutorials/merging-vs-rebasing#...

But I vastly prefer this workflow to both "merge only," where you can never get rid of those stream-of-consciousness commits, and "squash everything," where every PR ends up with a single commit, even if it would be more useful to have multiple commits that could be potentially cherry-picked.


👤 dimtion
The most important git feature I discovered was `git add -p`, this allows both to select which patchs to stage, but also to do a review of what you are going to stage. Combined with `git commit -v`, this allows you to have plenty of occasions to review your changes before creating your pull request.

Shameless plug, but here are other efficiency tips I wrote about, for working in a high demanding environment: https://dimtion.fr/blog/average-engineer-tips/


👤 bjourne
Here is my last five commit messages: "width*height is area", "fixing stuff i broke", "rm properties we dont need", "rm more useless attributes", "nicer figures". I do my best to keep the code base as clean as possible, but I couldn't care less about keeping the commit history pretty. Any time spent on prettifying git history is better spent on documenting the existing* code imo.

👤 wildpeaks
When multiple people work in the repo, I like Squash Merge in Github best because you can still do small commits in your feature branch, and when you merge, it generates a message from all commits messages (so there is still a trace of the process, but you can get rid of noise like "fixed a typo" with the benefit of hindsight) and history looks clean because it's merged as a single commit, no rebase footgun to worry about.

https://docs.github.com/en/pull-requests/collaborating-with-...


👤 lmarcos
I used to do exactly what you described a few years ago (when I was learning git for the first time). Not anymore. A few reasons:

- commit early/commit often. I usually push one commit when I think the feature is done. While others do a review of my code, I commit to improve the code/fix the issues found by others. The advantage here is that future readers looking at the history of file X line N can know what other files were introduced alongside file X (as a reader of big codebases, this is a nice side effect). I don't like hiding defects either from the git history (one could in theory squash all the commits of a given PR in order to keep the "history clean"... In my experience having a trace of bugs fixed at PR time, or other subtle details is also worth it and serves as documentation of what not to do).

In the cases I need to work through many days in a single feature, and only if the feature is so complicated/critical than I cannot reproduce it from scratch by myself again, then yes I push the progress upstream. This is usually not the case though: I stash progress. I tend to open small PR and usually I remember what I've done (so I could write the entire code again easily). Plus, hard drives fail, sure but they are also quite reliable. In 20 years of work I never experienced losing "non critical" work because of disk failure (for critical work, I for sure have a different workflow).


👤 deckard1
The flow you use will typically depend on the company you are working for. Using regular git (no Github/Gitlab) will often have a different workflow than Github.

My team (and myself) prefer this workflow:

- One commit per PR. This allows for easy reverts and cherry-pick.

- One developer per branch. You can do a few devs per branch, but rebases need to be coordinated and carefully handled, because:

- No merge commits. Only rebase onto latest main. Which means force-pushing PR branches and, thus, rewriting history (other devs working on same branch need to be aware that history has changed).

If you're constantly rebasing onto main, then all of your working commits sit on top of the latest code in main. Which means you do not have to deal with tricky merge conflicts, where your commits may weave in and out of the main branch at various points in time because you were doing "git merge" at random points. In addition, if you squash your commits before doing a rebase this will also make merge conflicts rather trivial, because you're only dealing with one set of merge conflicts on one commit.

That's the big picture, team workflow. For my personal workflow, I rely on "git add -p" and stashes. The only time I do a commit and push up code is: a) when I have a large change and want to make sure I don't lose it or b) others have already reviewed my PR and I want to keep the new changes separate to make their life easier when reviewing a 2nd time. I use "git reset --soft HEAD~" to squash instead of "git rebase -i" because I find it easier and quicker.

I must emphasize this point: learn "git add -p". It's extremely useful in the case where you have some changes like debugging code or random unrelated changes that you do not want to commit. It's a filtering mechanism.


👤 izietto
My flow is:

1.a. a lot of dirty commits/wip commits

1.b. a few of clean commits, when I spot changes that I know they are already a commit by itself

Before opening the PR:

2. `git log -p`: I inspect the commits I've done and I decide what should go together, what should be edited and what can stay as it is

3. `git rebase -i`: I apply the changes I've decided during 2

4. repeat 2 and 3 until I'm happy with the results

5. the last `git rebase -i`: reword almost every commit, as almost all the commits at this point have placeholder descriptions

I'm very happy with this strategy. It requires some time to get used to it but at the end my PRs are very clean and well-thought.


👤 timongollum
If you are afraid to lose what you've done, you can stash your minor changes to keep track of the small things that you already got working, and then, when you got enough working to make a commit, you do it with the best code you got. And for the many pushes, you could backup your projects files everyday. I think is a much more appropriate way to use the tools that git gives us.

That way, you won't be afraid to lose your recent work by messing something up, because you have the stash, and won't be afraid to lose your whole project/progress because you have a recente backup of it.

For instance, I have a backup script that runs everytime I shutdown my work computer so I won't have to worry if suddenly my hard drive gives up on everything.


👤 jmchuster
Create a feature branch for everything i work on. Then squash all commits on that branch for merge. If that doesn't look clean because there are too many features munged together, then split that feature branch into multiple ones (ideally would have already realized this ahead of time), then squash and merge each of those. So, then the main branch commit history is only "clean, completely functioning features" and then a link back to the PR that then has the full history of commits that got squashed, if you wanted more insight into the development process. The github UI simplifies a lot of this process and makes it a lot nicer to view.

👤 immutology
Squash-merge PRs. You can configure this in GitLab, GitHub, and Azure DevOps. Your private commits can be whatever you want and they get rolled up to a single PR commit when your working branch is merged to trunk.

👤 jedimastert
The git history on my personal/dev-machine are an absolute mess that I try to optimize for `git bisect`, so preferably each commit should compile (and I usually commit whenever a new part of the requirements is introduced/a new test passes)

The way our code review system works is essentially each upload is a commit, so the review's history is like a compressed version of the local git history. I usually upload when I think the branch is "complete"/will pass all of the CI, and ~once per round of comments.

The main git history is squash-and-rebase, so one commit per code-review, with the CR description serving as the commit message (with automatic links to bugs and code reviews and the like).

I personally like the squash-and-merge strategy for the main branch, it makes the most sense (especially when the code review's history is still around after)


👤 ghotli
I barely skimmed what's below so maybe someone else has covered this, maybe not.

If I need to hack on a task and I know I'm the only person that's going to work on the branch I amend changes to my commit basically constantly. The only reason I might not is if I want multiple commits on the branch to make review simpler for others.

```

git fetch && git checkout main

git branch feature/JIRA-###/words-describing-it

git checkout feature/JIRA-###/words-describing-it

git add .

git commit -m "JIRA-###: words / description"

git push -u origin feature/JIRA-###/words- describing-it

git add .

git amend

git push -f

```

`git amend` is an alias in my ~/.gitconfig

```

#cat ~/.gitconfig

[alias]

        amend = commit --amend --no-edit
```

So most of my commits I just amend the first commit I made, force push, review CI build results, rinse repeat until it's ready for review.

Simplest thing that works when it's just my own work on a branch. I'll also do a rebase pre PR review if need be.

```

git fetch && git rebase -i origin/main

git status

git add

git rebase --continue

git push -f

```


👤 pionar
I do the same, but before pushing, I squash the commits to remove the small intermediate commits. That may be what your coworkers are doing as well.

👤 teeray
I often commit as well to checkpoint, and push those onto a branch organized under my username as a backup (/ to make use of ref folders). When I’m done, I use interactive rebase to clean the branch up, writing meaningful commit messages for each unit of change (answering the question “why does this commit exist?” usually). I force push that cleaned up branch and then open a PR.

My view is that final commits are a form of communication, and deserve some intention. I’ve thanked myself when I’ve looked years back at work I’ve done and been able to figure out not only the change, but also my own state of mind.


👤 TameAntelope
At the end you can just do an interactive rebase and squish the commits away that don't clearly explain what functionality you've added.

I believe you can also configure your PR software to attempt to squish for you? I'm not sure about that, but I think I've seen an option in the settings somewhere.


👤 jdc0589
I see no value in shorter "clean" commit histories in a feature branch. In fact, if you need that, it might imply that the PR is too large to look at holistically, which is a red-flag.

Half my PR's probably have a commit that just says "mashed potatoes", or 5 commits in a row that say "maybe it works now?". It doesn't matter, its going to get rebased/squashed to a single commit in the merge process anyway (which IMO everyone should be doing, true merges from feature branches to mainline are bad).


👤 CogitoCogito
I usually squash my branches to single commits in the end and write a nice message then. There are times when I don’t do this. For example, yesterday I was moving existing code to a separate project and I first committed the original version and then made necessary changes in my next commit. This made it clear where things differed from the original.

Overall I think commits that land in main should be worthy of landing in main. Random crap you tried isn’t important. Larger self-contained changes are.


👤 kazinator
I often have several "topics" going on in the same branch, for which there are existing (unpublished) commits.

When I make some small change, it usually belongs to one of these topics. If it's the topmost commit, of course it can be combined into it with "git commit --amend --patch".

If the small change for one of the commits behind that one, then I make fixup commits with "git commit --fixup " or "git commit --fixup HEAD^^^" (as many carets as needed to refer to the commit I want).

If the little fix requires an addition to the commit message, then --squash instead of fixup.

These little fixup/squash commits can then be squashed into their target commits using "git rebase -i --autosquash".

That may be how some of your coworkers have clean histories.

In some environments, a change made up of numerous little commits like "fix typo" wouldn't pass peer review ; you're supposed to know ho to squash things together (but not too much so that topics get inappropriately combined).

Some shops have a policy that every commit has to build and possibly also pass the test cases. So you can't have an "oops, add missing semicolon" commit; its parent wouldn't build.

Commits should be like Stack Overflow answers: "is this useful to future visitors of this git history?"


👤 kraftman
Pretty similar to you, I commit as often as possible, but I also push whenever I commit so that its backed up. Then once its ready for review, I do an interactive rebase to try and group the commits into useful chunks for the people reviewing, and to improve the commit messages.

Git is crazy complicated under the hood which is awesome when you mess up and need to recover something, but in general my goal is to use as few git commands as possible and keep everything as clean as possible.


👤 meowface
>I also push often because I'm forever aware disks can fail. I'm not leaving a day's worth of work on my local drive and hoping it's there the next morning.

I separate file backup and version control. I keep every git repository I'm working on in Dropbox, and don't ever worry about how often I'm committing and pushing. I don't think git should be considered a substitute for a proper continuous backup system.


👤 crnkofe
The n1 rule I follow (and push everyone to follow) is: "Don't write shitty commit messages." It helps in general with everything related to Git.

Otherwise I decide ahead of time and focus on one area at a time that I know is a small chunk of functionality (like adding boilerplate for a small script, unit testing a piece of functionality, implementing an endpoint) and as long as I'm working on it I just use git add -A ; git commit --amend --no-edit . Once I move on to new self-contained batch of functionality I make a new commit and repeat the amending. It requires a bit of discipline to keep commits small enough but I like it since it's an easy process to follow.

Before I make a review request I usually do an interactive rebase and reword commit messages, sometimes reorder them and squash small stuff if it makes sense. After a review is in process I generally no longer rebase (to keep review transparent) and commit each fix separately.


👤 michaelsalim
I commit as soon as a single functionality makes sense as its own. Simple example: If function X uses function A, B and C. I'll commit after finishing A, B, or C. And once X is then, I create a separate commit. I also ensure the code still runs most of the time. How I group them depends on how complicated it is. The more complex, the more commits.

This helps me write code that are more standalone (easier to replace & test). Also helps to clear the cognitive burden when developing larger features. Anything that's committed are pretty much "done". So if I leave any work mid-way, I can simply look at the untracked files to know where I was at.

Then at the end of the work/PR, I go through every changes to make sure everything makes sense.

In practice, this means that the first code I write is often the last I commit (see my simple example above).


👤 bsimpson
I have three branches: macbook, imac, and develop.

develop is the mainline branch that other people could pull from.

macbook and imac are each backups of their respective machines. I use git merge --ff-only to rebase commits from one to the other when I change machines. If I need to change machines midway through a commit, I'll push a WIP commit to imac, fastforward macbook to match, and then git reset HEAD^ to keep the WIP files (but undo the commit) on macbook. I finish on macbook, make the commit, and push it to origin's macbook and develop.

Like the others here, I make liberal use of git rebase -i to reorder commits, etc. If imac and macbook ever diverge (e.g. I forgot to push a commit before changing machines), I also use interactive rebase to resolve it.


👤 spookyuser
git commit -m "Initial commit"

git commit -m "Add some stuff"

git commit -m "progress"

git commit -m "Add library"

git commit -m "Drop library"

git commit -m "Let's try this library"

git commit -m "Slider is working"

git commit -m "Fixing transparency issue"

git commit -m "Fixed"

git commit -m "I hate you"


👤 btschaegg
Not entirely unlike yours, but I also very often use `git commit --fixup` and let the computer clean up large portions of my messes with `git rebase -i --autosquash` later.

That is, nowadays I usually do the same, but through Git Fork [1] (via its custom command system), and it works even better than the terminal interface for me -- which doesn't happen often. But for fixups the "find the target commit in the history and use a context menu entry to flag your commit flow" really works rather nicely.

Also, Fork's interactive rebasing dialog does `--autosquash` by default.

[1]: https://git-fork.com


👤 throwawayboise
All I use in git is clone, pull, commit, push. I do not use feature branches. The only branches I use are for releases that need to be maintained separately from master.

I absolutely do not care if there are "fix typo" or WIP commits in the history. The amount of effort it takes to get a "clean" commit history has negative payback in my experience.

It should be said I am a lone developer for the most part, sometimes working with one or two other people. So Git in fact is overkill for that scenario. Subversion would do just as well and in fact I prefer it or mercurial.

In a large project such as Git is meant for, my usage probably doesn't apply.


👤 keehun
I do exactly as you say: commit early and commit often. However, something I find quite successful when publishing a Pull Request (aka CR/MR), especially large ones, is that I'll use git rebase to group chunk diffs together in a logical flow to create an easy-to-review set of commits.

If I have 5 commits that are all whitespace changes, I'll use git rebase to group them into one commit before I push.

If it's a large, complex PR, then I'll reorder my commits and pick-and-choose chunks from the many commits so that they make sense in a progression. For example, I might just add one new component in the first commit. Add a second component in the second commit. Third commit might be integrating those two components into the system.

If my PR consists of both "cleanup" like running black on python, then I'll make that its own commit instead of littering actual functional changes within cosmetic ones.

I try to make it as easy as possible for my reviewers to review my code and actually catch mistakes. I also tell the reviewers to look at the commits separately and not to look at the entire diff which can sometimes be hundreds of thousands of lines of code. Even a large PR with 500+ lines of changes can be easy to review if you structure the commits right. (It's really like many PRs within one PR.)

You may ask, why not have separate PRs then for each commit? Sometimes, that makes sense when it's so large that it's worth having an integration branch that lasts weeks or months. Sometimes, it's not possible to just merge that one commit due to problems it'll cause with compilation or linting or other issues. Also, with separate PRs, it can be more costly (in both CICD resources and the dev's time) to do automated testing on each commit separately.

With some practice, the git rebasing and reordering or restructuring commits is not as difficult as it sounds, especially with the aid of a good git GUI (I like Fork.app) that lets me choose exactly the chunk to commit from a particular diff.

Once I got really comfortable with this workflow, what I'll actually starting doing is... when I'm done with something and ready to put up a PR, I'll flatten (fixup) all my commits into one, and then I'll revert it and commit that. Then I revert the revert but instead of committing that revert-the-revert, I'll stage it. This gives me all of my changes in a staged state. Then I'll pick-and-choose chunks from that entire diff to craft a "narrative" like I was describing before. Once I have everything in the right order, I'll drop the first two commits to get of the initial and revert commits.


👤 notapenny
I commit as clean as possible, each one should be a functioning feature or a part of it. I try to do small ones, so generally they'll just be part of a feature I have on my todo list for the entire feature. That actually helps me to mentally move on as well. If I need to rename some stuff I might squash commits, but no wip commits in the actual history.

End of day I usually commit a "temp" commit with a few comments to myself and push that to the remote branch, revert that the next day and force push over it for the next commit.


👤 rendall
I say, you have it right and your coworkers are less so. Encourage your coworkers to be secure in their work. Show your struggles, false starts, mistakes, typo fixes. Embrace the messy history!

My last few teams' workflows and projects were such that commit history wasn't really a big deal. I'm skeptical that it matters if the dev-to-production process is smooth.

These tips are habits we have:

Keep commit message first line to under 50 characters, use imperative mood, capitalized. If you need more than that to describe what's going on then the commit might be too big.

PRs must pass all continuous integration tests + 1 or 2 devs approval. Pretty much always do what a dev suggests in review, even if you disagree.

Optionally, squash merge PRs to master, to make reverting easier. I prefer that.

If a feature is very complicated, break it down into smaller tasks, merging each to master when completed.

Maybe use a feature branch for super complicated inextricably tasks, but doing that too often is a code smell.

In that situation, code history isn't really that important.

That said, if you're still keen, look up these commands:

  git commit --fixup {commit id}
  git reset --soft {something}
  git rebase -i --autosquash {commit id}
Lots of force pushing, will be needed, but never, ever force push to a shared branch unless every one agrees and is aware.

Those can straighten out your history right out.


👤 Drdrdrq
I try to make clean commits that do one thing only. If I have trouble writing a meaningful commit message, it means I have failed and should do a better job next time.

That said, I often make a messy "wip" commit that I push to my branch, just so that the work doesn't get lost. But I always undo such a commit and clean it up.

Also, I always use git add -p, so that I can break changes into multiple meaningful commits and review them one more time before pushing.


👤 chaitanya
You can have the best of both worlds -- "commit early, commit often", and "nice clean commit histories" -- with git, and it is easier than most people think.

- So you start work on a new branch, and reach a checkpoint. Create a new commit with "git commit".

- Continue working, and when you reach the next checkpoint, create another commit. But this time, use "git commit --amend". Contrary to what the flag says, this doesn't modify the previous commit, instead it modifies your commit history and replaces the last commit with a new one[1]. So instead of having two new commits in your branch's history, you only have one.

- Repeat the process until you have something worth pushing to the remote.

- Once you've pushed to remote, remember to not make any further modifications to your git history[2] i.e. going forward, create a new commit and only then run "git commit --amend".

Now, there's an obvious question here: if "git commit --amend" keeps replacing the last commit with a new one, what happens when you mess things up and want to revert to NOT the last checkpoint, but some checkpoint before that?

The trick here that git has up its sleeve is called "git reflog". You see, while "git commit --amend" replaces the last commit in your branch's history with a new one, the older commit is not actually gone, its still there. And you can see all of them with "git reflog". Basically "git reflog" returns every commit at which the local HEAD (i.e. the commit checked out in your working directory) has been. So none of the commits that you replaced with --amend are actually lost, and you can find them all in the reflog.

Restoring to an older checkpoint becomes as easy as running "git reset --hard ", or if you want to play safe, "git checkout -b ".

Hope this helps!

Notes:

1. By design, a commit, or in fact most objects in git cannot be modified. They are immutable.

2. Basically you can play with git history as long as it is private to you and not shared with others. But the moment you push it to a remote you share with others it's no longer private and you must not modify that history anymore. Read Linus's note on keeping a clean git history: https://www.mail-archive.com/dri-devel@lists.sourceforge.net...


👤 yodsanklai
I commit often (as long as it compiles, pass the linter, and fast unit tests if there exist), then I rewrite the history. If I work alone on the feature (which is most case) I also push quite often on a private branch (mostly for backup, and for running the CI). If needed, I also rewrite the history of my private branch and `push -f` (even though this isn't ideal...).

My PRs are clean, each commit pass the CI and is its own thing.


👤 nikivi
I use Sublime Merge (https://www.sublimemerge.com) for all git commands. For automating commits on things like curated lists / wikis / .. I use gitupdate (https://github.com/nikitavoloboev/gitupdate)

👤 smcameron
I use stgit[1] which makes it easy to polish patches (commits), re-order patches, format and email patches, etc. It also makes it very easy to keep a lot of balls in the air at once, working on lots of commits all at the same time while keeping them reasonably separated from one another. I've had maybe 50 to 100 patches going at once from time to time (but usually more like 5 to 10). Then there are guys like Andrew Morton who's known to juggle 1000s of patches at a time using ancient home grown scripts that are the primordial ancestor of quilt[2] and stgit.

[1] https://stacked-git.github.io/ [2] https://en.wikipedia.org/wiki/Quilt_(software)

Edit: forgot to mention, once I'm happy with a given patch/commit, or series of patches/commits, I then cherry-pick or merge them from my patches branch over to the "real" branch, and then push them.


👤 ed-209
It works, I stage (git add), I refactor, it breaks, I checkout (preserves staged work), refactor again, commit push.

Also a fan of numerous small commits - makes good documentation and elimates wasted time untangling hours of thought.

And when I'm reviewing the last thing I want is for someone to waste time doing me the favor of burying their journey into some "perfect" mega-commit


👤 nickm12
I've read a fair number of the top level comments and I don't see anyone who develops like me, so I'll throw my hat into the ring. I'll start by saying that I've always worked at companies where the norm is trunk-based development, where the norm is to develop small changes (< 500loc, typically < 100loc), get them reviewed, and land them as a single commit with a nice message on the tip of the trunk branch.

The way I build these commits is to check out the main branch and start coding. As I write code/tests/documentation I start staging (git add) and committing (git commit) into a single commit. Subsequent commits are "git commit --amend". When I commit I usually update the commit message with any details about the stuff I'm adding.

Changes in the commit I'm pretty sure are going to be in the CR, staged changes I'm not sure about, and unstaged changes are "what I'm working on now". This way, "git diff" shows me what I'm working now (in case I'm interrupted) and "git show" shows me what I'm ready to push. If I want to sync with the trunk, I can do "git stash && git rebase && git stash pop", which mostly works well though sometimes there are merge conflicts that need resolving.

When I'm ready for review, I rebase my single commit, rerun any tests, do a git show and review the change top to bottom, then create the code review. Then I review the change again in the code review tool, where I quite frequently find silly things I missed like typos in documentation of print statements I forgot to remove. Then add reviewers.

The only downside to this I have is when I want to back out changes I've already committed. Git doesn't give you a great way to do this, but I've written an alias that can remove all changes to a file from the commit at the tip of the branch. I use this a couple of times a year.


👤 mattwad
I squash all commits in a PR as a clean commit. I don't see any benefit to knowing when or what someone did other the past few weeks to get to the point we're updating the main branch, and i don't want them to waste time either. If your PR is so large you need to review it as individual commits, maybe your PRs are too big.

👤 WorldMaker
My opinion is that the time for clean commits is the merge commits created by PRs. Git uses a two dimensional commit data structure and most git commands today take a --first-parent argument that gives you a smart, clean view of just the merge commits. It's only a bit of config work to make those your default aliases, and I just wish more UI tools defaulted to something more like it.

I don't necessarily care how "messy" the commits inside a PR are at that point if my default view is at the merge commit/PR level.

Teaching your coworkers how to use --first-parent may be beneficial to everyone and less stress than worrying about the peer pressure to conform to smaller "cleaner" commits.

That said, making cleaner commits is sometimes a useful skillset to learn for yourself. Sure, it can make code reviews easier, but it's advanced skills that take practice and most PR tools aren't particularly good at reviewing a commit at a time anyway so most PR reviews are entire PR at a time. I use tools like git add -p and git rebase --interactive as I feel necessary to clean up a commit narrative, but I also have a strong understanding of when not to force push and have years of "training" in these commands. I don't expect and don't want to expect junior devs to use them. I'm happy with the --first-parent approach to the DAG. I'm also always happy to teach junior devs how to use git add -p and git rebase --interactive, but when it is for them, when it is they who want to improve their skills.

A semi-related thing here is that the other way to remove a lot of "fix misspellings and whitespace" commits is to automate them away. Standardized formatters like prettier remove most of the manual effort of whitespace management. They can be setup to format on save in editors like VS Code. They can be added to pre-commit hooks. Similarly, many misspelling problems (certainly not all) get automated away with type systems and linters.


👤 shoo
always use `git add -p`. review each individual hunk before staging it into commit. dont blindly add everything.

once you've got it working, do a second pass on the commit history and compress it into one or more coherent logical commits, reordering or squashing changes as necessary. learn the interactive rebase interface. lots of operations of the form `git rebase -i HEAD^^^^^^`

where necessary, if you need to erase your old messy history from a feature branch and overwrite it with your tidied up commits, `git push --force-with-lease some-remote some-feature-branch`. in general, avoid using `--force`, and if you ever believe you need `--force`, prefer `--force-with-lease` . the latter avoids some failure modes related to race conditions where a coworker pushes some commits and your concurrent `--force` push that lands a few seconds later blows them all away.


👤 globular-toast
Rebase is the key.

There are two types of commit: checkpoints and versions.

Checkpoints are just you randomly deciding to "save" your progress. They don't necessarily correspond to completely working versions. The commit messages can be completely inappropriate for sharing but only make sense to you.

Versions are fully working copies of the program that could be checked out, run, released etc. These are what you present to your team and what gets merged in the end.

Sometimes you can write a new version straight away but other times you'll generate checkpoints first. Then you use rebase to get to versions. Use rebase as a way to make it look you are a superhuman writing perfect commits first time.

Getting good at rebasing takes some practice and pretty good understanding of git's data model. Learn this. And learn how to use --fixup, --squash and --autosquash to make your life easier.


👤 camnora
I tend to keep my PRs small enough to where several can be submitted within a day. This being said, I tend to keep my changes un-staged.

When I'm ready to commit:

1. `git diff` to get an overall picture of what changes were made. Which parts of this diff can be packaged into an isolated commit?

2. `git add -p` This is where I selectively stage bits.

3. `git diff --cached` to verify that the staged items are all in place

4. `git commit` with a detailed message.

5. Repeat steps 1-4 until all changes have been committed.

6. `git fetch origin main && git rebase origin/main`

7. Finally, `git push`

When PR feedback is left by peers, some teammates prefer you to not rewrite commits and force push. This makes re-review easier for them (especially if you use the Github features around PR review).

I opt for rewriting commits if it's okay with team members. This way you don't have "fix typo" commits getting merged into the main branch.

Edit: formatting


👤 _se
I think part of it depends on the tooling that you're using and what your review system supports. Assuming that these are all supported, I would generally:

1. Push very often, essentially every time I'm going to context switch or take a break

2. Iterate often, get things working, then clean them up, pushing at each step along the way

3. Interactive rebase to squash into meaningful stacked commits for review

4. Mark the change as ready for review by my team

I have found stacked commits to be the best way to both perform and author reviews by a wide margin. It's so much easier to review 4 200 line semantic changes than a single 800 line change. You'll get better feedback from others, and thinking about development in this way can also lead to better results on its own.


👤 SAI_Peregrinus
Commit often, then interactive rebase, then push to GitHub. Then code review happens, and any comments get addressed with some extra commits.

The annoying thing is that often someone will change CI for some unrelated subsystem (we're in a monorepo) and the way to fix that is "merge master into your branch". Of course I could rebase on top of master before pushing, and I do, but that doesn't work well if the CI change happens while my PR is in review. Then I have to merge master into the PR branch, since people do check branches out to review them sometimes.

Thankfully we squash PRs before merge, so it only ends up with a single commit in Master, but it does mean there's sometimes an intermediate mess.


👤 rootusrootus
I follow your flow, and I don't bother squashing my commits. So the repo history has lots of dumb little commits. We use a branch model for features, though, so I can just filter for those merge commits if I want to see granularity at the feature level.

👤 ffwacom
How I was taught:

A branch for each feature.

Each commit should be a logical segment of work, such that it tells a story of how the feature came to be from the different aspects of the system that needed to be modified.

The branch will NOT be a history of how it was actually programmed.

If master or another dependent branch changes, rebase the feature branch onto the dependent commits to keep merge conflicts under control as the branch is being developed.

The branch will be rebased with commits squished and fixed as many times as needed (often 100's in a long running feature), with the end effect being such that the branch and commit history will look like divine providence.

Took some hazing and months to learn but absolutely worth it IMO, and I can't go back now :)


👤 bern4444
I do the same as you - commit early and often, push nearly on every or every few commits, but I try and write my commit messages nicely even (and especially) if the code behind them is not so clean.

I care less about the code quality during development, so long as the commits make sense and the end PR is clean. Commits that may have placeholder names, disorganized code could include:

"fix: failing test case" "feat: support new user flow" "cleanup: variable names and helper functions" "cleanup: organize files" etc

If the commit messages are clear as to why the change was committed, reviewing is a lot easier even with less than ideal code along the way. So long as the end state is cleaned up.


👤 gabereiser
I got conditioned to commit early and commit often for much of the same reasons, hardware failures while dealing with code that touched hardware. GPU’s mainly. So I would commit and push to have CI do checks while I pushed more code changes so that I could reduce the cycle.

A few years ago, Acme Security Corp decided to use my commit frequency as an excuse to say “I didn’t know how to code…” and let me go. So really it’s all over the spectrum and the correct answer is, whatever your team is doing.

If they are holding back commits for uber PR’s, well, so should you. Are they in a commit frenzy and everyone PR’s all day long? So should you.


👤 setgree
My main git case is idiosyncratic -- I use blogdown [0] to host my personal blog, which means I am first pushing the content to GitHub and then it gets built/deployed on Netlify.

I typically commit once at first as a pretty complete draft, and then every subsequent edit/revision is a commit and rebuild. This comes to 20-30 commits per post, and some are indeed like 'fixed a spelling error'.

For this case, the benefits of being a frequent commiter clearly outweigh the costs.

[0] https://bookdown.org/yihui/blogdown/


👤 floodle
I don't use commits as "atoms" of the work. Instead, I work at a PR level. I make PRs small and often, and try to make each one a small, self-contained piece of work, usually not more than a day or two.

Practically this means that I commit and push whenever I feel like it, but then always squash the PR. At that point I create a clean and informative commit message.

However, I leave a lot of context and information in GitHub rather than putting it in large commit messages. I rely heavily on GitHub's automatic linking of issues and pull requests for this.


👤 musicmatze
It really struck me to read how many people have a "I don't give a f..."-style workflow with git, especially in the hackernews audience which I always considered to be more on a technical side.

With "I don't give a f..."-style I mean basically the idea that just committing away with some "wip"-style messages and then squash them all together before merging, or squash-merging them. This _kills_ all traceability and all future options to find out what was happening and _why_ (the whole reason git exists)!

In constrast, my workflow:

- Branch off of master

- Develop things, commit cleanly (which sometimes means 10s of lines of commit message for less than 10 lines of change!)

- Before everything goes into review, I revisit each commit. If there has to be formatting done, I create fixups for the individual commits and `--autosquash` them into the PR commits. Sometimes I even do something like `git rebase master -x "cargo fmt && git commit --amend --no-edit -a"` to automatically format each patch in the PR before submitting (or if I just missed to do it).

- Submit PR

- For each review comment, I create a fixup commit. As soon as I addressed all of the comments, I push the commits to the PR

- Repeat the step above until everyone is satisfied

- `git rebase master -i --autosquash` or, if master changed, sometimes even `git rebase $(git merge-base master HEAD) -i --autosquash`

- Wait for the PR to be merged

When a PR is ready, it could be one patch only, but also easily be 50 patches. Depends on the scope and size of the project and the PR of course.

This is my workflow for contribtions, both private and professional ones, but also for my private repositories (whereas "review" here is my own but also simply CI).

When working on projects on github in my free time, I even stopped submitting patches (after discussion of course) if projects use squash-merge, because if I put much thought and careful crafting into my commits and they just squash them anyways, I feel that they don't actually care and so there's no point in contributing for me.

(Edit: Formatting)


👤 jakupovic
Make as many commits as needed to get whatever it is I'm working on "working". Then by default all the commits get merged during Merge Request and we get one commit to master.

👤 dspillett
> The code may be a mess, the variables may have names like 'foo' and 'bar' but I commit to have a last known good state. If I start mass refactoring and break the unit tests, I can revert everything and start over.

For that use case, if you are sure you don't want the intervening history in the main source repository, is to keep your “scratch” commits to a local store. This could be a branch, or a separate repo.

For my work areas I have a timed rsync taking snapshots, and can kick it off manually if I want to make a snapshot for a specific time (just finished debugging a major function, for instance). If there have been no changes since it last run the snapshot is not kept (just a note added to a file to say one was considered at that time). Identical files are all hard-links to the same data so it is pretty space efficient unless you have some large assets that change regularly. This has the advantage that I don't even have to remember to commit to anything: the snapshot is automatically taken regularly. Rolling back is manual, but rarely needs to be done and can be done easily for small parts of the update if the rest needs to stay.

Every now and then, as this is just backing up temporary work status, clear down snapshots older than a given point in time. Using a source control repo and auto-committing to that would work as well as creating filesystem based snapshots like I do I should think, but all my other backups were rsync+snapshot based when I set this up so I just repurposed existing scripts for the job.

> I'm forever aware disks can fail. I'm not leaving a day's worth of work on my local drive and hoping it's there the next morning.

Keep the snapshots on a different drive, or even a different machine (they are mostly in my case, though “just because it happened to get laid out that way” rather than by thoughtful design). Mine are even covered by daily off-site backups so if the place burns down overnight I still don't lose them (or at least absolutely no more than a day's worth). Just be careful with rsync, if you use this method, due to the many hard-links as the --hard-links option can be quite CPU and memory intensive, and if your backups are usually append-only you'll need to occasionally wipe old snapshots that are long since irrelevant.


👤 ragebol
As long as I'm working on my branch, that I work on alone, I tend to rebase and interactively rebase those relatively often. The mantra is you shouldn't rebase public history, but since it's my working branch, I feel I can do whatever I want with it.

Nice thing about small, atomic commits that still pass tests, is it works well with `git bisect` to find issues.

I also split commits defining a function etc from using it in other code. So if I `git reset --hard` because I screwed up some caller, I still have the function.


👤 muzani
My "atomic" level is that it can build. I try to minimise time between commits/pushes because often I work on two different computers. So I don't try to keep it clean, and often cleaning is its own commit.

Android is a little weird in that a build may take 6 min on the production app though. So a lot of code is written and tested on an external repo where it might take 20 sec instead and then copied in when complete. Looking clean/complete isn't the intent but that's how it ends up.


👤 jedberg
I keep my personal dev folder in Dropbox so I don't have to worry about lost work since it always syncs to the cloud. My work dev folder uses syncthing to a remote server for the same purpose.

I make my own branch and then do checkins when I get to good stopping points. Then I do git squash merges to staging or the group branch when I have nice small updates. Keeps the public history clean and also lets me revert back to the previous stopping point, while not being worried about lost work.


👤 moonchrome
I commit when I hut logical milestones. I ammend typos and stuff like that if I catch it early.

The commit all the time is a waste of time for me - my IDE (intellij) has excellent local history.


👤 theshrike79
Create branch, I commit stuff always when my stuff isn't actively broken and I can somehow describe what the change did.

I push often just to be sure. (This is the modern equivalent of constantly hitting Ctrl-S to save).

Squash and clean up before merging to main branch after PR is reviewed and accepted. This makes rolling back the change a lot easier and keeps the main branch log clean from "let's see if this crap works" -style messages.


👤 perlgeek
I commit often, push to branches until I'm satisfied, and for smaller branches often do squash-merges so all of the mess doesn't show up in the history.

Also, I too used "foo" and "bar" as variable names, but stopped doing that. If I find myself using nonsense names it means that my mental model of the problem (or the solution) is too vague. Then I stop and think about it instead of going ahead at full speed.


👤 aurelianito
Commit early. But do not commit garbage. Each commit should be a small but meaningful advance. All tests should pass. If working with compiled code, off course it should compile. No todos. No clunky var or method names. A branch per merge request.

If you do it like this it is great for everyone. Do not squash commits or rebase. Those are antipatterns. We are lying to ourselves when we do it. We cannot learn from history we rewrite.


👤 jmkr
1. Create feature branch

2. Make changes

3. Commit changes at whatever point and write a good commit message about the feature.

4. Commit more.

5. Create feature/branch_rebase

6. Squash all the commits.

7. Push to fork

8. Make pull request

9. Make fixes and push those commits per fix.

10. Squash when merging back to that first commit.

I like having a separate rebase branch because then I have all my work in one branch and the history of what I did. Then I squash it and get rid of the that history, then maybe rebase main back into it if there we changes since I started the feature.


👤 bubbab
Messy WIP commits that are reorganized to cleaner commits later, like most others have said.

However, instead of rebasing, I often git reset to the beginning and recreate the commits from scratch, using partial file commits (or staged hunks). IDEs/editors like VS Code make it really easy to stage an individual part of a file for the commit. The CLI way (git add -p) has always been pretty confusing to me.


👤 AndrewDucker
Work on it, committing whenever I have something working, or it's time to go home for the day.

Merge it as is.

Because nobody is ever ever going to go through my commits "reading a story". What they care about is the final stage of my code. I could squash it all, but those extra commits don't hurt anyone, and the space they take up is neglible, so why not leave history as, well, the actual history?


👤 kstrauser
This made me realize I haven't heard about git-flow[0] in ages. There was an era when it seemed like everyone wanted to use that for everything, even when it didn't make any sense.

[0] https://nvie.com/posts/a-successful-git-branching-model/


👤 seba_dos1
It's really simple - commit early, commit often, push to your own branches/forks/repos where nobody looks but you. When you're done with your changes, squash them into something actually presentable and logically split into self-contained commits (interactive rebase is a wonderful tool), and then push out as a proper merge request.

👤 wry_discontent
For smaller PRs/bugfixes, I'll often just have 1 commit, and that's all it took me to write.

For bigger things, I do stream-of-consciousness, rebasing somewhat often when I get to a place where I like things.

Unfortunately we're all about merging, so that mucks up my commits a bit.

I use Magit to handle all that for me, which is by far the nicest git interface I've ever used.


👤 8note
I generally know what I want commits for before I start writing.

While writing I make my first commit once I've got one file changed, then ammend it as I go.

If I write a whole ton of code/don't plan well in advance, I can end up with a few changes on the go at the same time. I'll tend to reset all the commits at the end and interactively add lines to commits


👤 danielyaa5
I think clean commit history is overrated as long as your squash and merge prs into main. I’ve tried clean commits using rebase and I just don’t think overhead for me personally is worth it. What’s more important is small PRs. And if you can’t do that usually find spending some time reviewing the code with the reviewee is also helpful

👤 ge96
I commit chunks of work, so I can view that functionality/diffs in one segment. Can squash it later. I do try to save "often" in case something happens. I'll just push something up like "progress save".

I've seen some people commit like 40 changes in a PR lol. Happens just squash it.

Can also use another branch


👤 maztaim
git pull default branch. Create local branch. Make change. Commit. Test. Remove quotes. Commit. Test. Add quotes back. Commit. Test. Google quotes. Remove quotes. Commit. Test passes. Rebase to default branch and squash. Push branch and MR. Ignore peer review policy and approve MR. Repeat!

👤 user3939382
I have everything aliased and use few commands:

gb feature/Foo // make a branch called foo from the current (usually develop) and switch to it

// make edits

gac I did a thing // add all files and commit with this message

mduc // Merge current remote develop into this branch

git push

mkpr // Create a draft PR from this branch on github and open my browser to it


👤 amarshall
When forming commits, this is the rule of thumb I have: can the entire diff of a commit be understood and explained, both in content and intent, by its commit message, ideally by someone months or years from now? If not, the commit is not properly “atomic” or the message deficient in some way.

👤 tuckerman
I commit/push to a remote branch often mostly as a backup/easy undo. My commits are squashed for each PR; if I have two commits that I want to stay separate, I send them as separate/chained PRs (I like the idea that each commit in master represents a reviewed point in history).

👤 npteljes
We use feature branches. We commit frequently to those, but squash it in the end when merging to main. We rebase to the latest main to keep the feature branches up to date. This way history is organized (not that anyone ever looked at it), but also integration is frequent.

👤 phkahler
You need to include a step where you squash commits. For me that was the point where I had to learn vi, since "git rebase HEAD~4" will throw you into vi. So then I ran "vim tutor" daily for a week and learned enough to continue using git.

👤 yboris
Before I ever commit anything, I always run diff2html command to view my changes in HTML side-by-side (I even have an alias `diff` that makes it fast and easy).

https://diff2html.xyz/


👤 gxespino
I like to

  git commit --amend --no-edit
often

once a piece of functionality is working, edit the commit so it makes sense

start new commit and work on next piece of functionality, repeat

clean commit history not just for me but for my coworkers who will benefit from seeing a cohesive commit diff


👤 aghilmort
depends on function for me:

- new features tend to be one clean PR since there's so much experimentation on what will work and lots of commits tends to slow things down -- a nominal disk backup is helpful, or even just a commit when taking a break on a branch of your own, no PRs until clean

- refactoring tends to be lots of test / experimental / ugly branches with incremental PRs, since tends to be easier to say, ok, change this one thing

- been experimenting with some third in-between mode, say add X with a bit more of a planning / sprint mindset vs. oh wow, let's see if this works

- feels like this approach is more of works for startups, probably harder to do in enterprise / large teams?


👤 atmosx
Branch out

break down work to logically separated commits (one logical change at a time)

Fixups will eventually come when I change something that should be two commits down.

Make sure commit history makes sense to the reviewer

Explanatory commit messages: the why (this library did not fit the bill because ABC)

Open the PR


👤 RobertRoberts
I do the same as you, get the work done, keep is stable. Being clean and pretty is the _last_ thing I do.

(based on years of experience with errors, failures, data loss, etc...)

edit: But I also now like to be messy in a dev branch, so it matters less in the grand scheme.


👤 burnished
I try to commit whenever I have enough to write a coherent message. I branch out to provide me with an objective and I merge back into main when I accomplish it.

I think I'm going to start using rebase to get those clean commit histories though.


👤 dboreham
Lots of commits. Squash merge the PR (github feature) to clean up the commit history.

👤 smashah
I commit anytime the staged code can have a logical commit message. So I go for the smallest possible change per commit. Push when an issue can be closed/new release can be built or I leave my computer for any reason.

👤 adrianmsmith
> I also push often because I'm forever aware disks can fail.

You could consider using a tool other than git push for protecting your work against disk failures. I rely on Apple Time Machine for that for example.


👤 surfer7837
GitHub/Gitlab allow you to rebase your commits when merging. We do that, but has the disadvantage of mucking up things for people who are using that commit. I personally think it’s worth it

👤 tobyhinloopen
I commit and push often, with no sensible commit messages. The git messages aren’t used, except for issue ids and random garbage.

All details regarding code and reasoning is on the issues and pull requests.


👤 jessikat
I use branches gratuitously, and push to my forks regularly as you do, but I also do interactive rebases and edits when preparing to push to upstream to clean up my mess.

👤 miscaccount
have you looked into squash commit merge.

Basically you do the same workflow as you are doing now, just squash it to a single commit so that wherever is your last commit stays.

if needed you can go to your branch for history

https://stackoverflow.com/questions/5308816/how-can-i-merge-...


👤 whoomp12342
commit early, commit often is really great for beginners. I dont do that. I commit when it makes sense to. Since my commits will get rebase squashed on merge into master, I dont put a ton of effort into doing it perfect.

Reasons I commit 1) it feels right, this was a good logical step 2) ima bout to do something that might break everything like a refactor 3) everything works so it makes sense to before I do something else


👤 contingencies
No branching + messy commits. Exceptions would be forking public projects to contribute, and public project issue resolutions ("fix #XXX").

👤 cosmosgenius
ohmyzsh adds two git alias which I use very often `gwip` and `gunwip`. I push `gwip` commit for work which I am not clear on if to do a clean commit. Once I have settled on a unit of work ( like code + tests + docs ) I use `git add -p` to selectively create clean commits. For "fix misspellings and whitespace" kind of stuff use `git commit --amend --no-edit`.

👤 Macha
I commit chronologically, then depending on the scope of the changes refactor into logical subsets, or just squash into a single commit.

👤 bo1024
It sounds like a main problem is that you don't have your local drive backed up. Can you address that first?

👤 dusted
We're using gerrit and so when I have enough that I consider it worth keeping, I commit it and push it as a new change, I don't put anyone on review. I continue working, and doing commit --amend and upload until I'm done. Then I put people on review on fix whatever they come up with, and do the last commit --amend and push the final change. When QA accepts it I either submit, or rebase (if that is trivial, I submit, otherwise, I repeat the review, then submit when it has +2).

👤 BerislavLopac
Amend and force push are your friends.

👤 nvarsj
I've been introducing https://ejoffe.github.io/spr/ at work and am pretty happy with it. It makes the process of managing stacked PRs really smooth. With that tool, I usually just create a local branch and run git spr up when commits are ready.

👤 rurban
depends on the review and PR policies. most are very strict, so I tune my branch to the expected commits.

with magit of course. GNU even expects proper Changelog entries per commit.

and I always keep on track with rebase, pull --rebase and rerere. everybody hates nonlinear merge trees


👤 kblev
We just squash the commits in Gitlab at a Merge, so the sequential commits won't matter.

👤 blobbers
git rebase -i HEAD~5

Where 5 is some number of commits (or origin tags)

Is your best best friend! Write great messages, squash things, or fix ups. Force push over your branch after you clean up… and send out a great PR!


👤 francisofascii
Create a branch. feature_todo

Do a bunch of commits.

Push branch to remote as needed.

Refactor.

Create a new branch. feature_1

Reset soft back where I started.

Commit a nice, clean change and good message.

Rebase main.

Push.

PR.


👤 worik
I commit when I finish a task.

I push at least once a day


👤 MPSimmons
rebase -i then squash the changes you want to hide

👤 InvOfSmallC
Master only.

👤 EVa5I7bHFq9mnYK
Maybe they commit "misspelings and whitespaces" to their private branches and then commit clean batch to the shared repository?

👤 twofornone
>I also push often because I'm forever aware disks can fail.

In the 20+ years that I've been using computers, and ≈15 or so that I've been writing software, I've never experienced a drive failure.