threads / rfc / 43110

Re: [RFC] Introduce "git stage" (along with some heresy)

Subject: Re: [RFC] Introduce "git stage" (along with some heresy)

## tl;dr

5 messages between Dec 1, 2006 and Dec 2, 2006.

replies: 4people: 4as markdown or json

Carl Worth· Dec 1, 2006, 17:36 UTC · lore

[RFC] Introduce "git stage" (along with some heresy)

[This message, (yes,another long one from me), proposes 3 changes. The first should be uncontroversial I think, while the second and third are clear heresy, (and the second would require some amount of re-training or re-configuration by existing git user). Pick and choose as you see fit. I don't think they actually depend on each other, though I'll present them here as parts of a whole.]

Change #1: Add "git stage" command, use "--staged" instead of "--index" ======================================================================= If we're going to start describing the index as a "staging area" let's make the command set reflect that as well. I propose a new "git stage" command that is intended for human use when wanting to do a staged commit.

Then, a few other commands that currently have --index or --cached arguments could switch to --staged as well.

With this change here is a summary of some of the primary git commands (that are relevant to the current discussion):

add		Shove a file's contents into git's staging area
stage		Shove a file's contents into git's staging area
rm		Remove a file from git's staging area
diff		Show what's changed in working tree compared to
		staging area
diff --staged	Show what's changed in staging area compared to latest
		commit
commit		Create a new commit from the contents of the staging area
commit -a	Update the contents of all files in the staging area,
		and create a new commit from the new staging area
commit files...	Create a new commit that differs from the latest
		commit only in files... (which get new content from
		the current working tree). Staged content of other
		files (if any) will not be committed.

I hope that so far (in this email) I haven't said anything very contentious. This is basically just a summary of the existing behavior with things like "update-index" and "--cached" changed to "stage" and "--staged".

The introduction of this new "stage" command would be a very minor change. If you're not particularly picky about names, it might be seen as having no impact at all, (or even slightly negative since "add" and "stage" could be considered equivalent). If you are picky about names you might consider it slightly better to "add" when adding a new file and to "stage" when you want to put some content into the staging area.

OK, so now let me start in with my heresy[*].

To start with I'd like to group the above command into two groups such that one can be understood without a need to understand the purpose of the staging area. Note: the goal here is not to lie about the staging area. It will still be mentioned in the documentation for any command that needs to mention it, but in a way that a user can easily ignore those portions at first. So the grouping is:

Without staging --------------- add rm diff commit -a commit files...

With staging ------------ stage diff --staged commit

So far, that's just a re-grouping. No names or semantics have been changed.

Change #2: Make a staged commit an explicit act =============================================== The "-a" stands out to me here as the only command-line option needed in the first list, and the only command in the second list that performs a staged operation by default. So change number to is to redefine "commit" to mean what "commit -a" meant before and to require a new command-line option for staged committing, (the best naming I have so far is "commit --staged" with a shortcut of "commit -i"---the mismatch of "'i' as short for --staged" is a bit unlovely I admit).

Here's what we have after change #2:

Without staging --------------- add rm diff commit commit files...

With staging ------------ stage diff --staged commit --staged (or "commit -i")

Change #3: Change "add" to not stage any content ================================================ To finish off, I'd like to propose descriptions of the commands to allow the user to use the "without staging" commands as a complete set while being able to easily ignore any of the staging capabilities. This does trigger a need for a semantic change in the "add" command. Here are the proposed descriptions:

Without staging --------------- add Add a file to be managed by git

rm		Remove a file to no longer be managed by git
diff		Show the changes in the working tree compared to the
		latest commit, (or compared to staged content, if any)
commit		Commit the current state of all git-managed files
commit files...	Commit the current state of the specified files
With staging
------------
stage		Shove the current contents of the specified files into
		git's staging area
diff --staged	Show the changes in the staging area compared to the
		latest commit

commit --staged Commit the state of the current staging area commit -i

To make the above work, I think Daniel's suggestion of making "add" put 0{40} into the staging area should work just fine. I know that Linus has religious objections to these proposed new semantics of "git add". One response there is to just consider "add" to be a mud-pit command for people to wallow in that really want it, (like Linus' proposed "ci" command). If you don't want to be in that mud-pit, then just use my "stage" command along with "commit -i", (or with "commit" and some configuration option, or with "commit" and a rejection to my change #2).

Another response is that these new semantics for "add" really aren't any worse than other existing things in git, (for example, "git rm" isn't just updating file content into the index---because it even leaves the file around by default). [Actually, the fact that "git rm" doesn't delete the file by default is a bug (and it's my bug). I think the right thing is that "git rm" should be defined as always deleting the file from the working tree, and that it should be fixed to fail if the file if the file is dirty, (unless -f is passed)].

Other examples of the current semantics of git commands being just as "evil", (I would argue "usable" instead), are below.

I think that here, finally, I've made my proposal as clearly and consistently as I can. I think the above would only improve git, (by making it easier to use by new people, while still providing a consistent model and a way to easily learn everything git has to offer). Change #2 would be the hardest pill to swallow since it would mean some change in the habits of existing users, (the other changes could largely be blissfully ignored by trained git users I think). This difficulty could be softened with a configuration option something like core.commitStagedByDefault, or this one change could be rejected.

-Carl

[*] I say heresy, but I think all the talk about "inconsistency" and "dishonesty" in the proposals I've been making are really misplaced. The easiest way to see that is to apply the same arguments to existing commands in git and see that they are already inconsistent and dishonest.

Inconsistency ------------- If the consistent model is "'commit' commits the contents of the staging area" then what in the world is happening in the case of "commit files..."? There's really no way to describe that operation in terms of the staging area, because it simply ignores it. The closest you could get is to describe the internal implementation in detail:

commit files... Creates a temporary staging area from the latest
		commit, shoves the content of the named files into
		that temporary staging area, creates a new commit from
		that and then does [something] to the original staging
		area.

I (obviously) botched that. Somebody could write an actual, correct technical description. But you know what? It would be totally useless. It's really hard to describe what the current command does in terms of the staging area and nobody would care anyway. It wouldn't help anybody use the thing. The fact that all commit operations _do_ involve a staging area at some deep point in the implementation is totally irrelevant to the fact that what "commit files..." does do _is_ desirable, and is not hard to explain at a conceptual level. What the current documentation has is:

	"Commit only the files specified on the command line."

This documentation doesn't say _anything_ about the content coming from the working tree rather than the index. But that's _obviously_ the correct place for the content to come from, and that's what's implemented.

Dishonesty ---------- The argument here is that some "easier to use" commands lie to the user, giving them an incorrect idea of what's really happening, and that this will create barriers to later understanding. I think the same argument could be applied to say that there's no reason to have "add", "rm", "resolve", and "update-index" (or "stage"). These commands are all doing the same thing at a technical level, so why lie to the user and let the user think they are doing something different? My reply is that this isn't a lie, but it's providing names for the user that match the operations that the user is conceptually doing. That's called "providing a usable interface". If the user goes on to learn the internals and discovers that these are all wrappers around some shared core command, then the user can appreciate that elegance of implementation. But forcing everyone to _use_ one command for these conceptually separate arguments would be a mistake from the point-of-view of usability.

Carl Worth· Dec 1, 2006, 20:03 UTC · re: Carl Worth · lore

Some thoughts on resolving conflicts

On Fri, 01 Dec 2006 09:36:00 -0800, Carl Worth wrote:
Show 5 quoted lines
> To finish off, I'd like to propose descriptions of the commands to
> allow the user to use the "without staging" commands as a complete set
> while being able to easily ignore any of the staging capabilities.
> This does trigger a need for a semantic change in the "add"
> command. Here are the proposed descriptions:

By the way, back when we used to call it the "index" one of the things that was often mentioned as a reason not to "hide the index" is that the index ends up being so important during the process of resolving a merge.

This is extremely true, and this is where git really starts to shine. I've seen people get really put off by the index when they first encounter "commit -a" or a messages instructing them to "update-index" something. But, if people are properly presented with what git offers for helping with conflict resolution then I think they will fall in love with it.

But I think "hide the index" vs. "celebrate the index" frames the debate entirely wrong. It's not a matter of "working tree" vs. "index" being the king. The king is what the user wants to accomplish and what does git offer to help with that.

So, for example, in the case of conflict resolution, what git offers in an iterative process involving:

	git diff	Shows what still needs to be resolved
	git resolve	Indicate to git that conflicts are resolved in
			the specified files

(Yes, I'm assuming a future "resolve" synonym for update-index here) And finally, "git commit" when complete. This is a fantastic sequence since it fits what the user wants to do and helps the user do it, (and the incremental nature of it is helpful for large conflicts).

Note that I don't think it's important whether the final "git commit" executes a "commit -a" or a mode traditional commit-the-index. It would be exceedingly rare for someone to want to make a partial, staged commit during conflict resolution. So I think that special case can be entirely ignored when considering the user-interface.

So git provides tools well suited to the job here. One thing it doesn't do well is to advertise them to the user. It would probably be helpful to print some small section of advice and guidance when the conflict happens. Right now, git spews a lot of scary internal state that definitely gives the impression of things going wrong, and doesn't tell the user much about what to do. It would be nice to say something more along the lines of "A conflict occurred during the merge attempt. That's nothing to worry about---it happens sometimes. And here are some tools that git offers to help you fix things up:..."

And there are other lovely things that git provides, such as:
	git log -p --merge	Shows commits that contributed to this conflict
	git diff --ours		Show changes in working tree compared
				to our latest commit for unresolved files
	git diff --theirs	Show changes in working tree compared
				to the commit being merged in for
				unresolved files

To tell the truth, I hadn't really played with "diff --ours" and "diff --theirs" much before. They're right handy! I can't find any documentation for them, (it might exist somewhere deep in the plumbing documentation but I can't find it). It would be great to have examples in the "git diff" page showing these off, and maybe some hints in the message that comes from the conflicted merge.

So the above commands are wildly useful. But there not useful because "the index is an essential part of git", they're useful because they help the user get information related to what the user is doing.

One command that I didn't find in my experimentation was how to see the multi-parent diff after resolving the conflict. I found that I can do a single-parent thing with:

	git diff --cached	Show changes in staging area compared
				to our latest commit.

But I didn't figure out how to get the multi-parent thing there yet. After I make the commit object I _can_ see the result I want with "git show", but it would be nice to be able to see that before the commit.

Surely there's a command-line option somewhere that does this, with a name like -cc or -C or something, but it's something that I would argue should acquire a different name---that is, if I were doing conflict resolution often. As it stands now, I rarely have any conflicts to resolve, so any user-interface warts that git has here haven't rubbed me the wrong way yet. Other than the conflict spew which gives the impression of "Git tried (multiple ways) and failed to merge this mess. You're on your own now."

-Carl

PS. Here's the example I just used to experiment with conflict resolution. It's something like this that would be nice to have in something like "git tutorial conflict-resolution" which would run the following sequence of commands for the user and then invite the user to play with things like "git diff --ours" and "git diff --theirs".

The commands below look really ugly, so we definitely don't want the tutorial reader to ever have to go through all this state-creation. But the end result---what the user sees in "git diff" is so intuitive that it would be wonderful to have this kind of thing readily available at the command-line.

A more ambitious example might setup conflict in multiple files to teach the incremental nature of using "git diff" and "git resolve" together.

mkdir git-tutorial-conflict-resolution cd git-tutorial-conflict-resolution git init-db echo 'vvv Context paragraph 1 vvv This is a paragraph that exists for context. It will be unmodified in both branches. ^^^ Context paragraph 1 ^^^

This is a paragraph that I will modify in master and delete in other.

vvv Context paragraph 2 vvv This is a second paragraph that exists for context. It too, will be unmodified in both branches. ^^^ Context paragraph 2 ^^^

This is a paragraph that I will delete in master and modify in other.

vvv Context paragraph 3 vvv This is the third paragraph that exists for context. Again, it will be unmodified in both branches. ^^^ Context paragraph 3 ^^^

This is a paragraph that I will modify in two different ways in master and other. ' > file git add file git commit -m "add file" git branch other echo 'vvv Context paragraph 1 vvv This is a paragraph that exists for context. It will be unmodified in both branches. ^^^ Context paragraph 1 ^^^

This is a paragraph that I have modified in master.

vvv Context paragraph 2 vvv This is a second paragraph that exists for context. It too, will be unmodified in both branches. ^^^ Context paragraph 2 ^^^

vvv Context paragraph 3 vvv This is the third paragraph that exists for context. Again, it will be unmodified in both branches. ^^^ Context paragraph 3 ^^^

This is a paragraph that I have modified in master. ' > file git commit -a -m "master modifications" git checkout other echo 'vvv Context paragraph 1 vvv This is a paragraph that exists for context. It will be unmodified in both branches. ^^^ Context paragraph 1 ^^^

vvv Context paragraph 2 vvv This is a second paragraph that exists for context. It too, will be unmodified in both branches. ^^^ Context paragraph 2 ^^^

This is a paragraph that I have modified in other.

vvv Context paragraph 3 vvv This is the third paragraph that exists for context. Again, it will be unmodified in both branches. ^^^ Context paragraph 3 ^^^

This is a paragraph that I have modified in other ' > file git commit -a -m "other modifications" git checkout master git pull . other

Marko Macek· Dec 1, 2006, 20:44 UTC · re: Carl Worth · lore
Carl Worth wrote:
Show 6 quoted lines
> [This message, (yes,another long one from me), proposes 3 changes. The
> first should be uncontroversial I think, while the second and third
> are clear heresy, (and the second would require some amount of
> re-training or re-configuration by existing git user). Pick and choose
> as you see fit. I don't think they actually depend on each other,
> though I'll present them here as parts of a whole.]
/me likes (both emails).
Q: should git-commit require either -a or --staged when there is a
staged commit?
Mark
Sam Vilain· Dec 2, 2006, 21:03 UTC · re: Carl Worth · lore
Carl Worth wrote:
Show 9 quoted lines
> Change #2: Make a staged commit an explicit act
> ===============================================
> The "-a" stands out to me here as the only command-line option needed
> in the first list, and the only command in the second list that
> performs a staged operation by default. So change number to is to
> redefine "commit" to mean what "commit -a" meant before and to require
> a new command-line option for staged committing, (the best naming I
> have so far is "commit --staged" with a shortcut of "commit -i"---the
> mismatch of "'i' as short for --staged" is a bit unlovely I admit).

I wonder about backwards compatibility, but then another part of me says that porcelain are probably using "git-commit-tree" anyway.

How about considering alternative words? Like "git save" for this higher level and more user friendly interface.

As another idea (brainstorming here), what about an "autocommit" approach?
  git rm       # removes files and asks for commit message
  git add      # ditto
  git commit   # updates and commits everything
  git stage    # starts a staged commit
  git add      # modifies staging area
  git rm       # ditto
  git stage filename # adds contents to staging area
  git commit   # saves staging area as commit

Then you could have "core.autocommit" as a repo-config option, defaulting to off for "backwards compatibility".

Show 7 quoted lines
> Change #3: Change "add" to not stage any content
> ================================================
> To finish off, I'd like to propose descriptions of the commands to
> allow the user to use the "without staging" commands as a complete set
> while being able to easily ignore any of the staging capabilities.
> This does trigger a need for a semantic change in the "add"
> command. Here are the proposed descriptions:
The "autocommit" concept may make this less of an issue.
Wink Saville· Dec 2, 2006, 22:33 UTC · re: Carl Worth · lore
Carl Worth wrote:
Show 14 quoted lines
> 
> Without staging
> ---------------
> add		Add a file to be managed by git
> 
> rm		Remove a file to no longer be managed by git
> 
> diff		Show the changes in the working tree compared to the
> 		latest commit, (or compared to staged content, if any)
> 
> commit		Commit the current state of all git-managed files
> 
> commit files...	Commit the current state of the specified files
> 
As a newbie like this entire proposal and especially the above.
Wink Saville

← back to recent threads