(This is my second answer to question. On second reading I think the original problem might have been a bit different from the one I first understood.)
I understand the question as you're having a development branch paraller to master
. Usually these kind of branch style is called feature branches and I definitely encourage using those.
One should always try to keep feature branches clean. In practice, you want a feature branch that has commits you would had done if you never made any mistakes. For me, that means committing a lot and later git rebase -i
to fix the mistakes when I later learn about those mistakes.
By the time your feature branch is ready, it should look like
- Add API to do thing X
- Fix existing API Y for corner case Z
- Add feature B using X and Y (works in case Z, too!)
- Improve feature B: do magic stuff E
Instead of
- WIP
- WIP2
- Add API
- Move API to do X
- Add feature B
- On second thought, rename the parameters for X
- Fix feature B
- Fix APi for X
- Fix corner case Z
- Fix corner case Z for API Y, too
- do magic stuff E
- commit missing fILE
If you then rebase your feature branch to latest master
branch, the changes are high that only commit Fix existing API Y for corner case Z
may cause conflicts. If that commit is minimal change to modify existing API then fixing the conflict should be easy. In addition, that conflict only arises if some other commit has modified exactly the lines touched by your minimal change.
If you do feature branches and rebase feature branches instead of merging (my preferred style is to rebase so that fast-forward is possible and then do git checkout master && git merge --no-ff feature-branch-x
and document the whole thing in the merge commit – that allows keeping full history of branch and allows GUI tools to easily navigate around the feature if needed) you definitely want to keep your feature branches clean before rebasing those branches to master
. Not only your rebases will be easier but the history is readable in the long run. (Technically this results in a new commit which has logically the same contents as squash merge would have done but its second parent will point to sequence of commits that have the whole history of the feature branch. Those who prefer squash merges can use the first parent and those who prefer having history can follow the second parent. And you can write the commit message for the imaginary squash merge commit in this new commit.)
So in the above example one could rebase -i <old-enough-sha1>
and the re-order commits as 3+4+6+8, 10, 1+2+5+7+9, 11+12 where +
means squash. Git allows splitting and editing existing commits, too, but it's usually easier to keep commits really small and then squash some of those later. Note that in this example even the original commit number 10 ends up before the original first commit. This is normal and reflects the reality that your implementation was not perfect. That does not need to be stored in version history, though.
In your case, it sounds like you have a feature branch where multiple commits add and remove the same stuff. Squash those commits as a single commit (may end up as no change which is okay). Rebase your feature branch to master only when the feature branch looks clean. Definitely learn to use git gui
or some other tool that makes committing changed lines instead of files easy. Every commit should be a change that modifies a sane collection of stuff. If you add a new feature X, the same commit must not fix existing function Y or add missing documentation about Z. Not even if those changes were made to the same file. To me, this is the kind of stuff that Linus Torvalds meant when he said "files do not matter".