What does cause a lot of mental overhead (and consider myself a decent engineer) is creating commits that have a single purpose in the first place.
Working on a new feature I often have to refactor something, and while I am at it, I clean up some related parts. That is at the minimum 3 commits applied in the correct order, sometimes over different branches. At that point there is a lot of `git add -p` and `git stash apply` going on, which takes considerably more mental energy while you see some colleagues getting away with `git commit -a`.
Writing good commit messages itself is the reward for creating good commits.
I do partial commits significantly more often than committing everything, and often do things like a couple commits, checkout another branch (and GitKracken does an auto stash + apply) then commit a separate fix there before switching back to the original branch.
Also there are several other things it makes easier: being able to multi-select commits and see a combined diff, quicky diff between two random branches, and just generally browsing back through history. And importantly, it's easy to do all this without having to remember, copy/paste or type any branch names or commit hashes.
With the CLI, all you can do is cycle through "Stage this hunk [y,n,q,a,d,e,?]?" prompts, and if you mess up, you have to exit completely and do `git reset --patch` and cycle through those prompts again (at least, I don't know another way to do that). Actually it's a bit worse because it has even more options:
Unstage this hunk [y,n,q,a,d,j,J,g,/,s,e,?]? ?
y - unstage this hunk
n - do not unstage this hunk
q - quit; do not unstage this hunk or any of the remaining ones
a - unstage this hunk and all later hunks in the file
d - do not unstage this hunk or any of the later hunks in the file
g - select a hunk to go to
/ - search for a hunk matching the given regex
j - leave this hunk undecided, see next undecided hunk
J - leave this hunk undecided, see next hunk
s - split the current hunk into smaller hunks
e - manually edit the current hunk
? - print help
If you use this all the time and get used to the commands, and are also good at picturing the separate pieces you are trying to commit in your head, it's probably fully functional.If you're like me, and come back to your code after an unrelated 1hr meeting (or lunch) and are trying to sort out the 3 or 4 separate changes you did earlier in the day to make nice logical commits... good luck.
You can get a hybrid of the two if you use commands like recountdiff (from patchutils) and git-apply --cached. I do this by reading the output of git diff into vim, editing diff hunks and running recountdiff on those hunks, and running git-apply --cached. If I mess up, I can always read the output of git diff --cached and run git-apply -R --cached to unstage that hunk.
I find it better than using the CLI menu driven tool that you refer to.
And you're right: it's completely functional for doing partial commits, but I agree definitely not as pretty.
This makes sure you can fully test your refractors and that their change sets stand alone.
Reasons include: understanding how a change works (all related code in one place without extra distraction), easy to revert, easy to figure out why that code is there during a blame.
That's why I typically write code where I implement a feature and then go through the diff to determine what to stage and what goes into each commit. Trying to do this while implementing something isn't really something that's worthwhile.
I agree, this is what demonstrates maturity in an engineer. Perhaps we need to focus on commit driven development.
I personally do not allow teammates to ship macro commits covering multiple features. I call this out in code review process.
Working my current coding job I'm by no means the fastest coder, or the best, but man I get praise for documentation, comments, commit details. It kinda amazes me how many skills that are just straight required in one field, earn heaps of praise in others.
I really really disagree. If the branch has one logical commit then no merge commit is needed. Otherwise the merge commit shows what happened and more importantly that these N commits came into the mainline as a single change.
And I'm not cherry-picking... I usually see much, much worse git logs than this from merge-commit users. In fact, if I scroll down, this is what it looks like:
Edit: For a point of comparison, here's one of my projects using atomic commits and rebases:
My projects also have a linear progression. They look like...
* -- Bugfix
* -- Feature B
| \
| * -- Implement some other logical part of feature B
| * -- Implement first logical part feature B
| /
* -- Feature Agit log --first-parent
With that switch and writing correct merge-commit messages you get the best of both approaches.
That doesn't mean using merge-commits is wrong, that means people write bad commit messages, which you already complained about.
I fear you may be terribly misunderstanding the point of atomic commits and readable commit messages. Everything within a merge commit will still come up during a bisect, for example. If the commits are non-atomic and/or they are badly written, that is still an issue.
--first-parent hides the problem, and adds a new one. I'm not sure what your thinking is. Once again, what's more readable to you of all the screenshots I linked?
#!/bin/bash
bad=$(git rev-parse "$1")
good=$(git rev-parse "${@:2}")
git bisect start "$bad" $good
filter=$(echo "$good" | sed 's/^/^/' | cat <(echo "$bad") - | xargs)
comm -13 \
<(git rev-list --first-parent $filter | sort) \
<(git rev-list $filter | sort) \
| xargs git bisect skip
I haven't tested it all too thoroughly, but from what `git bisect view` tells me, this seems to work like a `git bisect --first-parent`. I'm wondering why there is no such option...For anyone unfamiliar with git extension scripts: You should be able to put it in an executable (`chmod +x`) file (say `git-bisect-branch`) in your $PATH to make it usable (`git bisect-branch $bad $good...`).
I suspect that if merge-commits hadnt been the default on github since forever, it wouldnt have so many proponents.
Do you have buy in from other team members for this?
Also what do you do if master needs to be merged back into develop? Do you really rebase develop and make everyone fiddle with all their feature branches?
You can see this in action here:
Feature branch is prefixed with issue number. eg. PROJ-1234-update-ui-to-do-something. That ensures that every commit is logged against an issue.
If you list --first-parent and show the list of merge commits, you see a list of features which were implemented into the dev branch.
If you need to drill down into lower level changes of a feature, you can look at the individual commits that were made in that branch.
That way merge commits are atomic commits of features, and commits in a branch are logical commits/steps for an individual feature.
And in answer to the original post - I try to make all commits have a message of what was the type of change, what the change was, and where it was. eg. "Added a clear button to reset all form elements on the update address page". The general rule is, you should be able to read the commit message and know exactly what was changed and where. Compared to "Added clear button".
Support from tooling also helps a lot, e.g. "checkout branch of issue XYZ".
1) Create a post-commit hook script that only allows commits into feature branches.
This prevents accidentally committing into the dev/master branches (it still allows merges).
2) Create a script file that all developers use to start / end features which handles all the branch creation, pushing and pulling. That way the process is standardised and you cant make a mistake.
eg.
* script startfeature abc123-this-is-new-feature
* script endfeature (this automatically picks up local open features)
The only time there is ever an issue is when the end-feature causes a conflict. This is solved by rebasing your local branch, fixing up any issues in your commmits and then ending it again.
You want both that goal and the individual tasks which achieved it to be visible.
I guess we have fundamental differences on how a tree should be structured. It's more of a DAG in your case, but I prefer to keep a tree a tree.
The only point I'm making essentially is that "the commit" is too granular to capture all the information you need about your work and its history. You need some kind of grouping mechanism as well. It doesn't have to be merge commits, necessarily. But as I understood it, your original argument was that "there is no need for anything higher level than a commit." But there clearly is imo.
> It doesn't have to be merge commits, necessarily.
I was thinking of a tagging strategy.
Even better, use `git commit -p` and skip the staging area entirely.
If you end up staging the wrong hunk, then you will have to reset and recommit. With git add -p, you would only need to reset the hunk.
> you don't have to reset anything
You have to reset the branch pointer to the previous commit, but I think using the --amend switch may do that for you.
That's because, like GitHub itself, the documentation for them is not in the commit messages but in the pull-requests. Look at their PR page (https://github.com/pypa/pipenv/pulls) and individual PRs, you'll see that it's much better organized.
The difference is that now the source of truth is not git itself, but wherever the pull-requests are.
And you think storing that source of truth separately from the code is... a good thing?
Also take a look at `git gui`, which is part of Git core.
I am new to git. You will have to merge eventually with somebody when you are collaborating. Can you really avoid merge commits?
I don't like how `git reflog` keeps track of which hash did I switch to. I only jump to hashes for a quick run. I don't want it actually tracking that in the log. Does anybody know how to avoid that as well?
> I am new to git. You will have to merge eventually with somebody when you are collaborating. Can you really avoid merge commits?
Many developers prefer to only rebase, such that master looks like a single chain of commits without any branching or other administrative structure.
(best used with --ff-only in your config to avoid unnecessary/unintentional merge commits)
It was one of the few things left in git where I prefered IDE's over the plain command line.
Tip when using patch modes: spend time learning edit mode, and IMO avoid edit mode for checkout or reset patch modes.
The git log is one of the main entry points to an open source (or closed source, in fact) project. Following them tells a story, and can help you understand decisions made.
Imagine you have two projects to bisect. One of them has clean, atomic and descriptive commits. The other has a git log that looks like this: https://github.com/pypa/pipenv/commits/master?after=d4d54eeb...
I know which one I'd want to debug. This is also the primary reason why I highly dislike merge commits: They make git logs extremely unreadable.
The thing is that writing clean commits is something that is extremely easy to do. Unlike docs and tests, it's not more work (it's less, in fact) and it's not something to continuously maintain as the code changes. It's an immediate improvement you can make to your development habits that will have a consequential improvement to your QOL. You will catch bugs doing this!
(Tip when writing atomic commits: Use `git add -p`. That lets you stage patch hunks.)