threads / discuss / 43317

Re: Missing features in git

Subject: Re: Missing features in git

## tl;dr

12 messages between Nov 14, 2006 and Nov 14, 2006.

replies: 11people: 7as markdown or json

linux@horizon.com· Nov 14, 2006, 13:49 UTC · lore

Missing features in git

I've been writing some more git docs, and I came across a few things git does that I'm not sure are desirable.

One thing I noticed is that with ref logs, you've just re-invented the CVS problem of associating history with a name. If you want to rename a branch (say, from "mischacks" to something suitable for publication), do you rename the log or not? It's a less virulent form, but it seems like the same disease.

Another minor quibble: AFAICT, "git checkout -f -m" is meaningless (-f overrides -m), but doesn't complain.

One feature that I noticed is missing, is that there's no easy way to do a "git reset --hard" while doing a "git checkout" style merge.

An example of when you'd want to do this is performing a "git bisect" with a local "#define DEBUG 1" change. Particularly if you hit a non-compiling version and need to back up.

  
The only way to do this currently is somethig like:
        git checkout -b temp HEAD^
        git branch -f bisect temp
        git checkout bisect
        git branch -d temp
or the way git-bisect does it
  
        echo "$rev" > "$GIT_DIR/refs/heads/new-bisect"
        git checkout new-bisect || exit
        mv "$GIT_DIR/refs/heads/new-bisect" "$GIT_DIR/refs/heads/bisect" &&
        GIT_DIR="$GIT_DIR" git-symbolic-ref HEAD refs/heads/bisect
  
Either way, it reserves a second branch name, and seems like a bit of
a hack.  I'm already a bit annoyed that "bisect" is reserved but not
clearly documented as such, and note that the branch name "new-bisect",
which we are switched to temporarily, does not match the pattern
"refs/heads/bisect*" that bisect_start checks for.

The only thing I don't know is if this should be a git-checkout option or a git-reset option. The former shares far more code, but the latter might make more logical sense.

  
Personally, I'd prefer if the requirement that HEAD point to
refs/heads were enforced when checking in rather than checking out.
  
Then you could check out an arbitrary version without any of the annoyance
above; I could "git checkout tags/foo" or even "git checkout deadbeef~3".
I wouldn't be on a current branch (which would necessitate changing
"git branch" output), so HEAD would simply contain an object ID directly
rather than being a symlink/symref.

It's a bit of a PITA auditing the code base to find everywhere affected by changing this invariant, but it doesn't conceptually difficult. Is there anything fundamental that this would break?

The above would make a "-b <newbranch>" option to git-commit more useful, but it's not a half-bad idea as it is. It's just one extra command (git checkout -b newbranch; git commit), but it takes a bit of thinking to realize that "git checkout", which normally modifies the working directory, won't touch your ready-to-be-committed changes in

Jakub Narebski· Nov 14, 2006, 14:53 UTC · re: linux@horizon.com · lore
linux@horizon.com wrote:
Show 5 quoted lines
> One thing I noticed is that with ref logs, you've just re-invented the
> CVS problem of associating history with a name.  If you want to rename
> a branch (say, from "mischacks" to something suitable for publication),
> do you rename the log or not?  It's a less virulent form, but it seems
> like the same disease.

Well, it would be nice to have command to rename branches (with its reflog) and to rename tags (even in their packed format).

[...] 
Show 5 quoted lines
> Then you could check out an arbitrary version without any of the annoyance
> above; I could "git checkout tags/foo" or even "git checkout deadbeef~3".
> I wouldn't be on a current branch (which would necessitate changing
> "git branch" output), so HEAD would simply contain an object ID directly
> rather than being a symlink/symref.
And what would happen if you want to checkout other branch? Where the ID
in the HEAD would go to? HEAD just _has_ to be reference to _branch_.
 
-- 
Jakub Narebski
Warsaw, Poland
ShadeHawk on #git
Karl Hasselström· Nov 14, 2006, 15:47 UTC · re: Jakub Narebski · lore
On 2006-11-14 15:53:18 +0100, Jakub Narebski wrote:
Show 12 quoted lines
> linux@horizon.com wrote:
>
> > Then you could check out an arbitrary version without any of the
> > annoyance above; I could "git checkout tags/foo" or even "git
> > checkout deadbeef~3". I wouldn't be on a current branch (which
> > would necessitate changing "git branch" output), so HEAD would
> > simply contain an object ID directly rather than being a
> > symlink/symref.
>
> And what would happen if you want to checkout other branch? Where
> the ID in the HEAD would go to? HEAD just _has_ to be reference to
> _branch_.

The id that used to be HEAD would not be saved anywhere. It would still be reachable from your refs, presumably, just like before you checked it out. (It would not be the case that you had made commits on an unnamed branch that would now get lost, because the tool would refuse to commit until you had created a name for the branch.)

-- 
Karl Hasselström, kha@treskal.com
Jakub Narebski· Nov 14, 2006, 17:15 UTC · re: Karl Hasselström · lore
Karl Hasselström wrote:
Show 20 quoted lines
> On 2006-11-14 15:53:18 +0100, Jakub Narebski wrote:
> 
>> linux@horizon.com wrote:
>>
>>> Then you could check out an arbitrary version without any of the
>>> annoyance above; I could "git checkout tags/foo" or even "git
>>> checkout deadbeef~3". I wouldn't be on a current branch (which
>>> would necessitate changing "git branch" output), so HEAD would
>>> simply contain an object ID directly rather than being a
>>> symlink/symref.
>>
>> And what would happen if you want to checkout other branch? Where
>> the ID in the HEAD would go to? HEAD just _has_ to be reference to
>> _branch_.
> 
> The id that used to be HEAD would not be saved anywhere. It would
> still be reachable from your refs, presumably, just like before you
> checked it out. (It would not be the case that you had made commits on
> an unnamed branch that would now get lost, because the tool would
> refuse to commit until you had created a name for the branch.)

If HEAD would contain an commit ID directly, then you shouldn't be able to commit at all. Not very useful, it is.

-- 
Jakub Narebski
Karl Hasselström· Nov 14, 2006, 17:36 UTC · re: Jakub Narebski · lore
On 2006-11-14 18:15:23 +0100, Jakub Narebski wrote:
> Karl Hasselström wrote:
Show 15 quoted lines
> > On 2006-11-14 15:53:18 +0100, Jakub Narebski wrote:
>
> > > And what would happen if you want to checkout other branch?
> > > Where the ID in the HEAD would go to? HEAD just _has_ to be
> > > reference to _branch_.
> >
> > The id that used to be HEAD would not be saved anywhere. It would
> > still be reachable from your refs, presumably, just like before
> > you checked it out. (It would not be the case that you had made
> > commits on an unnamed branch that would now get lost, because the
> > tool would refuse to commit until you had created a name for the
> > branch.)
>
> If HEAD would contain an commit ID directly, then you shouldn't be
> able to commit at all.

I agree. You aren't on a branch, so you can't commit. (Of course, creating a branch for the commit you're on is trivial: "git checkout -b <branchname>".)

> Not very useful, it is.

Well, as long as you only want to visit the past, not change it, it can be useful. For example, we could skip the "bisect" branch, since you aren't supposed to commit to that anyway.

-- 
Karl Hasselström, kha@treskal.com
Jakub Narebski· Nov 14, 2006, 17:45 UTC · re: Karl Hasselström · lore
Dnia wtorek 14. listopada 2006 18:36, Karl Hasselström napisał:
>
> For example, we could skip the "bisect" branch, since
> you aren't supposed to commit to that anyway.

Well, to have "branch" to which you could not commit, just put ref outside refs/heads.

Another solution would be to be able to put non-head ref in HEAD, but allow to commit only if the prefix is refs/heads/

-- 
Jakub Narebski
Shawn Pearce· Nov 14, 2006, 17:49 UTC · re: Jakub Narebski · lore
Jakub Narebski <jnareb@gmail.com> wrote:
Show 10 quoted lines
> Dnia wtorek 14. listopada 2006 18:36, Karl Hasselström napisa?:
> >
> > For example, we could skip the "bisect" branch, since
> > you aren't supposed to commit to that anyway.
> 
> Well, to have "branch" to which you could not commit, just put ref
> outside refs/heads. 
> 
> Another solution would be to be able to put non-head ref in HEAD,
> but allow to commit only if the prefix is refs/heads/

That's not a bad idea. Then you can checkout a tag and have 'ref: refs/tags/v1.11' in HEAD, which means anyone who puts $(git-symbolic-ref) calls into their PS1 will see "refs/tags/v1.11" as their current branch, reminding them they are looking at the past.

Doing bisect branch in refs/bisect rather than refs/heads/bisect would then likewise remind the user that they are bisecting, and since neither matches refs/heads/* you cannot commit.

Linus Torvalds· Nov 14, 2006, 18:40 UTC · re: Shawn Pearce · lore
On Tue, 14 Nov 2006, Shawn Pearce wrote:
Show 16 quoted lines
> Jakub Narebski <jnareb@gmail.com> wrote:
> > Dnia wtorek 14. listopada 2006 18:36, Karl Hasselström napisa?:
> > >
> > > For example, we could skip the "bisect" branch, since
> > > you aren't supposed to commit to that anyway.
> > 
> > Well, to have "branch" to which you could not commit, just put ref
> > outside refs/heads. 
> > 
> > Another solution would be to be able to put non-head ref in HEAD,
> > but allow to commit only if the prefix is refs/heads/
> 
> That's not a bad idea.  Then you can checkout a tag and have
> 'ref: refs/tags/v1.11' in HEAD, which means anyone who puts
> $(git-symbolic-ref) calls into their PS1 will see "refs/tags/v1.11"
> as their current branch, reminding them they are looking at the past.
I agree. This would probably be a good way to do "read-only branches".

Allowing people to do a "git checkout" on anything committish, but then not allowing them to commit to that, is probably the right thing to do.

Together with a nice readable error message from "git commit" (and merge, and pull - although you might well allow "fetch" to update the thing that current HEAD points to, though), this would be a lot easier to use for people who just follow somebody elses branch.

Junio, what do you think? It wouldn't even be backwards incompatible, because we're strictly allowing a superset of what we used to..

			Linus
Petr Baudis· Nov 14, 2006, 19:37 UTC · re: Linus Torvalds · lore
On Tue, Nov 14, 2006 at 07:40:30PM CET, Linus Torvalds wrote:
Show 18 quoted lines
> On Tue, 14 Nov 2006, Shawn Pearce wrote:
> 
> > Jakub Narebski <jnareb@gmail.com> wrote:
> > > Dnia wtorek 14. listopada 2006 18:36, Karl Hasselström napisa?:
> > > >
> > > > For example, we could skip the "bisect" branch, since
> > > > you aren't supposed to commit to that anyway.
> > > 
> > > Well, to have "branch" to which you could not commit, just put ref
> > > outside refs/heads. 
> > > 
> > > Another solution would be to be able to put non-head ref in HEAD,
> > > but allow to commit only if the prefix is refs/heads/
> > 
> > That's not a bad idea.  Then you can checkout a tag and have
> > 'ref: refs/tags/v1.11' in HEAD, which means anyone who puts
> > $(git-symbolic-ref) calls into their PS1 will see "refs/tags/v1.11"
> > as their current branch, reminding them they are looking at the past.
  There's been a brief discussion on a related topic in
	http://news.gmane.org/find-root.php?message_id=%3cPine.LNX.4.58.0507120938240.17536%40g5.osdl.org%3e

(apparently the Linus-has-another-totally-awesome-idea kind (no irony (*))). Before, Cogito did basically what's proposed above to permit again: storing a random sha1 in the HEAD when it is seeked away to some historical commit. It switched to using a temporary branch for this purpose, but I'm not sure about the Linus' reasoning that

	"in order for a "git checkout" to not get confused and possibly
	throwing a cogito temporary head away ..."
which has been apparently clear to me back then. :-(
> I agree. This would probably be a good way to do "read-only branches".
> 
> Allowing people to do a "git checkout" on anything committish, but then 
> not allowing them to commit to that, is probably the right thing to do.
  So, the basic relaxation is that "again after a year, HEAD does not
have to be a ref at all".
> Together with a nice readable error message from "git commit" (and merge, 
> and pull - although you might well allow "fetch" to update the thing that 
> current HEAD points to, though), this would be a lot easier to use for 
> people who just follow somebody elses branch.
  Hmm, so that there would be something like refs/tracking/master as an
alternative to refs/heads/master? I'm not really sure if it is a good
idea. On one hand, you can relax my favorite fastforward restrictions on
those tracking branches, but I think the net worth is negative since you
will have to burden new users with yet another concept of "readonly
branches", tell them things like "either do git clone --no-tracking or
the first time you will want to commit something locally, create a
'head' using git checkout -b master HEAD" (you are already on a "master"
branch but it's a different "master", you know?) etc.
  (*) You never are ironical about Linus, the Chuck Norris of CS. (And a
hero, too!)
-- 
				Petr "Pasky the Wow Pruit Igoe is
					Awesome Too!" Baudis
Stuff: http://pasky.or.cz/
#!/bin/perl -sp0777i<X+d*lMLa^*lN%0]dsXx++lMlN/dsM0<j]dsj
$/=unpack('H*',$_);$_=`echo 16dio\U$k"SK$/SM$n\EsN0p[lN*1
Junio C Hamano· Nov 14, 2006, 22:09 UTC · re: Linus Torvalds · lore
Linus Torvalds <torvalds@osdl.org> writes:
Show 10 quoted lines
> On Tue, 14 Nov 2006, Shawn Pearce wrote:
>> That's not a bad idea.  Then you can checkout a tag and have
>> 'ref: refs/tags/v1.11' in HEAD, which means anyone who puts
>> $(git-symbolic-ref) calls into their PS1 will see "refs/tags/v1.11"
>> as their current branch, reminding them they are looking at the past.
>
> I agree. This would probably be a good way to do "read-only branches".
> ...
> Junio, what do you think? It wouldn't even be backwards incompatible, 
> because we're strictly allowing a superset of what we used to..
(late reply -- just came back from a clinic)

I do not see any fundamental reason not to allow HEAD to be a commit that you cannot commit on top of because you are not on any branch, and I agree it is the right way to go.

I am not sure how much pain we are talking about yet though.
Karl Hasselström· Nov 14, 2006, 15:39 UTC · re: linux@horizon.com · lore
On 2006-11-14 08:49:58 -0500, linux@horizon.com wrote:
Show 6 quoted lines
> One feature that I noticed is missing, is that there's no easy way
> to do a "git reset --hard" while doing a "git checkout" style merge.
>
> An example of when you'd want to do this is performing a "git
> bisect" with a local "#define DEBUG 1" change. Particularly if you
> hit a non-compiling version and need to back up.

Yes, this feature would be good to have. StGIT makes it a little less painful to hack around this limitation, but you still have to remember popping your patches before running "git bisect good|bad".

Then again, in StGIT's case this could be solved by creating an "stg bisect" command that takes care of this for you. But I still agree with you that it would be a win if regular git could do as you describe.

-- 
Karl Hasselström, kha@treskal.com
Junio C Hamano· Nov 14, 2006, 22:13 UTC · re: linux@horizon.com · lore
linux@horizon.com writes:
Show 5 quoted lines
> One thing I noticed is that with ref logs, you've just re-invented the
> CVS problem of associating history with a name.  If you want to rename
> a branch (say, from "mischacks" to something suitable for publication),
> do you rename the log or not?  It's a less virulent form, but it seems
> like the same disease.

I do not think we currently rename the log but it is probably a bug. Renaming tag should also be made easier.

> Another minor quibble: AFAICT, "git checkout -f -m" is meaningless (-f
> overrides -m), but doesn't complain.
It should be made to complain.
Show 9 quoted lines
> An example of when you'd want to do this is performing a "git bisect"
> with a local "#define DEBUG 1" change.  Particularly if you
> hit a non-compiling version and need to back up.
> or the way git-bisect does it
>   
>         echo "$rev" > "$GIT_DIR/refs/heads/new-bisect"
>         git checkout new-bisect || exit
>         mv "$GIT_DIR/refs/heads/new-bisect" "$GIT_DIR/refs/heads/bisect" &&
>         GIT_DIR="$GIT_DIR" git-symbolic-ref HEAD refs/heads/bisect

The name new-bisect is really temporary the code just shows it punts on a situation it does not expect (it essentially expects a clean tree and working with a local change is by accident) and being ony half helpful to the user while recovering from that unexpected situation. In other words, that's a room for improvement. We should be rewrite the above with something like what we have in the else clause in git-checkout around line 150. It might make sense to make it a shell function and sharable between checkout and bisect (perhaps there are other uses). Then we do not need the temporary branch.

> Either way, it reserves a second branch name, and seems like a bit of
> a hack.

You are correct to call it a hack. git-bisect started as a technology demonstration of the underlying rev-list --bisect feature. The thing is that the technology was so nice and useful that we started using it before cleaning up these implementation details. I think instead of documenting the duct tape that holds git-bisect together ("rev-list --bisect" is great, and what "git-bisect" does is basically great but with minor glitches like you noticed and with hacky implementation details; I am calling the latter "duct tape"), we should redo the hackish part.

Enough with bisect. And jumping the head while forward/back porting the local change would also involve the same kind of change, so "git reset --merge-local-change" might be a good addition, but I am not sure about the details yet.

← back to recent threads