Git Things
matklad.github.io
matklad.github.io
This may work for small projects or open source projects that generally receive high-quality PRs, but this sounds truly infeasible for large teams or organizations.
There are a couple reasons why the code review process occurs before merging. First, it helps keep the canonical version of the codebase in a "correct" state. This is, ironically, an extension of the "not rocket science" principle that the article mentions. Without this invariant, any checkout of the codebase might contain what is essentially "WIP" code that will waste other engineers' time if they have to interact with it. Second, the social pressure of blocking someone else's work is an important and necessary force for motivating code review. The idea that folks will go back and review code that's already been merged is akin to "will add tests in a followup PR". It's a pleasant lie we tell ourselves, but it rarely comes true.
Relatedly, the article also seems to suggest that the code REVIEWER should be providing the changes necessary after code review. This is problematic for so many reasons: first, it places an even higher burden on code reviewers' time which no one wants; second, it encourages poor code hygiene if someone else is just going to fix up your crappy work later on; third, it robs junior engineers of what is perhaps their most valuable learning experience: getting feedback from more experienced engineers and acting upon it.
(there are other issues here; the general thesis of the article leans heavily towards making code easier to _write_ rather than _read_, which again I think it just not appropriate for a codebase with a significant lifetime or number of contributors)
I want to slightly disagree on this for folks who are early on in career e.g me; I think there is a lot of value in learning to communicate precisely and having a habit of writing good commit message has certainly improved my skills, both verbal and written.
Even though, this activity appears futile for a minor change; it might help to improve other skills :)
My analogy is your bookshelf books still need jackets!
As for "spacing", you can probably tell the why from the diff as well. The "what", however, tells you whether it's even worth opening the diff.
The most important part of a commit message is the context.
Good enough commit log:
utils: Minor
drivers: Fix
assets: Update
docs: Typo
Hopeless commit log: Minor
Fix
Update
Typo
Context should answer the "what". Think of it as the one word you tell a new conversation participant to let them realize what you are talking about. drivers: Fix
Let's say your fix causes issues later. When I read the commit message, I want to understand what you were trying to fix so I don't accidentally re-introduce the problem when I fix your fixExplain the issue, and by all means mention the bug number, but you must explain the issue you are dealing with.
Point is bad commit messages are sometimes a symptom to a larger problem and as with this vendor it is often a process problem, not an individual developer problem. In my opinion, the main problem here is that management simply doesn't care she it doesn't empower leads and developers who should care.
> Although I tend to have a short summary and mention the ticket number.
So for example
XJ-8883 - Ensure DB connections are closed sooner in main poolExample:
EntityStore build method records the specifier argument when given
Article about it: https://github.com/aaronjensen/software-development/blob/mas...
But yes, even minor changes should have a descriptive commit message that summarises the change in order to make commit history useful.
Now, when the code itself needs an explanation then that should be in the form of a comment next to the code itself.
But, I think the value of commit messages become really apparent once you (or your team) are relying on other visualizations of commit messages like `blame` and `bisect`
Tracking down the introduction of a bug with bisect and resolving to a commit whose message in “minor”, because the person in the past didn’t think this change was important enough to describe intent can quite the temporal foot-gun.
Even for the most minor commits, including "improve comment formatting ; utils.pl" allows skimming that message and comparing it to the actual commit.
It also helps me be aware of what I'm doing, pointing and calling style.
That said, an "utils" file is often a smell in code organization.
Contra: while this is true, it's worth considering how 'information about which files are effected' scales when examining the history of a repo. A commit message that simplifies and clarifies the intent is more useful than one that requires a future reader to read each git commit in full to understand the effect.
Right, but this is not always the same as the files which I intended to be affected, and the matching of the two is the sanity check here.
utils was just an example filename.
I also don't get why fediverse, mastodon seem to limit characters on a post, mimicking the stupid twitter, which is a niche social network. I way be wrong about the limit (never tried), but these platforms feel for ignorant outsiders like twitter-clones with a different infrastructure.
The reason why we still have them is a mix between inertia, it’s hard to read long lines and it’s nice to be able to have two files side by side.
Mastodon is a twitter clone, with different infrastructure.
`MyDict[family][subfamily][element][property_a][value]`
is much better imo than:
```
MyDict[family][subfamily][element][property_a][
value
]```
Probably the data format is what's wrong here in the first place, lol, but anyhow.
MyDict
[family]
[subfamily]
[element]
[property_a]
[value]I am not one of the ones who cry about Make, but many do and for that reason primarily.
Similar to how websites employ a lot of white space, imagine if instead the text went from the first pixel on the left to the last one on the right, it feels awkward.
The solution would have been (let’s say JS since that’s the most “beginner”ish language where this could even have a perf impact”):
const myElement = myDict[family][subfamily][element];
const propValue = myElement[property][value];
that said, having a max-length, apart from the eyesight reasons, have this nice side effect that make it blatantly obvious that one expression is definitely too complex, as you've shown in your second example
(MyDict
[family]
[subfamily]
[element]
[property_a]
[value]
)
I honestly think all suggestions here are equally bad in their own regards.80 should be fine for most single lines of good code in most languages.
Comments can usually be longer so I have my linter ignore comment length.
Some discussion here: https://stackoverflow.com/questions/578059/studies-on-optima...
For me it’s one of those things without a “scientific” answer, but the status quo is good enough that it’s easier to just go with it and never think about it again. Like linting and autoformatting, tabs vs spaces, all trivial stuff that detracts energy from building a product for customers.
It feels like what's doable in one language, might be a bit more restrictive and annoying to follow in another one. For example, Java, when written in an idiomatic way, with verbose variable names etc.
C++ with even a modest template will flow over 80 without much effort.
I'm now using the condensed width font Iosevka font [1] with 160 chars as my max width in clang-format and indents at 1.
After a few days of using it, I'm converted. It was a bit odd looking at first, but I guess that's brain plasticity at work.
My subjective opinions on a few of them:
Java I find works well with a soft 100, hard 120. The language and library tends to prefer and encourage longer class and function names, and these are often repeated multiple times in a single line.
C# seems to work well with 100.
Rust I find well works with 100, and this is the default out of the box format for rustfmt.
Javascript (especially with 2 spaces) works well with 80 characters.
When writing code for documentation sites (e.g. https://ratatui.rs/tutorials/hello-world/), often the code block fits in an area which is surrounded by prose with a width optimized for reading. The resulting width of the code block tends to be ~80 or less. It's worth making this tweak to your formatting for such example code.
He would have the homework submissions compiled to LaTeX and printed out - then he would write comments by hand. 80 characters was what would fit on a line. If you had more characters, lines wrapped wrongly and you’d get incorrect looking code - so he refused to grade.
I’ve since warmed up to the max line width idea. It’s nice to be able to read code on a small width window (I’ll rarely have a single full screen window with just code) and ensure each line is a valid syntax (and not unreadable due to softwrap in the editor). But I still think 80 chars is a bit extreme. 120 is nicer in my opinion.
Then I pull out my 13” Air and remind myself that there are reasons for limits. For Python, I just default to black, which is 88 chars. Easier to not argue with people that way. Plus, I’m free to write it with super wide columns if I want, and then let my precommit hook fix it.
I'm really disappointed by how weak the dotnet formatter is in comparison.
The rule is slightly anachronistic now, and originally came from the time when CRT terminals (and printers) were 80 columns of text, and there was no graphical window manager. The rule was primarily about making code comfortable to read for everyone involved, to meet the minimum display with of anyone in the group. If you have a wide display but I don’t then it sucks for me. Plus I’m pretty sure that when I first started programming, a lot of printers and text editors and a lot of software wasn’t very good at handling long lines and characters would get mugged or discarded. Almost everything today will just wrap and display reasonably, but that wasn’t always the case back in the days of ed/edlin and earlier.
These days I hardly ever hear about width limits anymore, though it does come up every once in a while when people have to work a lot in terminal or ssh sessions. I do have to drop into Linux non-graphical mode all the time, and even edits files and build code sometimes, and code that’s wider than the display is less fun to wade through. People still like having a reasonable limit, like we’d definitely get complaints if we start writing code that’s 500 characters on a line, right? :P
I really like this approach, a nice short message with a summary, followed by a longer description in the "body".
One feature I really like from GitHub is you can set an option when merging a Pull Requests that when merging the commit title will be the Pull Request title, and the body of the PR as the message.
Join me, ignore the dumb red lines and make your commit message as long as you like. Linters are usually easy to configure and set to wrap at e.g 120 as well.
In-line diffs mix the flow of change in with the flow of execution, both going down the page. Side-by-side diffs makes those two axes orthogonal. With decorations (either in a terminal or a code review tool) even 80 column code requires a fairly small font. Editing two panes of code only needs 161 columns but a decorated patch might be more like 170.
The point font size is its height in increments of 1/72”, and most fonts are half as wide as their point height. The largest font one can use on a 27” monitor (24” wide) is 20pt. My eyes get knackered at anything smaller than 18pt.
The limits you should observe today are more like:
* Code line length: ~120-140 characters. Longer than that and it gets unwieldy especially in side-by-side views. But you don't need to be strict about it. It doesn't matter if the occasional line overflows. The best code formatting algorithm I know (the Prettier algorithm) doesn't have a strict limit.
* Commit subject length: 72, because Github will overflow anything longer into an ellipsis.
* Commit body line lines: No limit (unless you are developing the Linux kernel). I don't know of an interface other than email that doesn't wrap them.
(You're going to get a lot of old farts disagreeing with this advice, but don't listen to them.)
"I used to be with 'it', but then they changed what 'it' was.
Now what I'm with isn't 'it' anymore and what's 'it' seems weird and scary.
It'll happen to you!"
seriously, it's not "being old farts", it's being reasonable with your eyes and your tools: while you may have 20/20 eyes, you will agree with me that viewing a diff of two columns of code at 10px fonts is not an happy thing to do, as the difference may be just a comma or a dot, and those on a 15" 4k monitor are about 0.3mm. And you cannot really view the whole columns without scrollbars or overflows if you set the font much bigger.Here are some common examples:
1. GitHub's single commit view word-wraps at monitor width, not at a reasonable line length:
https://github.com/matklad/repros/commit/9351f5c91bd1d80dbcc...
The result is completely unreadable.
2. `git log` in a full-screen terminal wraps at window width, not at a readable length.
3. In VS Code in Magit, lines are not wrapped at all by default. It is possible to enable wrapping, but then they are wrapped at the screen edge, not at a readable line length. I _think_ VS Code actually has a setting to visually wrap at column, but I wasn't able to make that work.
Additionally, if you look at that commit message, you'll see that, to properly soft-wrap it, you'll need a markdown parser to recognize an indented bullet list. VS Code actually wraps that list correctly, but I expect very few other tools to do that.
https://github.com/matklad/repros/commit/7d7dc3e3539e8cc68e5...
(Every time I see someone advocating for _not_ wrapping plain text formats, I can't get an image of a hammerhead shark sitting in front of a 16:9 display out of my mind :-)
https://i.postimg.cc/x1KFkyMZ/image.png
I think I'd take stupidly long lines over that tbh.
On my laptop with a 13.3" 1080p display I can fit two side-by-side terminals at 87 characters wide (two of these are used up by vim to display git symbols and linter markers, I don't use a number line).
This is not because I can't afford a laptop with a bigger screen, but rather that I value portability, lightness and thinness over screen size. That being said, on larger desktop monitors I also value easy readability meaning that the font size is usually so large that I don't get much more than 90 characters per column.
This means I can have a man-page or other form of documentation as well as an interpreter window on the right side of my screen (in two stacked windows) and the code on the left side of my screen.
I really don't even quite understand how people handle the defaults of VSCode with its enormous directory tree column and the minimap. about 40% of horizontal screen space is wasted in the default VSCode configuration. I mean, to some extent, multi-monitor setups help solve this, but again, I value portability and while I do have a multi-monitor setup, I don't want to be reliant on it to be able to comfortably get work done.
So, at least for me, there's an enormous amount of value in keeping things to 80 characters in width. This doesn't mean I never go over that limit, it just means that most of my code is comfortably readable both when using a small laptop and when using a larger desktop setup.
The other main reason is making it easier to read. It's not that helpful or easy to keep track of a line which is 120 characters long. Not least of all because in my setup it would either end up off-screen or badly hard-wrapped. There's a reason newspapers still use columns and it's so it's easier to keep track of where you are in a paragraph. Now in code, it's not usually 10 120 character lines one after another, but usually it's still harder to make sense of what you're looking at when it's one really long line.
As for regular code, similarly, there are situations where the code doesn't take up the full screen, e.g. in split-screen mode, or viewing a side-by-side diff.
That said, there's a balance to be struck between facilitating those use cases and facilitating the common use case of reading (or even writing) the code, and there's no clear "right" answer. Which is probably why there are so many debates on this.
The first is agreeing on a width limit, whatever that width might be. Such a limit might already be controversial but it seems to make sense.
The second is the question of what that width limit should be.
There's a fun story about the with of our railway gauges and roads and Roman chariots that has the lovely quote that if you"...wonder what horse's ass came up with it, you may be exactly right."
https://www.snopes.com/fact-check/railroad-gauge-chariots/
When it comes to an 80(ish) character limit, that does indeed come from old monitors, which took it from older line printers before them.
https://en.wikipedia.org/wiki/ADM-3A
But it all goes back to Hollerith cards, which were invented in the late 19th century and used in a US census at around that time.
The tools try to limit the summary line of Git commit message to 50 characters. Not the whole message. That is a useful convention. The usual convention is described here: https://git-scm.com/docs/git-commit#_discussion
Instead, adjust the asserts such that they lock down the current (wrong) behavior, and add a clear // TODO: comment explaining what would be the correct result. This prevents such tests from rotting and also catches cases where the behavior is fixed by an unrelated change.
Don't do that, because they are still comments and even worse, the tests are lying. In pytest, there is a way to tell that those tests are expected to fail: xfail, so it will show in the test results every time and it's clear it's just a temporary things and should be fixed.https://docs.pytest.org/en/7.1.x/how-to/skipping.html#xfail-...
It's very hard to rank them, but very high on my list of things that make me want to send people actionable threats is when I'm searching down the history of a bug to figure out if it was a deliberate change that had unexpected consequences or just a mistake is when the git blame path ends on a commit like this.
I don't care how minor you think your change is, if you're working on something collaboratively you need to express intent in your commit messages!
100%! And if somebody cannot communicate the intent, there is a high chance they don't understand fully what they just did.
Elaborate commit messages are great, and most people write commit messages that could be more elaborate.
But most people also tend to squash several changes together, because it's easier.
Being overzealous about writing elaborate commit messages for minor cookies is IMHO probably counter productive.
If the commit message is "fix indentation" then I will ignore it and skip over it while searching the commit history. If it's "minor" or "fix" then I have to open the commit and look to see what it actually is.
These "minor cookies" add up rapidly.
(side note, people that squash important changes together are also on The List)
Can we put those on a separate List?
For anything with my team (or personal projects), where I can be guaranteed everyone understands exactly what I’ve set up, I add a pre-commit hook to fix minor linting issues and then add the change to the commit. If black, tf fmt, shfmt, etc. can just fix the problem, then do so, and don’t bother asking me if I’m sure.
It’s as if a thousand commits cried out in anguish and were suddenly silenced.
Is the change _just_ changing indentation or is it changing something else which is hidden by a bunch of indentation changes?
The difference between a commit message like "reindent" and "." is that the former makes it clear that the change is intended to be completely superficial and (unless you're writing python) should have no impact on anything.
Now the person who wrote that commit message may be lying, or may have made a mistake, but, with even a commit message such as "reindent", it's much easier/faster to approach the issue if your bisect lands on such a commit message as you can go straight for "well let's normalize both versions to see if there's an accidental change hidden in here" rather than getting frustrated trying to figure out what the commit was about.
> But most people also tend to squash several changes together, because it's easier.
Don't.
I also disagree with the author of the article about having an unclean history, it's not that hard to keep a clean history and, combined with actually properly splitting and isolating changes, makes it much easier to review the code. Proper use of git isn't solely centred around making bisect work.
This has got to be the author fishing for reactions, right? My reaction, were I on his team, would be to revoke commit privileges.
If I'm working on something "by myself", I'm really collaborating with my future selves, who will appreciate the information that I'll forget after the next context switch.
Yes this will record a file move in one of the commits but if you diff between before and after the two commits a file move might not be shown. When thinking about file moves in git it is worth remembering this comes from the diff tool not from the commits since the commits just show the state of the file system at the point it is committed.
$ git blame file3 -C1
9afddd5e file3 (Izkata 2024-01-01 11:40:51 -0600 1)
9c8635a7 file2 (Izkata 2024-01-01 11:40:27 -0600 2) 4
9c8635a7 file1 (Izkata 2024-01-01 11:40:27 -0600 3) e
9afddd5e file3 (Izkata 2024-01-01 11:40:51 -0600 4)
Also fun fact, -C can be given up to 3 times:1 - Look for the source in files modified in the same commit
2 - Look for the source in any file that existed as of that same commit
3 - Look for the source in any commit
Great for if you suspect the original committer didn't do the delete/add (move) in one commit.
I think the real failing is that it isn't very good at handling the "rename and slightly modify" case, even when it theoretically could. Of course it's non-trivial to detect that case, and there are flags you can use to improve the detection but it's still not great in my experience.
It might make sense to allow adding hints to the git commit message to help it. Dunno if anyone has tried implementing that.
(Personally, I've not come across the need for such a feature. But I can understand why people might want to have it be available.)
* You need to add `git cp`.
* You'll mess things up if you accidentally `mv` instead of `git mv`.
* All IDEs have to add support for this.
* Have fun resolving metadata merge conflicts!
That's just the things I thought if in a few seconds.
IMO the sensible way to improve this is to have a place for Git to add hints, so that it's automatic rename detection algorithms work better.
Files just need to know where they moved from, if anywhere. Copy, modify, delete operations can be auto-detected almost all of the time. Lineage can be edited after the fact if needed.
Most code review tooling is too coarse in the sense that it expects a small number of reviewers to know the full context of the change or has too many reviewers that slow down things.
Tools to show relevant parts of the change to specific reviewers and potentially break apart a large change into smaller ones automatically will go a long way especially in large scale codebases or when team sizes are large enough that the full context is not understood by everyone on the team
That is a technical solution to something that could be handled by the team. While tooling might help people in the team still have to make same and sound decisions like unblocking team member that needs to do a quick fix.
However,should you, while developing your feature, find some minor thing that needs improving (e.g. adding comments, spelling fixes or tests to some existing API you encountered while developing your feature), it can be better to keep it separate, so there's fewer red herring in both the history and the review.
Typically, you git log main --first-parent
This is kind of obvious after reading but no one ever explained this to me until now
It tends to break apart when working with large teams or when working on mobile apps where one can't just rollback a change easily after it's shipped. The descriptions need to be more detailed and capture lot more information.
For example, our team would require all mobile devs to add information about feature flags for each change to turn the feature off if things go wrong. This also places additional review burden to make sure the flag covers all the new code introduced and does not interact in a bad way with other existing feature flags
I would genuinely like to know in what world this is useful advice. Maybe in personal projects or small teams or companies with a maniacal lead?
I just can’t imagine someone in a sufficiently large organization (which most companies hope to be someday) goes sifting through feature commits to get to the bottom of a failing test.
It's basically the lowest-friction way I know to do the part of TDD that counts.
You'd need to either exclude the test (probably not a great idea) or mark the commits as skippable. Also not a great idea.
Because if so, I agree that a commit that introduces a failing test will break bisect (there will be a pass->fail transition AND a fail->pass transition, and bisecting requires there to be at most one such transition). But even more importantly: Without the separate commit containing only the failing test, every commit will pass. That is, bisect will have no useful signal at all.
I think that can't be what you're suggesting, so could you clarify what bisect process you have in mind?
If you try to bisect such a situation, you'll not be able to run git bisect run on your tests because of the failing test.
I'm not against a failing test being committed, but I believe it isn't hugely productive. The test failing is surely a signal only to the developer who wrote the test that it failed? Or perhaps it should be failed on a seperate branch and then merged to the master branch, which is pristine.
Every organisation has there own workflow, I suppose.
Edit: a crucial bit of info I didn't note was that you keep developing and add a lot more commits before it is noticed.
My hot take is that you should use exactly as many characters as you need to write a meaningful summary, and people who insist on using tiny terminal windows can deal with the consequences of their actions.
> Though not required, it’s a good idea to begin the commit message with a single short (no more than 50 characters) line summarizing the change, followed by a blank line and then a more thorough description.
It’s like the headline of an article, it doesn’t need to tell you everything. That’s the job of the “more thorough description”. And if you omit the summary line, then you’re just writing a description, and the limit no longer applies. It’s not a hard limit anyway.
I’m not sure it has to do with tiny terminal windows so much as encouraging people to be concise. It allows you to skim commits without reading essays, for example.
> Third, our review process is backwards. Review is done before code gets into main, but that’s inefficient for most of the non-mission critical projects out there. A better approach is to optimistically merge most changes as soon as not-rocket-science allows it, and then later review the code in situ, in the main branch.
But the tip about adding a failing test as a separate commit on the feature branch wouldn't survive a merge and it wouldn't live long enough to be reviewed either.
I like most of the advice in this article but this review one is giving me pause.
What would you do to avoid this?
Sometimes the same situation comes up with tests, but it is not as common in my experience.
Assuming we’re not taking about user guide kind of docs, then a major benefit of writing docs first is to clarify your thinking. Being able to explain your intent in the written word is valuable because you will often uncover gaps in your thinking. This applies to a specification, or to acknowledging problem reports and updating with theories on what the cause of said problem is and an approach to confirming or fixing it. You can even reference that problem report in commits and merge requests. It pretty beneficial all around.
And docs don’t have to me masterpiece works of art. Just getting people to clarify intent is a huge win. Peer reviewers don’t have time to do a super deep dive into code. If they know what you intended code to do, that’s something many reviewers can check pretty quickly without having to know much context.
It’s selfish and naive to disregard basic documentation of intent.
It doesn’t have to be like this. This is a choice. There’s no reason a team cannot work towards a world where they can push a small fix to master directly after running their 5 second test suite on their local machine.
We are used to squalor, but it’s not necessary. It’s the result of a series of choices. There are other ways to do our work that maintain continuity of productivity. Old projects can feel like new projects.
Any time someone writes something like this, they are normalizing slights against our fellow developers. They may not recognize it as such because it’s all they’ve ever known, but we should (collectively) know that better is possible and strive for it.
> When fixing a bug, add a failing test first, as a separate commit. That way it becomes easy to verify for anyone that the test indeed fails without the follow up fix.
As a reviewer, I want the first commit to be passing and then the bugfix commit update the test to still pass
- Makes change of behavior obvious to reviewer
- Assumeing all commits are tested, ensures the test is testing the right thing
Yuck! Tests asserting wrong behavior?! Then you might as well not have the thing. Just remove it entirely.
[diff] colormoved = "default" colormovedws = "allow-indentation-change"
That is great for understanding the actual code changes wish it was on by default.
Here’s a quick guide:
Working on a feature branch with someone else and you have local commits and they have remote commits? Rebase
Need to rewrite history? Rebase
For just about everything else, there’s merge.
The downside is there can be dragons in the merge commit (you can make arbitrary changes there) so it requires discipline. I only rebase if the merges conflict and therefore introduce a chance for dragons. So, yeah sometimes I turn right.
If it happened because say someone else did a refactor then rebasing on that refactor (and that might actually mean manually doing a lot of stuff) is easy to inspect. The problem comes through as a commit with something weird unrelated to the change.
It does require line by line code review by the coder, which few might want to do, but I like to do before opening any PR. Because in any case it is so easy to have an editor window open while your cat walks on the keyboard and pastes your password, or whatever :-).
If someone did change something out from under me, I could very well see myself doing my work over on top of theirs, and a rebase may help with that, but I would certainly try to merge first.
I tend to use rebase solely when I specifically want to rewrite history so as to pretend it was different than it was.
Thanks for engaging and discussing!
This is awful advice. The test is there to prevent regression. Why would you change it to not only allow the wrong behavior, but to enforce it? Either fix the test or decide that you won't, in which case remove it. If neither option appeals to you then mark it as optional and let CI succeed with a warning. But changing the test to lock the wrong behavior? No, just no.
At the end of the day it’s all just a sequence of commits and some commit labels.