Use Git tactically
stackoverflow.blog
stackoverflow.blog
Minor terminology pedantry: free climbing merely means making progress using only one's hands and feet (or other body parts as appropriate) on the rock. It is in contrast to other styles including aid climbing, where it's totally fine to do something like place an expanding cam in a crack and step into a sling attached to it, thus using the equipment to make progress. You can still place cams and other stuff when free climbing but they're just there to save you if you fall. It's not free climbing if you're using gear to get up the wall.
Free solo climbing is free climbing without any protective equipment. If you fall, you keep falling until something else arrests you (the ground, a ledge, etc).
Honnold practiced freeing all the moves with protection until he was confident he could solo the route.
It's common to refer to free soloing as "free climbing" but it's not the same thing. Though people who aren't climbers likely don't care about the difference anyway, and that's fine. I'm just pointing it out in case anyone here is interested.
No one ever forces you to pull out all your gear and free solo the route, just like no one is going to delete your git repo and ask you to rewrite your code from scratch. These are just things a tiny minority of people do, sometimes with disastrous results.
That said I like the analogy. But maybe the take home is that we don't all need to be Alex Honnold, where one mistake means certain death, and can instead settle to be the guy who takes the occasional 15 foot fall.
Interactive rebasing with fixup/squash makes the end result very readable. `git reset` and `git add -p` can also be a huge boon when tidying up a branch before PR.
I can understand they don't want to oversquash - I rebase & squash so that each commit should be able to stand on its own. I think the above link is a good argument for squashing (and rebasing) to the point that each commit should leave the system in a state where everything works, because otherwise you can't use `git bisect` and actually determine if it's good or bad. I will often have more granular commits where I break something and then fix it in the next commit.
IMO this is classical example of "don't solve the administrative tasks with technical means". Git is a tool, how do you use it is up to you.
Even then, you can't unleak auth credentials so you will need to change them anyway. Then leaving the commit, while embarrasing, is no longer an actual issue.
The value of this cannot be underestimated, in my experience, as using things like “git bisect” mean you always have a working build.
I commit maybe once every few hours. I don't recall my diff exceeding 2000 lines, normally it's a few 100s. I can't recall the last time I lost code. It only happened a few times ever. It was never a problem to quickly redo those changes (and possibly improve along the way).
Is there any other point in doing micro-commits other than being "safe"? If not, I have other things to worry about.
Besides, my editing environments don't play well with checking out an older commit to see what's there - they don't recognize this as "going back". Much better to simply undo.
I try to make my commit history nice but the way people obsess about it, I don't think is productive.
His commit messages are equally maddening - “change this variable to 5”, “fix broken spec”, “change variable to 7” ad nauseam…
It's the git equivalent of "useless comments" like `int x = 10; // set x to 10`
Comments answer why (and even 'what' is fine if the code is difficult.)
But simple rules like that always have exceptions.
It also makes tracing the historical "why" of a change easier to understand. If you don't squash it's a random change in the log, if you do squash then it's "adding feature x".
This is one of those "ounce of prevention vs. a pound of cure" situations.
We tend to discount everyone else's time but our own.
Might also be worth thinking about long term asymptotic effort vs marginal effort at a particular point in time--typically, the more often you do something (decomposing a commit into multiple independent parts, in this instance), the easier and less time-consuming it becomes.
Both. I don't really follow what you're getting at, I think we're in agreement? confused
My commits all stand alone; each is a distinct change that pretty much always is in a working state. Often it will be a series of refactors before the real change. Ironically, though it makes bisect easier, I find it rarely need to use bisect: just looking at commit messages (via either log or blame) is often enough to isolate a problem.
I accomplish this using interactive staging, and within my branch, and before I push, using any combination of rebase, squash, amend and fixup. I also prefix commits with the ticket to make referencing way easier.
My failed experiments (a change that later is undone) don't usually make it to anyone else, unless it's something modified during a PR review. On a small handful of occasions I've changed something radically and will delete the remote branch, modify and interactive rebase ny local one and push it again (or push as a new branch). For a feature I work on for a couple days, I'll commit a couple dozen times or more, rebase/squash to maybe 5-10 commits anyone else sees, and push to origin maybe 2-5 times.
When merging the branch (PR), I use merge --no-ff. This preserves commits and makes it easier to see later during a blame. And since the commit history is clean, it's not "noisy".
One of the massive benefits of this is the ability to take a big PR and split it up for easier review: I can branch off halfway, make a PR there with just the refactors, merge it, then the actual change merges cleanly later, and preserves the parent commit relations, so you can visually see what happened. This also works great when you fix a bug on the way to adding a new feature, or want to ship the early part of a feature early before everything is done (but only decide that after work has already started on stuff you're not ready to ship).
I have a local branch, usually just called "wip" for work-in-progress and commit to it frequently. Then when I want to push to origin I pull all the contents from that branch into my "real branch" and commit it as one commit. Then delete the wip branch and repeat.
If I'm working on multiple tasks at once as my distracted brain loves to do, I'll have several wip branches. wip-new-hook-events, wip-update-sdk-version, wip-refactor-button-state. Sometimes the branches conflict with eachother, but dealing with that is worth being able to drop a task for a few days while I'm stumped or blocked. I also don't tend to micro commit when tests pass, just whenever I feel a smaller idea is either done, or I don't know how to progress yet.
I'll also add that sometimes I do break them into a few commits on "real branch" if it makes more sense to do so, and I use a GUI tool for merging if I need to solve conflicts. I also don't really worry about commit messages being useful in the wip branches. Oftentimes the commit message is just "wip".
The idea is that each commit should be small enough to fit in one "page" of PR review, with source lines and test line diffs <150 each, and everything should be one integrated and independent change with unit tests all passing and deployable to prod, etc. Honestly, I can't see a downside to this, other than maybe taking a while to internalize this process and to properly decompose commits.
Have you considered advocating for a change of company standards? Sometimes thats a sisyphean task, but if the only argument against it is "that's how it's always been done" it might be a worthwhile endeavor.
I have a "micro commit" sitting in a branch right now that's just a single line change, for example. That's not super common, but I don't have any strict rules for myself about when to or not to quicksave.
> Have you considered advocating for a change of company standards?
Oh trust me I did. I'm at a better job now :p My current company's git policy sounds identical to yours. But I still like this workflow for myself.
If you do 'git checkout wip .' it pulls in all the changes committed to your wip branch into the staging area of your current branch. Then you can just 'git commit -m "My meaningful commit message."'
That's the cool thing about git though, it has a lot of compatible workflows so you can do what's most comfortable personally.
Why bundle all changes into a single commit? How can the commit message be meaningful at that point? Why not just submit multiple atomic commits?
If I'm working on really small tasks where I know I'll be done quickly and won't need to look back at my earlier changes, I don't bother making a wip branch.
In general our OSes aren't storing a complete history of a file, so we rely on git "quick saves" (as someone else put it).
The git log is as much an artifact of the development process as the ticket history or the code itself. You don't want to publish your quick saves for someone else to have to dig through in two years time - give them something a bit more polished than that.
Up until the point of merging, other developers reviewing the PR can see your development path, and optionally examine individual commits. In GitHub at least, these individual commits usually make their way into the squashed commit's comments as a bulleted list of changes (after some cleanup), which encourages better final commit messages in general.
The point about bulled list of changes is a good one.
I usually have my changes planned out before I start writing code, including which commits I want to make. Figuring out how to go about making a change is a design time problem, rather than implementation time imo
Why squash commits to begin with? Unless you're getting too many commits per package per time period, then maybe that's an indication that you should modularize your codebase.
It's often a tactic of "make the change easy, and then make the easy change". So the first few changes make the change easy -- they are refactoring to prepare for a behavior change.
Then often the last commit is small and has all the behavior change.
Now it's big enough to describe / review. So I do a git diff master.. , make sure it's what I want, run a wider set of tests, squash the commits, write a description, and merge.
In my mind these tactics makes going forward faster. I don't have to check every box before making progress. I'd say 95% of time I just keep piling on commits in the "happy path".
But 5% of the time, on a particularly tricky bug/feature, I might go backward, and this method saves me time. I don't have to worry about polluting the master branch.
-----
Before git I didn't really have a decoupling of "commits" and "work that could be pushed". I was usually working on one big commit. I appreciate this flexibility in keeping track of "code maturity". The master branch should always be kept working with all tests passing.
But I often want to save code in "less good" states. And it even helps if I'm on a desktop and want to move to a laptop to change locations. I just push to the dev branch and then pull on the laptop, without messing up the master.
1. Runs CI
2. Can be merged independently (to reduce the chance of merge conflicts by preventing drift from master/main)
3. Can be reviewed in isolation (so your reviewers can follow your route)
Is also good. That's the idea of Stacked Commits (https://graphite.dev/blog/post/N8SOs49y4bYdV1Vjf5qE).sql_parser: add new languages now supported by parse_unicode()
sql_parser: switch from using old parse_text() to parse_unicode()
sql_parser: add parse_unicode()
sql_server_health: ....
sql_server_health: ....
...
rather than like:
fix stuff in this random function
updating functions using random function
The ones with the tags help the reader determine what things go together much more easily, so even those lame commits are much more helpful if they have a tag:
sql_parser: fix stuff in this random function
sql_parser: updating functions using random function
Nearly everything is a feat or fix, but it's much more useful to know which project it applies to.
I believe Kent has also posted a few videos on YouTube demonstrating this approach.
In the past this has caused me to commit way too little (i.e. sometimes weeks between commits...), but I've improved on that somewhat.
I still have a bunch of tabs open on my laptop about some Git workflows that should allow me to commit more frequently and then revert back to my 'starting point' for the gutter indicators and other diffing. Must get around to trying some of that stuff out.
I'm probably missing some other useful tool or way of working to help with this kind of thing - let me know if anyone else has thoughts on this. Personally I don't think I could ever get to this 'micro-committing' state, but there is a middle ground somewhere that I'm aiming towards.
[1] https://code.visualstudio.com/Docs/editor/versioncontrol#_gu...
The combination of 'git commit --amend' with 'git reflog' enables creation of commits that are hidden from the usual history as shown by 'git log --oneline', but no less fully inspectable later. Likewise recoverable, by passing commit hashes from 'git reflog' to the usual tools.
Definitely a late April Fool's blog
I use Jetbrains tools and it actually does this automatically through local history. I've never really needed it, but if you like that kind of thing, I'd suggest finding a tool that does it automatically for you
We have enough cheap space that we should be able to easily have (non-binary) file changes have a history log in our file system itself and be able to revert local changes without needing the full change graph that git entails.
I'm all for small commits, but committing every time ctrl+s happens would be an anti-pattern because ctrl+s doesn't imply it compiles whereas I'd want any commit to compile. (passing tests optional).
Also, some editors/IDEs have a file history function (separate from version control).
I've personally had my git repository become unusable when my laptop lost power too soon after making some commits, before the background periodic sync occurred. (XFS)
My safeguard is "undo", but I rarely need it. If I do, I just copy the contents of the file to a new one (ctrl n, ctrl a, ctrl c, ctrl tab, ctrl v) before undoing the wrong path - because any change on older version will break redo history, unfortunately.
[1] https://vimhelp.org/undo.txt.html#undo
Vim also has this in gundo. It's one of the killer features of the older editors that doesn't seem to be present in newer ones.
The key is to convince these people that the git history can be very worthful if edits are commented appropriately. Rapid committing in small steps comes after.
Jokes aside, what advice would you give to such developers? Asking for a friend.
https://blog.isquaredsoftware.com/2021/01/coding-career-git-...
TL/DR for that section:
- Write Good Commit Messages
- Make Small, Focused Commits
- Clean Up Commit History Before Pushing
- Only Rewrite Unpushed History
- Keep Feature Branches Short-Lived
followed by some thoughts on "Git Archeology" as a useful concept.
Make small commits, make descriptive commits, whatever.
If you have something more advanced than that, there's a bit of essential complexity there that's kind of hard to get rid of anyway.
There might be some better VCS out there, but I'd only want to switch if everyone else does. Having to stay familiar with 2 different systems sounds worse than 1 mediocre one.
Per function means its easy to see old code without being overwhelmed