Things I know about Git commits
jvt.me
jvt.me
If you don't know about it, it is well worth checking it out now.
It is your one ticket to getting to whatever previous, good (or bad!) state you were at any point before[1]. The only thing it cannot help you recover is locally made changes that you never committed to anything at any point, and have since locally deleted. This is surprisingly rare, at least for me, as I just tend to `git stash` stuff I don't want anymore.
With `git reflog` in your backpocket, you can rebase, merge, branch, delete branches, cherry-pick, rewrite history, whatever, all to your heart's content. `git reflog` keeps a journal of every single action, and tells you the HEAD before and after each action, which you can then simply checkout or `reset --hard` your branch to.
I never even use any arguments with it, I literally just `git reflog`.
[1] Unless garbage collection has deleted the commit you were pointing to, I guess, but I've never had to use `git reflog` that far in the future.
gc.reflogexpire=never
gc.reflogexpireunreachable=never> gc.reflogexpire
90 days
> gc.reflogexpireunreachable
30 days
If you use Git in a terminal, a much simpler alternative to the reflog is having the commit hash in the prompt. Then comparing/reverting to a previous state just requires scrolling the terminal to find the commit id. It's much easier to read than the reflog. With the help of the shell prompt, the terminal can help with the questions "what did I do to get there and how to get out?".
I also think that Git has become much more error-proof. It's been a long time since I've seen anyone lost in their own repository like I'd seen years ago. There are better GUIs. In the command line, modern Git often displays hints, has warnings for some dangerous commands, and sometimes explains how to rollback.
When working on big projects with many people, multiple release branches, and constant updates to those release branches, there‘s bound to be situations where you try to juggle multiple local branches for the same change(-set) to massage conflicting changes into place, only to after a while go „no, that went bad, let‘s start over“.
Having the commit ID displayed by the prompt is a good idea, and it solves the problem in a similar way. However, I prefer my prompt to be on one line, and it‘s already pretty long. Plus, I often do lots of stuff between git commands, so I‘d have to „hunt“ for the prompts. I need git reflog on average probably about once a month, and just typing „git reflog“ and seeing what git operations I‘ve performed is perfect for me.
Top that up with `log` showing all branches at all times (except if you ask otherwise) and you end up pretty much where mercurial is. Detached heads is such nonsense. Git missing phases and obsolescence (to denote if and how history eventually got rewritten) is another.
The only thing I need to remind me to be careful about is, as said, uncommitted changes. With some luck, Time Machine can help for that, but to be safe I practically always stash away changes instead of somehow undoing them.
What I think that should be one of the first things and most people don't know is written in "What is Git?" chapter from Pro Git: https://git-scm.com/book/en/v2/Getting-Started-What-is-Git%3...
YES. Go be a baker or something! If you're going to be a developer you should generally speaking be willing to figure out what's wrong and how to fix it.
Know how your tools work. Bad carpenter what have you...
Also speaking of `git reflog` and such - I still stand by this article I wrote a few years back - https://donatstudios.com/yagni-git-gc
99% of people should just turn automatic `git gc` off. You don't need it. Turn it off and you never have to worry about losing work. It's there, you just have to find it.
What kind of pastries do you like?
But it shouldn't take hours to "fix" a repo in most cases. As you said: git reset.
Anyway, I've been putting some of my repo in very bad states by doing git operation from different unix user that lacked the right permission to do all the writes leaving some corrupted objects and stuff which needs a bit more than just a reset to recover. Then it takes some time to recover, but that's because I also don't want to loose some hours of un-pushed work.
Doing a delete of my repo could have been faster but I'd have lost some temporary branches and squashes and stuff that would also represent some work.
But I think the original point is for people who are not even able to do a git reset.
There are some simple next steps to go from there:
- notice that you don't have to reset to the main branch, you can put in any branch name
- it doesn't even have to be a branch name, it can be any kind of reference to a commit
- 'git reflog' is useful to find out a reference to a commit you recently were on, that you can then give to reset
- you don't have to use --hard, I personally like using --keep, and sometimes you might want to keep local changes so you don't supply those options at all
- ...
What you already did is what Zambyte suggested you should do. Of course there is diminishing returns at some point, but that point comes way after 'git commit -am $msg && git push'.This is such nonsense. The git you can learn in an hour gets you exactly to the point where you end up in this "now what?" situation. Furthermore unless you're working through this more regularly than every 6+ months you're not going to remember how to fix it.
Take notes.
One needs to learn full branch names and how the abbreviations used in daily life really work. A local branch called origin/foo can be confusing, especially after being pushed. And of course one needs to understanding the concept of remote branches in the first place, they are not really remote, typical poor git terminology.
For mass mess-ups in the work tree git status -s | grep ... oneliners are your friend.
gc.reflogexpire=never
gc.reflogexpireunreachable=neverSo, I don't judge, but sympathize with people who just "delete the repo and start from scratch". Unintuitive, user-hostile tools call for heavy-handed solutions.
IME, most people are willing to learn something when they're shown the value for invested effort. That "delete the repo" is standard answer for fixing f*up, tells more about the tool than the people using it. (I.e. it requires disproportionately big investment of time for little value.)
It's like sticking to the text editing feature of your IDE because you can't be bothered to learn how it works. Sure, you _technically_ can do that, but you're losing on everything that makes an IDE useful and probably losing actual days or weeks worth of work because of that.
And that's the problem. Because every developer has spent an hour learning how it works by themselves but then each of them in completely different ways, from different sources, on different projects and workflows, some more correct than others, because there's not one single perfect ground truth way of using git in every situation, but git offers one million ways of shooting yourself in the foot once you land on the job, even after you think you learned git in that one hour.
And that IMHO is git's biggest problem: too powerful, too many features, too many ways of doing something, no sane defaults out of the box that everyone can just stick with and start working, too many config variables that you have to tinker with, etc. Case in point, just look at the endless debates in the comments here on what the correct git workflows are wand what the correct config variables are, nobody can agree on anything unanimously on what the right workflow of configs are everyone has their own diverging opinion.
I've met many devs who hate git with a passion but they just have to use it because management said so and because evry other workplace now uses it, just like Teams and Windows. Not saying git is bad per se, just pointing out the crater of pitfalls it opens up.
Years ago when private repositories were still a paid feature on GitHub, you could use Bitbucket, which had them for free, and offered Git and Mercurial. A few years later Bitbucket announced they were removing Mercurial support because "Mercurial usage on Bitbucket is steadily declining, and the percentage of new Bitbucket users choosing Mercurial has fallen to less than 1%".
https://bitbucket.org/blog/sunsetting-mercurial-support-in-b...
I also don't see anyone else choosing to breathing anything else than oxygen either. It's not like they have so many other options when the job market requires git and most coding tutorials also feature git and schools also use git, so the entire industry decided to use git despite other options existing.
Again, that doesn't mean git is bad or that is loved by everyone or that it's the best. Betamax also lost to VHS despite being technically superior. A lot of victories are won by the lesser product given enough inertia and being at the right time and the right place. Kind of how Windows and SAP got entrenched in the 90s. People and orgs were buying into it because everyone else was also using it so your only choice was to use it too no matter your own opinions on it. What were you gonna do? Piss against the wind and torpedo your hiring prospects by pigeonholing is some other "better" tool that nobody else uses?
I don't remember what VCS I used at my first job in the embedded industry but that one was hands down way better, easier and fool proof compared to git, with a nice GUI long before GIT GUI tools were even remotely good, it just didn't survive there long term because it costed a fuck tonne of money in licensing fees for the org. You can see where this is going, right? When it comes to bean counters, free beats paid every day regardless of most other arguments.
Opening an unknown file and having the option to see all it went through is powerful. The commit messages and history will tell you which files are related, which files change together, why they do, etc.
It's a superpower that a team can cultivate.
Git isn't only code sharing that you commit and forget. It is a database of code that tracks what changed, why changed, who changed and when changed.
Want to know why a piece of code exists? Find the commit that introduced it, read the message. If it's not enough, you can search the commit and find the discussion on GitHub/GitLab/etc.
A new bug suddenly appeared? Use git bisect to find what was the commit that introduced it.
Some months ago I posted some tips about using Git as a debugging tool, if you want to read more about it: https://news.ycombinator.com/item?id=39877637
git log -L:funcname:file
funcname will be matched after wrapping it in a "function declaration identifying" regexp.Get the entire history of a given function (as long as it was in <file>). Not a daily driver, but sometimes unimaginably valuable.
Session::set_state (XMLNode const & node)
I would write: git log -L:set_state:session_state.cc
If I worked in C, and I had this in a file: void
some_function_name (type1_t t1, type2_t t2)
I would write git log -L:some_function_name:thefile.ccI'll check it and I'll update my article with that!
Whether or not relying on git commit messages for source of truth documentation is a good idea is debatable, but personally, when people do it I'm glad they did.
I'm searching through commit histories several times per hour.
You can find more than that, e.g. what was the context of the introduction of a code, find the discussion about that introduction, what was the situation of the project when the code was introduced, etc. But even if you only want to blame, it is enough to keep a good commit history.
> Nobody will look at my 1-year old commit and I'm not going to look at anyone's 1-year old commit.
If you commit history isn't good no one will. And no one will benefit of using git. And then you don't need to use git.
> Git for me is a collaboration tool and code backup tool, and that's about it.
If you don't see the point of having a commit history, there are other tools that fit it better. For code collaboration, JetBrains' Code With Me and VSCode's CodeTogether. For code backup, Google Drive or Dropbox.
That fixes the "Squashing, when you have 100 crap commits, and then not re-editing the message is a crime" item, because suddenly not re-editing will give you a fairly useful message. This ofcourse assumes the PR description is useful, but I've found it much easier to convince people to write a decent PR description than to write decent commit messages.
[1]: https://github.blog/changelog/2022-08-23-new-options-for-con...
First I head about automatic squash messages, and if they include the whole title and description I should look into it a bit more!
It also really helps if you can wire up some continuous deployment to automate something tedious like properly incrementing the version number in the semantic version, updating the changelog, and deploy out a new `latest` or `next` tag to the package registry.
Even the most reticent users are often inspired to follow conventional commits once they see the possibilities that open up.
1. "How squash merge can break Git repos with submodules", about a situation where squash merges silently broke a repository: https://news.ycombinator.com/item?id=40846596
2. "Git: please stop squash merging!" giving some technical explanation about what squash merges actually are under the hood and how the misconceptions about Git leads to invalid "pros" about squash merging: https://news.ycombinator.com/item?id=40840572
Some developers don't even use git and just use some UI on top.
I wish GitHub would have an interface to do interactive rebase and edit commit messages.
When I occasionally have to work on Linux or macOS for our Electron based cross platform product, I haven’t yet found a GUI tool I feel comes remotely close to TortoiseGit in terms of advanced features. So much so that if I need to do things like rebasing or dealing with slightly complicated merges, I push first to a branch and then do the Git work on my Windows machine.
I could work out all the Git command line arguments for these advanced use cases, but for something like rebasing with some squashing or skipping, I can’t see how anything except a well designed GUI could be anything except a seriously clunky and much slower experience.
My feeling is that (good) GUIs in general make a lot more sense for Git, instead of having to read the entire manual upfront to know the available tools, the GUI can show relevant options based on context and you don’t get bogged down having to type out/paste commit references and other things into your terminal.
I occasionally use the built in Git tools on VS Code and Visual Studio 2022 (the latter of which only in the last couple of years didn’t completely suck), but generally only for simple pulls, commits and pushes, which is generally more convenient in the IDE than having to switch to TortoiseGit.
Of course a tool is just a tool, if you don’t “get” how Git works, no tool will save you, but I do think TortoiseGit’s rebase UI helped me get to the point where Git “clicked” for me sooner.
That’s just an interactive rebase; on the command line it opens a text editor. It’s pretty easy.
Is this saying 1 feature / main "idea" per commit?
Overall this post is gold, but also probably preaching to the choir. IME it's challenging to convert non-believers to the faction of Orthodox Git.
For me, learning the ins and outs of Git felt like uncovering a part of myself which has always been there. Nothing new was created, only revealed.
- Add tests in a commit *before* the fix. They should pass, showing the behavior before your change. Then, the commit with your change will update the tests. The diff between these commits represents the change in behavior. This helps the author test their tests (I've written tests thinking they covered the relevant case but didn't), the reviewer to more precisely see the change in behavior and comment on it, and the wider community to understand what the PR description is about.
- Where reasonable, find ways to split code changes out of feature / fix commits into refactor commits. Reading a diff top-down doesn't tell you anything; you need to jump around a lot to see how the parts interact. By splitting it up, you can more quickly understand each piece and the series of commits tells a story of how the feature of fix came to be.
- Commits are atomic while PRs tell a story, as long as it doesn't get too big. Refactor are usually leading towards a goal and having them tied together with that goal helps to provide the context to understand it all. However, this has to be balanced with the fact that larger reviews mean more things are missed on each pass and its different things on each pass, causing a lot of "20 rounds of feedback in and I just noticed this major problem".
As an example of these is a recent PR of mine against Cargo: https://github.com/rust-lang/cargo/pull/14239
In particular, the refactors leading up to the final change made it so the actual fix was a one line change. It also linked out to the prior refactors that I split out into separate PRs to keep this one smaller.
Also agree with GP that each commit on master should be passing/deployable/etc., but I don't see why they can't be merge commits of a branch that wasn't like that.
The author says he reviews himself on a GitLab MR / GitHub PL, I rely on two things for this:
- git add -p, which also helps me split stuff in several commits if needed. It bothers me that it doesn't work for new files.
- git difftool dir-diff for changes with several commits
I like that it would work on any git hosting, and that it works locally. And that I can just amend my commits if I see something.
I often find myself spiking things then breaking them back down with -N and repeated `git commit -p` to form a reasonable history. The workflow seems to really suit my mind. However, it does require some testing vigilance if you're manually editing the hunks for clarity on top of simply splitting them up.
There's constantly new things to learn about git. I use `git add -p` extensively, but never thought to check for an option like that.
Lazygit is an amazing tool that can definitely do this (and many other useful things).
One day I hope to have a positive and comprehensible experience involving git and perhaps even git and another programmer, but that might be too much to ask.
You rarely want to do _just_ this, i.e. call out the bad commit message in question and nothing else; you're generally going to have something to fix because of that "bad assumption", too. You just branch off the old commit, do the needful (read: make the correction to the code), commit it, and then merge that into mainline. It seems like a lot of people fail to consider that a branch merged into mainline does not become immutable; at any point you can check it out and make further changes and then remerge those and do all of this as many times as necessary.
If you really do need to only draw attention to a bad commit message in a past commit and correct the record but nothing else, then you can just "annotate" it with an ordinary merge commit—
1. Check out the old commit that you want to annotate (create a branch there if you want, but it's optional).
2. Merge the mainline into this HEAD/branch, making sure force a merge commit using --no-ff, and write your "annotation" in the commit message.
3. Merge that into the mainline branch.
When working on the code, it's also very useful to access the history without having to open each ticket. Being able to hover a line, having the message of the commit which last changed it, and be able to say "ah ok, it was for this" is quite useful.
> I’m not staying long enough in this company to see them changing from Jira to something else, so I don’t care about the Jira dependence to understand commits
Isn't this a terrible argument in favor of "Fixes NNN" commits? Someone who care about the longevity of a versioned thing should care. If you don't care anyway, nothing matters much.
And even if someone is interested in an old commit that contains a ticket number for a ticketing system that does not exist anymore, the commit itself (the code changes, not the message) tell the story as well, just not in prose.
That is more than enough time to change ticketing system, wiki platforms, or collaborative chat services.
I was only thinking about the application code space where elaborate code comments are definitely more common.
Honestly, this just sounds like we need better integrations with JIRA into dev tools. I'm not going to rewrite tickets in commits / PRs. That ticket has its own history, linked tickets, acceptance criteria, epics, related bug tickets, etc.
But indeed, you could imagine some script that fetches the ticket title / content from the ticket number and commits. Nice weekend or hackaton project.
For new contributors it's probably also more useful to see the latest snapshot of the code base and code comments than having to check a git log in order to find out why a function would be written in the way it is.
Sometimes you need to go back and read through a lot of context understanding why a block of code does what it does, and it's a lot more efficient for the rest of the time to have a quick reference to the full explanation rather than pages of comments giving that context.
If you like to see chains of commits each with a complete feature, just remember that merge commits are commits as well!
Sure, rebase a feature branch before merging it, but then merge it with a merge commit ! (merge --no-ff)
That way you'll have both your "atomic commits", and actually atomic commits, that is commits with changes that any diff program will be able to highlight correctly. Verification will be extremely easier, but you'll retain the ability to browse the history at a higher abstraction level (either use any decent graphical git browser, or simply --first-parent).
Though to be honest I am not sure what you mean by “jumbled” diffs. If you’re pushing a bunch of intermediary commits that don’t make sense on their own, don’t; that’s what interactive rebase is for. If you’re seeing jumbled diffs it might be specifically because you’re relying on merge commits instead of rebasing and fast-forwarding; if you consistently and diligently rebase that isn’t a problem.
I know that many do, unfortunately.
It's a legacy of how git was used initially, I guess, and of the use of e-mail for sending Linux patches
> Though to be honest I am not sure what you mean by “jumbled” diffs.
Simple modifications such as code moved or indendation changes wil usually only be recognized by diff programs if they're on their own, without other changes included.
It's unfortunate, but since that if the state of things, adapting to it by making small commits will make reviews massively simpler.
Yeah, those are some of the situations where I will push multiple commits. Though indentation changes are something I prefer to avoid by using autoformatters.
Well when you extract a function, add an if, loop, or do any number of other things, you can't avoid changing the indentation (unless you want to leave a mess)
Rather then misleading, the diffs are much harder to check, instead of showing that only white space changed (or in general, rather than highlighting only the actual thing that was changed).
Basically, it's little additional work which guarantees simpler reviews for everyone, I think it's warranted
The most important point for me is that the same tool is used for 2 purposes: Keeping track of quick trial and error experiments so that you can return to the best one once you have made up your mind. And producing a readable story of self-contained atomic commits telling a story to the reviewer and yourself weeks or years later.
I hate myself every time when I don't get the context switch right between those 2 modes of operation. Or just don't do the second phase because I first need fo fix a bug and will continue with the current branch later.
- oh my zsh git shortcuts are great
- git-extras (especially git-brv for when you're an open source library maintainer)
- pushing to PR submitter's branches to clean up a PR ready for merge is a good skill to know
This has been my experience. Very painful
Unless your work is trivial, you will have crap commits. Have discipline but don't insist on working on trivial things just so your commits appear logical without rework.
My point is about atomic commits, not git.