Hacker News new | past | comments | ask | show | jobs | submit login

Coming from someone who learned hg well before git, and who's now being more or less forced to use git long after developing comfortable hg workflows, the staging area feels like a half-baked implementation of what it's supposed to be doing.

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.




Some useful commands I'd use in scenarios like this are:

  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.


> I do wish git had some notion of sub-commits with their own messages

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-...


Interesting! That's almost what I want, except here's the specific behavior I want 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.


You can show only merge commits with

    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.


> 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.

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.


See my reply to mmebane about sub-commits. Re: UX, specifically the patch editor (git add/reset -p) is sometimes difficult to use. For example, I've had issues with changes that happen around empty lines refusing to apply for unknown reasons.


With git, you can stage line by line, file by file. You don’t need to commit everything at once. Also, you can add the lines you want in to staging, then use the stash feature to temporarily save lines that you don’t want in the commit. This allow you to test the commit without all of the extra lines you don’t want.

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.

[0]: https://git-scm.com/book/en/v2


I don't know mercurial, and I agree about the limitation you mention for stashing the index. That said if I need to do a refactoring or something else while I have an unclean tree, I stash before doing it, then commit it, and then unstash my work.

> 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 think people generally get side-tracked by focusing too much on the staging area. It's a red herring in my opinion. I barely notice that it's there, and I do the crafting-the-public-changelog thing on an almost daily basis.

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.


Resorting to rebase is a pretty heavy price to pay though.


From my experience, there's no such a heavy price as long as you don't rebase what you already shared with others.


How so? It's very fast and you can abort if you do it wrong.


Well, the obvious and immediately painful one is the hard-to-recover failure mode when you discover something you rebased wasn't private after all. (Which can happen in many situations, eg github merge button, even without other people involved).

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.


> Well, the obvious and immediately painful one is the hard-to-recover failure mode when you discover something you rebased wasn't private 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.


Stashes should cover your first scenario; put those changes (both staged and unstaged, optionally also untracked files) in a bag and re-apply them when you are back from your short expedition: https://git-scm.com/docs/git-stash

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


Problem with stashing is that you then need to redo the work of staging afterward.


You only need to git stash pop and the staging area is back as it where, there is no need to redo the work of staging.


you can --keep-index to avoid stashing it, and then make an additional stash. If you have modifications within the same file it'll complain about conflicts though, so on the second sash you'll have to `git stash show -p | git apply -R` and then drop the stash, which is super clunky.


Stashing saves/restores the staging area so you shouldn't need to redo anything.


> 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.

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^)


The staging area is trivial to use effectively.

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.


> 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).

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


You should look up the stash command.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: