I understand your second point, but I have a hard time understanding the difficulty with this part. Why is it hard for people to understand the idea of staging?
You put things in a box one at a time before closing the box. Does it require more explanation than that? What do people find difficult about it?
People often then learn that there is a local file and some remote file: they can cope with a save -> upload workflow. Lots of traditional VCS turn this into a save -> commit workflow.
Git adds two stages to this that people can't see the need for without understanding the internals: an extra step between save and commit, and an extra step after commit.
(The discussion reminds me of all those people who think that if they just start by talking about monads then people will find Haskell easy and natural...)
1. Is my document saved?
2. Are the changes staged?
3. Are the changed committed?
4. Are the changes pushed to my fork on e.g. github?
5. Are the changes merged into the upstream repository on e.g. github?
However, I find the occasions for using the staging area in practice are few and far between, for the simple reason that I can't test and execute the code that's in the staging area without also having the code from the working directory also be there. It feels like after having partially staged some of my working directory, it would be a blind commit with no guarantee that things are working.
Very rare is the situation that I can break out a list of files over here that are for feature A and some over there for feature B, and never the two shall interact.
I think this is probably what most struggle with regarding the staging area, without being able to articulate it.
I do not see why a source control system should make it easier to make a commit that hasn’t ever existed on disk and thus cannot have been tested.
I think the better model would be to stash your changes and have an diff editor between the on-disk working copy and the stashed version that allows you to commit a set of changes as several smaller, more coherent commits.
That wouldn’t guarantee that each of those intermediate commits gets tested or even built, but it would guarantee that each smaller commit is in the on-disk copy at some time.
Not necessarily. One nice option that the git rebase command has is --exec (which can be specified multiple times). So you can run a rebase and have git execute a command (like running a test suite) for each commit in the branch. If any commit files, the rebase process will stop and let you amend the commit to fix the issue.
> or you have to do extra work (stash changes, test, restore stash) to do that.
I've found that it's easier to write and locally test a given feature and them incrementally stage parts of it and create commits before pushing the code up for review. To me, that's easier than just making a large commit and then trying to split it out into a better set of commits after the fact.
For example, I may write a new method and then call it several places in the code. So my first commit would be to add the new method along with its unit tests and my second commit would be to add calls to it in the code base and update the associated integration tests (if necessary).
Almost. Most often it's:
- Working on solving problem A - Notice problem B - Start to solve problem B - Notice I'm getting distracted from A, and return to finish it. - Want to commit my fix for A, but don't want to lose or forget the partial work on B.
Two different approaches I might take in this situation, depending on whether B is related to A.
1. If they are related (eg, B depends on A), use `add --patch` to commit A, then finish and commit B. 2. If unrelated, use `git stash --patch` to stash B, then commit A, then switch to a different branch to finish B.
Honestly, I see the point of both stash and staging, but not both together. Too many tools for the same job. On my long list of projects to do is a git porcelain that combines some of these concepts (eg, stash and working directory which would be tied to a branch):
- Each branch would have a single stash. - When you check out a new branch, all uncommitted changes are automatically stashed. - If the branch you're switching to has anything stashed, that stash gets popped. - Any current workflow that involves stashing can be replicated by using a branch instead of a stash.
This way, branches can be thought of as "state of the working directory", which is more intuitive with the branching tree model, imo; commits are a snapshot of the repo at that point in time; and the staging area is just a way to choose what should be included in those commits.
Git’s workflow wouldn’t even be sane without the staging area. This is what allows you to fix mistakes and make your work presentable for remotes.
I did exactly the same diff/tidy/diff workflow when I used p4 and svn, neither of which make a distinction between "working directory" and "staging area".
P4 and svn don’t have a strict commit parentage, which is why you can push commits in those systems in any order. Git’s strict concept of parentage is what makes the staging area so important for keeping your workflow similar to p4 & svn Workflows. Without a staging area, you’d either have to always fix mistakes with new commits, which is bad, or rewrite already pushed history, which is worse.
The terminology is a bit different - unless configured with mandatory locking (essential for some workflows) you don't have to open for edit. You just edit stuff and it goes in the "default changelist", roughly equivalent to automatic staging.
> Without a staging area, you’d either have to always fix mistakes with new commits
Mistakes at what point? In the normal svn workflow you can review with svn diff, then when you're happy do svn commit; it's just that there's no local place you're committing to. In both cases there's a critical point, either "svn commit" or "git push".
I’d guess you’re learning toward talking about svn, which I don’t remember very well, and I am leaning towward talking about p4, which always does mandatory locking.
You’re right the terminology is different between these different systems, I’m just pointing out that the git staging area has what you can think of as some equivalences in the other systems. Or, you can think of it as tradeoffs. Either way, the git staging area is something that helps you pretend like you’re using svn or p4 in the sense that it helps support editing multiple changes at the same time before pushing them to a server.
> Mistakes at what point?
With git I’m referring to mistakes between commit and push. But there’s a philosophical difference here that I glossed over. With git it’s easier to commit early and often than it is with svn or p4. With svn & p4 it’s easier to lose your work because version control doesn’t know anything about it before you push. If I make micro-commits, which I want and I like, then I put more “mistakes” along the way into my local history, and I can use the staging area to clean everything up before I push. With svn & p4, you make those mistakes and do the cleanup without ever telling the version control, and you run a greater risk of losing that work while you do the cleanup.
At work I’ll hit the squash option on gitlabs merge request which moots all local machinations.
It's also possible to do this without the stash command, by making both commits right away, and testing them later. However, that would involve rebasing(?) your second commit on top of any changes you end up making to your first commit, so using the stash makes more sense to me personally.
The git stash man page talks about this: https://git-scm.com/docs/git-stash
“If you mistakenly drop or clear stash entries, they cannot be recovered through the normal safety mechanisms.”
One of the best things about git is how big the safety net is, as long as you tell git about your changes. Almost any mistake can be fixed, so why use features that aren’t sitting over the safety net?
You're adding a feature to your proggie. That involves modifying the main bits to add the feature and, say, adding a couple of interfaces to internal library modules.
Split out the changes to the library modules into separate commits---it's safe because nothing uses them, they're logically separate from the feature changes (although they don't appear to have a justification without the feature), the log will be marginally cleaner, and git bisect will have more granularity.
Your example is an implementation of the box-putting algorithm, but it doesn't need to be mirrored in the put-box CLI.
put-close-box file1 file2
This command could encompass all the putting and closing. Since you only close boxes when you are done putting things in it, I don't see a need or purpose to split it up. put-box file1 file2
close-box
A closed box (commit) is always going to contain stuff that was put in it, so why separate commands?Anyway, `git commit file1 file2` by itself is most of the way to being the put-close-box function you want; it just doesn't work for adding/deleting files from the repo. Seems like they could make a lot of people happy by closing that gap and letting `git add` be an intermediate-level feature.
But the vast majority do, or at best become perpetual intermediates (https://blog.codinghorror.com/defending-perpetual-intermedia...).
99% of developers out there didn't need a power tool for source control (source control is already quite a power tool many devs can barely handle, even in SVN form...), yet here we are: Git is imposed everywhere, with its horrible UX.
Many beginners will start by always doing "git commit -a" and that's fine, as long as they know there's an alternative once they need it.
Surely, most of the time when you go to commit, it's all the files you've changed?
EDIT: Now that I think about it, I also have several repos where I have changes that I never intend to ever commit them, because they are development conveniences for me personally.
I'm a JS dev mainly working in React on a web app with a backend team using PHP. Often I'll be working on a branch with maybe 2 or 3 people and I often end up working on a few things at a time. Say I'm working on a feature, and I notice some bug I'll fix that and then get on with my feature. Once I go to commit I pretty much always do a 'git add . -p' and I very rarely want to add all the files I've worked on!
Even things like switching a config file to use a service like apiary where I don't want to commit my change to the config to use apiary.. Or change to my webpack config for testing, etc.
I've used Perforce, SVN and Git and the whole 'staging' area thing always felt very natural to me. Here are the files you've edited, which ones want to be commited? It gives me a second chance to go through and check everything before I've commited, and often that stops me leaving in any odd comments or debug code.
What is the usecase where one needs to remember that selection for more than just a few minutes?
In Mercurial, I much prefer to just make it an actual commit in the draft phase (the default phase) and just keep rewriting that commit. Mercurial provides tools for both selectively adding and removing hunks from a commit (both `hg amend` and `hg uncommit` accept --interactive for hunk selection). If you're extra paranoid, you can make it a commit in the secret phase so it's not shared prematurely by accident.
It's pretty much functionally equivalent and doesn't require an extra location in which your code can be. It's either in your working directory or in a commit.
A bonus of this approach is that now you have a meta-history, hidden by default, of what you've "staged" and "unstaged". It's kind of like a reflog but with, in my opinion, a better UI. And of course, the index/cache/staging area in git doesn't use refs, so there's no reflog there.
git commit --verbose --patch
and bypass the staging area entirely. I don't find it helpful.