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?
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.
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/
https://docs.github.com/en/pull-requests/collaborating-with-...
- 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).
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~ 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.
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.
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.
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)
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] 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 ```
``` amend = commit --amend --no-edit
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.
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.
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).
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.
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 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?"
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.
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.
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.
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).
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.
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"
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
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.
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.
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.
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.
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.
- 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 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...
My PRs are clean, each commit pass the CI and is its own thing.
[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.
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
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.
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.
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.
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.
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
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.
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.
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 :)
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.
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.
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.
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.
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)
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.
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.
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.
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.
The commit all the time is a waste of time for me - my IDE (intellij) has excellent local history.
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.
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.
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.
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.
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.
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?
[0] https://nvie.com/posts/a-successful-git-branching-model/
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.
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
I've seen some people commit like 40 changes in a PR lol. Happens just squash it.
Can also use another branch
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
git commit --amend --no-edit
oftenonce 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
- 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?
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
(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.
I think I'm going to start using rebase to get those clean commit histories though.
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.
All details regarding code and reasoning is on the issues and pull requests.
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-...
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
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
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!
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.
I push at least once a day
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.