I'm used to thinking of commits as atomic commits--roughly, each commit is the smallest change that atomically makes sense. So you should be able to use the staging area to build up that commit as you find more pieces to make it in. But while I'm slowly hacking away at this commit, I talked with someone else and decided to try an idea which turns out to be a small commit. But I've already got this half-built-up commit that's staged, so I need to commit that to make the new commit, and then somehow reverse the patches and restage the commit (which is something that's well outside my git comfort level).
What hg has is a few different features that make it possible to build up not just a single commit but an entire commit sequence and do operations on that commit sequence (like reorder them). The git staging area is kind of a weak version of that, but it ends up being in limbo: too complex for the simple just-commit-everything model but too simple for try-to-craft-the-public-changelog model.
Edit: the model I adapted to from my hg workflows is effectively commit --amend. Mercurial has an interesting way of doing history tracking such that if I really need to, I can actually follow the history of the "oops, I need to change these commit because I missed a compiler failure" or "I forgot to save this file before committing," which is a feature that git doesn't have. If you promoted amending the last commit, you wouldn't need a staging area to build up a commit, the last commit does it for you already.
> What hg has is a few different features that make it possible to build up not just a single commit but an entire commit sequence and do operations on that commit sequence (like reorder them).
Again, I've never used hg (beyond a simple tutorial), but you can commit frequently with git (every few other changes "WIP") and reorder / fuse / edit later through interactive rebase.
I would highly suggest reading Pro Git [0], a well written book on not only how to use git, but also the best practices, common workflows, and some of the internals in to how git works. Many refer to it as the essential book on git.
To clean up outgoing changes, you can run an interactive rebase of the current branch onto its remote tracking counterpart. This will list all affected commits in a text editor, and allows you to reorder, squash, change the commit message or the content, much like how you describe it works in hg: https://git-scm.com/docs/git-rebase#_interactive_mode
I'm confused about what you think --amend is lacking. If you want a full history including amendments, why not make real commits? In case you need it git can track the original edit-showing history of something too, it's just awkward. Do you think more people would use it if it was easy?
Also if you really want to uncommit and restage it's a simple command to write down. (reset --soft HEAD^)
I recommend learning about the combination of `git gui` (including its amend option), `git rebase -i`, and `git commit --squash=/--fixup=` (and don't forget to set `git config --global rebase.autosquash true`).
The workflow in Git could still be improved: `git gui` should really allow you to create "squash!" commits more comfortably, for example (I often end up staging with `git gui` and committing on the command-line).
Also, it would be awesome to have an editor in which you could make edits to selected commits directly in a rebase-like fashion. But I don't think that exists for any version control system out there, and getting the UI right for dealing with conflicts would be quite challenging.
The other is that it destroys history, you change the record of what actually happened to the version that is a plausible simplification in the opinion of git's diff/merge heuristic. You then can't then go back for to look for explanations of bugs or test code changes, which change happened first or whether a fact claimed by a commit message was really true at the time, or where is some change a merge mess-up or considered change.
Rebase also really complicates the mental model of git you have to work with.
I appreciate though, that there are cases where you want to withhold the record of what actually happened and use squash/rebase, it's a type of privacy from others. But simply grouping together commits into one is not a good reason to do it, that's what merge commits are for after all.
How is this hard to recover? You have full control of both copies, the system protects you against data loss, and you can easily pick one of the two, rebase one against the other, etc. to recover.
> Which can happen in many situations, eg github merge button, even without other people involved
This is the real problem and it's DVCS agnostic. If you make a bunch of changes to the same code without staying current, a human is going to have to reconcile it. If you follow recommended practice and update your local changes against the shared upstream regularly, this is a far more manageable problem — and that's true for every version control system in existence.
> The other is that it destroys history, you change the record of what actually happened to the version that is a plausible simplification in the opinion of git's diff/merge heuristic.
More correctly, you change it to the version as presented by the human who made the decision to rebase. If someone chooses to remove important context you can have problems but that's the same category of social problem you'd have with someone who uses poor commit messages, makes commits which are incomplete, etc.
> Rebase also really complicates the mental model of git you have to work with.
I find the opposite to be true. Most of the people I've taught seem to quickly grasp the idea that a rebase is simply taking your set of changes and moving them to apply against the current shared consensus rather than that state when you started, whereas merges cause regular confusion during code review or conflicts when people are asked to reason about changes someone else made.
Git stash
> What hg has is a few different features that make it possible to build up not just a single commit but an entire commit sequence and do operations on that commit sequence (like reorder them). The git staging area is kind of a weak version of that...
The git way is to make a new branch and then use git rebase -i
git add -p # select what to add to the staging area
git reset -p # deselect chunks that I decided I don't want anymore
git stash # saves your working state in a temporary commit (not in your branch)
git stash pop # restores the working state from the last git stash command and drops the temporary commit
git rebase -i $start_point # where $start_point is either a branch or some commit in your branch history. -i means interactive, which allows you to reorder/edit/reword/squash commits
git checkout -b $newbranch $start_point # you can make a new branch at any commit in history, so if you decide to reorder your commits, then declare and older commit as a branch and get it merged first, that's fine. $start_point is optional, if unspecified the current HEAD commit is used.
For your specific example, with a staged partial commit on branch idea-1, I would: git commit -m 'WIP idea 1'
git checkout -b idea-2 HEAD~1 # makes a new branch at the parent of the HEAD commit
git add -p # pull in the desired changes
git commit -m 'idea2 implementation'
git checkout idea-1
git add -p # stage the rest of the changes
git commit --amend
git rebase idea2 # if idea 1 depends on idea 2
I do wish git had some notion of sub-commits with their own messages, and a better UX for cleaning up a set of commits before merging them.You could use the "fast-forward with merge commit" style. [1] Then you can treat the commits from the branch as sub-commits, and the merge commit as the parent commit.
[1]: https://stackoverflow.com/questions/15631890/how-to-achieve-...
A clean git log view that shows only meta-commits
Git blame shows both the meta-commit and the sub-commit for each line
I suppose I could get creative by enforcing that tags be embedded in commit messages and then filtering them in the log view, but it would be better if it was standard. git log --merges
However, I don't know if there's any way to achieve that with git blame, and not all tools may offer that kind of log view.Does rebase -i handle this adequately or am I misunderstanding what you're describing here? My workflow sounds like exactly what you're saying here, where "subcommits" are my development commits and the UX is the rebase -i editor.
Just:
- edit files - git add -e - optionally git diff --staged (to review what will go in a commit) - optionally lather, rinse, repeat as needed - git commit
Alternatively:
- edit files - git add - git commit - lather, rinse, repeat - git rebase -i origin/master (or whatever) and reorder/squash/edit/split commits as needed
For the latter's last step, sometimes the easiest thing to do is to squash all commits when you're done, then git reset HEAD^, then apply the git add -e && git commit loop in order to break up your work into logical commits.
When done, push.
It doesn't even have to go away. (And on that note, I'm bummed that the conclusion in the article is to do that, especially because everytime this comes up, there's an uproar.) But, jeez. Just tuck it away so futzing with it is an opt-in decision for the 5% of the time that you need to make sure you aren't committing all of your changes. (And on note #2, if a person is doing partially staged changes more often than that, my question is why? What kind of chaotic, stream-of-consciousness style of development leads to that, and is it even producing good results? It doesn't take especially careful planning to not end up in the scenarios where partially committed changes is the answer.)
EDIT: I suppose I have to use it to add new files :)
It's quite common to tell newcomers to always `git commit -a`, and `git add` for adding new files. That's pretty much exactly where SVN is/was, and nobody complained about that being complicated or onerous either.
I use git status -s and git diff quite a bit as a sanity check (for things like whitespace) and to do first party code review before I send anything out.
The staging area is a core part of my personal git workflow pre-commit. It seems to be producing good results so far.
You can trivially replicate that without staging by amending the HEAD commit, or building a bunch of partial commits then folding them with rebase -i. You can even do that automatically by committing with --fixup or --squash
Errr… no you're not. `commit -p` exists.
The basic problem is that git was designed to solve Linus’s problems, and Linus’s problems are not your problems. However, because of network effects, most of us end up using the same tool.
If I’ve got a big change which I want to split into three small internally consistent changes, with each change overlapping in many files, how in the world would --amend help?
> I like the ability to make a big, complex change and checkpoint stable portions (subsets) of the work as I go.
However, it does not replace all use cases of the staging area, which is why I gave it only as an example.
Of course, `commit --amend` with `-p` could solve your problem as well.
Alternatively, “create a new, 'pending’ commit, without a commit message, don’t point HEAD to it yet, and change most of the commit-related commands to modify this commit until it is finalized” also solves all the use cases of the staging area, because that is what the staging area is, but it is presented in a much more confusing way and ends up being yet another new concept for git beginners to grasp.
After thinking about this more, I find myself agreeing that in terms of beginner friendliness, the combination of incredible name confusion and the relative paucity of use cases where it’s necessary make the staging area an interesting area for significant change in the git defaults.
I disagree with your statistic though - I can't tell you what the flag is precisely because it's almost never what I want; so I never use it and haven't learnt it.
I think these things just vary with workflow.
git commit -a
It does an implicit git add -u (which does not add new files not yet part of the repo) 161 g cap # commit --patch --amend
170 g adu # add -u
205 g add -p # add --patch
277 g ci # commit -a
484 g cip # commit --patch
At least, I use it a lot. ;)There are ways to do this using stash but if you stash before you commit, you get merge conflicts when you pop the stash. The workaround I've seen is to commit before you stash, but that means you're also committing before you've actually tested that change, which seems pretty broken, IMHO.
When you're done:
git checkout master
git merge --squash <branch_name>
git commit -m "<Real commit message here>"
If you have lots of unrelated uncommitted changes in the current repository and want to split up the commit, how can you easily check if the changes passes the test suite? With all the other unrelated changes it can be hard to make sure that only relevant changes becomes part of the commit, and that they don't result in regressions. This script clones the repository to the directory ".testadd.tmp" in the current directory and applies the staged changes there (unless -u/--unmodified or -p/--pristine is specified), chdirs to the same relative directory in the clone and executes the command specified on the command line there.
It's available from
https://github.com/sunny256/utils/blob/master/git-testadd
and the test suite (place it in a subdirectory below the script) is at
https://github.com/sunny256/utils/blob/master/tests/git-test...
It's stable, and I've been using it almost every day for 1.5 years. It also works with binary files.
I'd be happier if the staging area was handled at the filesystem level, give me a directory which at all times contains the staging area, so I can test it.
While currently this workflow is clunky, it could be made to work with the same nice semantics of your workflow, and not have a staging area at all.
I have seen people get in trouble with rebasing and generally while manipulating history but never with the staging area.
I think that's what mercurial tries to solve, it makes manipulating history safe (by tracking the state of the commits, whether they were published or not for example, and always keeping all the history but hiding it after it was rewritten).
More importantly, the article is about the staging area being there by default.