
Plan Your Commits - ingve
https://dev.to/rpalo/plan-your-commits
======
throwanem
Or just clean up your local history before you push. Frequent commits are
handy for checkpointing a known state so you can experiment freely and easily
revert failed attempts; then, when you know you've got something good, you can
squash the relevant commits and rewrite the messages to provide solid detail,
and push a clean result.

Interactive rebase is an amazingly powerful tool - a little hard to use at
first, but the investment of effort to learn its capabilities pays off
manyfold. If you're looking for an easy way to level up, and you haven't
investigated it before, I can't think of a better choice.

~~~
golergka
Please don't do that. Development history like frequent commits and merges has
enormous value, for bisecting and debugging. Once detroyed, it's never coming
back.

What's the purpose of git commit tree at all if it doesn't truthfully
represent the development process?

~~~
stinos
I get your point about the development process representation, but I don't
think you get the OP's point completely, there are ways to get there which do
involve rebasing. I assume OP works the same way like I work; not every commit
represents an interesting part of the development process (more like from the
thought process while working) and rebasing afterwards is simply mandatory.

By example: this morning I was working on some huge refactoring and already
had 5 short meaningful commits (not all with proper commit message yet) when I
noticed some small bug creepd in so I added a line to fix it. That line then
goes in it's own fixup commit for one of the earlier commits. Likewise, I
noticed one of the new interfaces would be better if it had 2 more methods.
So: new fixup commit again. A couple of commits later one logical piece of the
work was done, so I did a rebase to get all the fixup commits amended to where
they belong. Then I reviewed all commits carefully, rebasing again and
squashing some commits because it made more sense to me like that, and
adding/editing proper commit messages on the go. I work best like this (mainly
for pretty large changes or new features which add a lot of code - for small
fixes here and there it's obviously not as involved) and it does produce a
perfecly viable story about the development process, useful to other readers,
but only because of the rebasing. Otherwise it would be a mess. This to
illustrate it's not always as simple and general as 'please don't do that
rebasing madness'.

~~~
convolvatron
imagine if you could express that 'rebase' commit as a summary commit, without
destroying history and history, but allowing you to present the change as a
documented logical unit.

(i.e. what if git log just showed the highest level aggregate commit message
by default)

commenting your changes properly doesn't have to involve destroying history -
thats just an accidental failure of git that has caused a lot of people a lot
of trouble

~~~
WorldMaker
I do think it is a bit of a mistake for git UI tools to so prominently feature
the DAG first instead of something more akin to `--first-parent` views.

(Especially in the way that a lot of old SVN/CVS users came into git wanting a
simple straight line view and finding tools like rebase instead of `git log
--first-parent`, we've ended up in a bit of a mess where people expect to do
the heavy lifting in "sandblaster" tools like rebase instead of better UI/UX
tools.)

------
jordigh
I don't plan my commits. I start writing whatever seems necessary. It's kind
of impossible to draw a map for a terrain you haven't seen yet. If I feel
like, ok, now it's time to record what I've done, I use `hg commit
--interactive` to pick apart my working directory into relevant things using a
very pleasant curses interface for diff selection. I can always later on `hg
amend` any commit that I think may need it.

Then there's the awesome `hg absorb` that will let me amend a whole stack of
commits at once, automatically. It figures out which commit on earlier in my
stack should get whatever change is in my working directory and automatically
cherry-picks the right fix from my working directory to the right commit.

[http://files.lihdd.net/hgabsorb-note.pdf](http://files.lihdd.net/hgabsorb-
note.pdf)

If I need to change gears, I make a temporary commit, labelled by some
bookmark, and move to a different branch of my repo. If I want to throw out
part of my working directory, I use `hg revert --interactive` or `hg shelve
--interactive` depending on how permanent I want the throwing out to be. This
uses the exact same curses interface as `hg commit --interactive`. I also
rebase (i.e. change the base only, not rewrite the commit), fold/squash, and
edit commit messages, authors or dates as needed. The best part is this all
propagates metahistorically via changeset evolution (available now as an open
beta on Bitbucket). It's all really nice, and I wish all of the Mercurial
workflows for polishing commits were more well-known.

------
znpy
I'd go the opposite direction: commit as often as you can. Ideally after each
micro-iteration, when something new is working.

This way, at the end of the day you can just rebase the whole branch and
squash all of the micro-commits in a whole commit implementing the whole new
features.

It's nothing fancy, just rebasing (which is very very easy in git).

~~~
blauditore
It's not so easy if you have changes belonging to different commits that are
close together in code (e.g. affect the same lines). For instance, if you need
to adjust formatting in a place where also functionality will change, it's
extremely tedious to rebase if you have logical changes before and after the
formatting commit.

~~~
fragmede
Sadly, you're right, but while AST-based diff (vs text-as-code based) has yet
to become mainstream, code formatting standards date back to the second ever
programmer, and some of those are for good reason.

To use a trivial example, Python allows trailing commas for list items, eg:
[1,2,]. With diffing in mind,

    
    
        list_o_things = [
            thing1,
            thing2,
        ]
    

then, the diff to add "thing3," is a single line , rather than two in order to
add a comma to the preceeding line.

The solution, then, is the code should be "properly" formatted at all times,
to avoid running into a situation that goes formatting -> logical change ->
formatting leading to the problem you describe.

There are similar hacks, where git's diff implementation supports ignoring
white space, and doing word-based (vs line-based) diffing, but what we're
really after is AST-based diffing tool(s).

~~~
majewsky
> The solution, then, is the code should be "properly" formatted at all times

This is why I love gofmt(1).

------
akkartik
Yeah, I remember a time when I never used to remember to commit at the right
places, and I'd end up with a pile of work I had to laboriously sort through.
I ended up writing a little bit of VimScript that helped me get into the habit
of committing in the right places.

The crucial idea is to make the changelog a first-class file that I can see
and interact with in my text editor alongside my code. As I bounce around my
text editor, the changelog keeps popping up in front of me, making sure it's
never too far from my thoughts, and encouraging me to write in it. Everytime I
save the changelog, editor automation records my latest update to the
changelog as a new commit message. The changelog now becomes conversational, a
record of my thought process as I work through a problem. Commit messages go
from cacophonous to harmonizing.

Over time I stopped needing these training wheels, but they were very helpful
for a couple of years. More details:
[http://akkartik.name/codelog.html](http://akkartik.name/codelog.html)

~~~
fiatjaf
Great solution.

------
siddharthgoel88
I loved this concept. I understand that practically we generally first write
the whole code and then think about commits. By my manager always says "Coding
is the last and easiest part of the job. Hard and creamy part of the job is to
think about solutions and choose the best one among them." And this approach
(given in dev.to) of thinking of the commit messages before actually coding
gives a framework to that thought. That means before hand one is clear what to
do (definitely during actual implementation we will find some extra nit-picks
here and there and will add some extra code).

So with this style one can follow the style :-

Jot down the requirement/problem ==> Think of all solutions for the problem
==> Evaluate solutions ==> Choose best one ==> Jot down commit messages before
hand ==> Implement solution ==> Use "git add -p" to separate logical units of
your commit as per your commit messages already prepared ==> Tada !!

------
dvcrn
I have a bash alias which just commits to my branch with "wip" and execute
that regularly. When I'm done with my feature I rebase all commits and rewrite
the message in the angular commit format with a thought through message and
summary + ticket references

I have a rough history of what changed in case I need to revert but before
actually merging the branch I want to make sure all is cleaned up for future
reference.

I found it surprising how little people do that and just push with poor
ungrounded messages

Similarly I am a very big fan of the phabricator flow with their arc cli which
kind of works very similar

------
yeukhon
Basically a lot of devs including myself tend to write a lot of code, then
wait until the code is ready then we decide how to commit. Part of this is
just we are focused on solving the problem, the other half is we like the
commit to contain working code and code that looks pretty to read. Who would
want to review code that's messy? I don't mind reading code that does not work
for the sake of giving feedback whether one is heading in the right
approach/direction.

I've known people who don't care about how good the code is, they just keep
committing, stating that's okay, because they will make sure at some point,
most likely the head will contain fully working and tested code. When they
merge they don't squash commits, they just merge because they believe full
history instead of the glorified end state. I only do this to my personal
project which I really don't care if the project ever takes off or if I am
starting out the project for the first few weeks. I just need to get my
project working.

I usually take a trade off. I use the --patch option often to split my changes
into logical commits. I also try to make sure when I have a working code I
commit and if I need to fix a small typo here and there I just squash and
rebase. For slightly larger change I can add that changeset as a new commit.

I recommend folks to get used to using gir add -p option. It isn't hard to use
the 'e' (manual edit) function, since the 's' split option cannot split lines
further for something like:

    
    
        +line1=2
        +foo='this is food'
    
        This line still here
    

You use 'e' mode to split.

------
d--b
I'm using Visual Studio to both code and commit. In there is a "pending
changes" window that allows me to cherry-pick the changes I've made and make
small consistent commits instead of a big one.

I'm not sure if such a UI exists for other coding environments, but I
definitely wouldn't bother doing it if I was using command line to commit.

~~~
fiatjaf
That's a great thing about Visual Studio. Difficult to do in a shell/vim
environment.

~~~
jononor
`git add -p` allows to interactively stage changes per chunk quite easily.

The '-p' flag is also supported on reset and checkout commands.

------
tombert
I have a basic script I wrote that uses inotify and commits on every save.
Then I can easily just use the interactive rebase and squash commits together
and have a clean history.

~~~
fiatjaf
WHAT? ON EVERY SAVE? This is not serious, is it?

If it is, what do you assign to each commit?

~~~
tombert
Yes, every save. The commit message is a timestamp and the file touched, which
is usually enough for me to figure out what I did there, making the squashing
relatively straightforward.

This isn't as bad as it sounds because git has tools built in to handle
merging commits together and deleting unwanted commits through `git rebase -i`

------
Myrmornis
This article exemplifies something I see novice programmers doing which it
would be good to avoid. Basically, it places too much emphasis on "commit".
This attitude does indeed lead newcomers to commit rarely. I think the word
itself doesn't help; we shouldn't think of it as "commit", but rather "quick
painless snapshot". The milestone beginners should aim for is where they
commit frequently, and come to see the git UI as the way to change the state
of their code (rather than the undo button in the editor!) If reworking
commits were easier, people wouldn't think they had to be so perfect and
they'd commit more. Magit can solve this, even for non emacs users.

------
sb8244
I don't plan my commits but think it's a good idea for certain types of
developers.

I find that testing really helps me define commit borders. For instance, I'll
often commit once each new endpoint is tested, new class (if small) is tested,
etc. Testing is great because it defines a natural place to commit (once the
tests pass and the tests describe the functionality). However, some things
aren't tested (shame on me) and I find myself saying to commit once something
is working.

Keeping a clean working tree is _critical_ to me. As is using a visual commit
tool. I often see better commit messages and chunking done with visual
committing

------
jacques_chester
I find that pairing is good for this. One of us usually chimes in about
whether we've reached a good commit point.

Sometimes we'll keep a running list of things we want to loop back to. For
example, a rename to cuts across multiple files, some unrelated. I like those
to be in separate commits for a cleaner history.

While it is, as others have pointed out, sometimes easier to commit "messily"
and clean up the history.

This is very helpful in some cases, but I see it as tactical and not
strategic. In particular it is easy to go far enough down a road that it
becomes difficult to disentangle what ought to be separate commits, especially
because you need to go back and run the tests on each independently before
committing to master.

So if I see a decent commit point (the tests pass!) I will prefer to take it
then and there, before turning to refactoring or to the next step in the work.

On those occasions where the ultimate solution was so unclear that we wandered
around on a WIP branch for longer than a day or two, I sometimes forced into
what I call Athena Commits (they spring fully-formed from the brow of Zeus).

I tend to hold to the short headline, but typically I find we write longer
narrative blocks and then longer dotpoint lists of subsumed changes. I dislike
this position but, if I have to do it, then my successors to deserve to know
why it happened and what, in an ideal world, the shorter steps in smaller
commits might have looked like.

One last tip: always _always_ use `git add -p`. I've caught so many commented
lines and half-baked thoughts and things intended for later commits this way.

------
alangpierce
I've seen a lot of people take the approach where they commit all the time
with little things like "fix typo" or whatever, then clean it all up before
sending to code review. One downside to that approach, though, it that it can
make it harder to organize your work into small coherent chunks, each of which
is a self-contained step that can be its own code review. My preference is to
instead have one commit per code review, and use "git commit --amend" to make
changes to those commits.

If you're worried about losing work with the "git commit --amend" approach,
some things to keep in mind:

* Git keeps your history even when you use "git commit --amend"; it's in the reflog. So you can do "git reset HEAD@{1}" to undo an amend operation, etc.

* All JetBrains IDEs (IntelliJ, PyCharm, WebStorm, etc) have a "local history" feature that automatically keeps track of every change you've made to your code, independent of what git commits you made. I've found it useful several times, especially when I mess some git command up. If you're not using a JetBrains IDE, I'm sure there are other similar tools.

------
alangpierce
Another thing I've learned over time is "don't be afraid to introduce commits
before your current working state".

Let's say you're writing code and you need to use component X, and as you dig
into it, you realize it's not factored the way you want. Rather than lumping a
refactor of component X into one code review with all the rest, it's often
better to save your work (using stash or commit), move your git HEAD before
your latest work (using interactive rebase), do the refactor that you want,
and then apply your other work on top of that refactor. If you're doing more
work with component X and realize the refactor needs a tweak, or if you notice
a bug from the refactor, you can go back and fix that commit using interactive
rebase.

When you're happy with the refactor, you send it out as its own code review
with a commit message mentioning "This is preparatory refactoring for feature
Y". Then both the refactor commit and the feature commit are self-contained
and easy for you to look over and easier for code reviewers to understand.

~~~
jordigh
In Mercurial, all of this is a single `hg absorb` command. There's no need to
stash, move the HEAD, or use interactive rebase. Just modify the working
directory and `hg absorb` will figure out which commit back in your stack you
really wanted to modify. The pre-refactored commits are also automatically
marked as obsoleted by the edits, which means they're still there, just hidden
by default, and they know which commits replace them.

Un autre monde est possible!

[http://files.lihdd.net/hgabsorb-note.pdf](http://files.lihdd.net/hgabsorb-
note.pdf)

------
andreareina
I have this problem as well. My solution is to commit on a successful test. I
still miss a few ("alright, now that foo is working let's get bar to work with
it!"), but my cadence is much improved for it.

I also make an empty commit describing what the session's supposed to do, then
amend it when done.

------
kuon
I think it really depends of the project, the team size...

\- When I work solo on a project, I do my "daily commit of work".

\- On an open source project, I squash my commits at the end into one per
"feature" or "issue".

\- In a larger team, I usually work in a branch, then I submit the whole
branch for review, this let people checkout the branch, test it and review it,
without breaking their own workflow. After this, the branch is merged in the
format that make the most sense for the project (usually, but not alway, 1
commit per branch is kept).

For the planning thing, I prefer to create issues (even for my own personal
project).

~~~
fiatjaf
Now you're making "planning" seem like a huge deal. No, sometimes you just
want to do little change, that's what the article is talking about.

In other words: you commit a lot, right? Do you have an issue for each commit?
If not, then you're not using "planning" in the same way OP is doing, and your
comments about "planning" don't apply.

~~~
kuon
If it's not a personal project, I usualy have 1 issue per commit. All my
issues have a #close xxx in the comment.

------
xaedes
I have a seperate git gui open in the background all the time. It shows me all
the changes I made since last commit. So coding without commiting a long time
will result in lots of changes piled up there. When that gets to much to
overview or I feel the need for a savepoint (to do some experimentation) I
commit.

When I didn't commit for a long time (mostly during long refactorings) I break
up the changes into logical groups and commit them seperately using the
interactive commiting feature. Using a good GUI to help me there improved that
workflow a lot for me.

------
fiatjaf
My first memory of browsing a GitHub repository, just a little before I
started using Git (and any version control whatsoever), is one of a repository
that had a brief description of each file in it right in front of it.

Actually, the brief description was the commit message of the last commit that
modified each file, you know what I'm talking about, but somehow in that
particular repository they did some trick to make these commit messages appear
as descriptions of each file.

Or maybe it is just an erroneous impression I had at that time.

------
bluesnowmonkey
More generally, plan your work. For a big change, find valid intermediate
states to get from where you are to where you want to be. Always keep the
build working and tests passing but accept that there may be redundancy or
inefficiency until you finish the series of changes.

Sometimes you have a change partially complete and realize you need to make
another distinct change in order to finish this one. Have the discipline to
stash this change, do that one in its own commit, then continue working on
this one.

------
z3t4
1) What I did 2) Why I did it 3) Any additional documentation and references

I commit when a task is finished, or when I take a break or go home for the
day.

Before I push I make sure that I have not broken anything. If it's still work
in progress I hide it behind a feature flag.

I push after most commits.

Only using one branch, always ready to deploy.

There are many work flows and this is just one, that I use for the current
project. I however find it very simple, it's just commit and push. No fancy
stuff.

------
tomphoolery
Most of my commits end up being "Yep" or "more stuff" or "Fixing tests". But
before I make a pull request and/or push the branch to my remote, I squash
them all together and

What you're saying about planning out/writing out what to do _before_ the
code, however, is still great advice. I end up doing that in the ticket
description so I know what I'm doing before I touch any code.

------
Macha
One of the teams I work on is pretty religious about one commit per PR and
mandatory rebasing, which is a nuisance at times, particularly when one
feature is made up of multiple different steps

e.g. refactor code to allow thing X to be pluggable, implement new alternative
for thing X, load thing X if user has the right permissions all need to be
squashed down to just "implement thing X"

~~~
Androider
That kind of dogma does seem a bit silly, but why wouldn't you split the
commits into separate pull requests instead of squasing, e.g. PR1: "refactor
code to allow thing X to be pluggable", PR2: "implement new alternative for
thing X" etc. The discussion around PR1 can then focus on just the plugin
interface for example.

~~~
Macha
Each PR needs its own branch build and then a CI build after merging. So
there'd be lots of downtime just screwing around with CI builds and resolving
interim merge conflicts.

------
edavis
Over the years I've developed two strategies for working with git that have
helped me quite a bit:

1) Don't introduce git right away. In the early stages of a project, at least
for me, I'm usually making major changes across multiple files. The commits
during this time have relatively little value for me. Only once the basic
structure has stabilized do I run "git init".

2) The git index is your friend. Always be incrementally updating the index in
preparation for a commit. For example, as soon as a new feature has been
written or a bug fixed I'll immediately add the changes to the index. Then
I'll go back through and look for ways to "clean up" whatever changes I made
and then add those changes to the index. Finally, right before committing I'll
run "git diff --cached" and ensure each changed line is the smallest possible
change necessary to achieve what I was going for.

------
systematical
This is too simple, not sure why I hadn't thought about this. I should really
add this to project management with expected commit states during sprints.

------
draw_down
Nahh, that sounds hard. Rebase till it hurts.

