On Wed, 13 May 2015 08:29:39 -0700
Michael <keybou...@gmail.com> wrote:


> Alright, maybe this is my first point of confusion.
> I thought "HEAD" is where you are at -- which of those letters you
> are pointing to. And, it may also be where a branch tip is pointing.
> If I make a commit while on a branch, then HEAD -- which letter I'm
> at -- updates, and the branch tip pointer also updates.
> If I'm detached, then which letter I'm at updates, but the branch
> tips do not.

That is correct.

> Based on that, I thought that "git reset --soft" would change which
> letter I'm pointing at, and leave the branch pointers unchanged.

No.  That would not update a branch pointer only if you would be in a
detached HEAD state: in that case just the HEAD would be updated.

> What you seem to be saying, if I understand correctly, is that being
> at a branch tip does not mean, "I am pointing to a letter, and the
> branch tip is here as well", but "I am pointing at a branch tip, and
> the branch tip is currently here".

Well, I reckon the key to understand why these things work as they do
in Git is to grasp the concepts of "refs" and how is the HEAD ref is

As you supposedly know, a ref (short for "reference") is *a name* which
points at "something which can be resolved to a commit" (Git manuals
neatly refer to those "somethings" using the term "commit-ish").
Refs have type, and there are two types: heads and tags.  Most people
call heads branches, and the main Git command to manipulate heads is
called `git branch` as well.  Sure, this adds to the confusion but oh
well.  "Heads" is a somewhat better name because it underlines that
branches in Git are mere pointers to commits, and commits do not have
special properties which make them "belong" to a branch (as they do in
Mercurial), and branches in Git can't have multiple heads (as they do
in Mercurial).

Okay, the next thing to understand is that head refs in Git can be of
two kinds: "direct" (or "simple" or "normal" -- you name it) refs point
directly to a commit using the SHA-1 name of it.  A file representing
such a ref merely contains those 40 ASCII characters of the SHA-1 name
it points to.  Another kind of a head ref is "symbolic":  such a ref
contains a reference to another ref by including a line of the form:

  ref: refs/<type>/<refname>

Normal branches are direct refs, and that's why a branch is said to be
a pointer to a commit.

The ref named "HEAD" is special because it can be either a direct ref
or a symbolic ref.  If you check out a branch, HEAD becomes a symbolic
ref pointing at that branch.  If you check out something which can't be
moved by committing, HEAD becomes a direct ref.  I'm sure you can now
see that the current kind of the HEAD ref precisely defines whether
you're in a "detached HEAD" state or not.

Consequently, the rules of how operations on HEAD behave depend on its
current kind: when Git tries to reach for a commit through that ref, it
either does this right away -- if HEAD is direct, -- or has to
*dereference* this ref first -- if HEAD is symbolic.

Now observe, that when you call `git reset --soft|--mixed|--hard`
while you have a branch checked out (and HEAD is symbolic) Git chases
the chain of HEAD -> branch and changes the ref representing a branch.
Note that HEAD in this case is not really changed at all!  It just still
points to the same ref as before, just that ref got updated.

Conversely, in a detached HEAD state, `git reset ...` above operates
on the HEAD ref directly (there's nowhere to resolve it) and moves it

> If that is the case, then the first thing I would need to do to make
> "git reset --soft" behave the way I think it does is to first go to
> detached head at the same letter (so I am now pointing at the letter
> that the branch tip points to, rather than pointing to the branch tip
> pointer), then I can move head without moving branch tips.

That is correct.

The question is: do you really need to do this?
I'd say bringing just a single file from a dirty devel branch onto
master could be done way simpler (see below).


> Now, lets say your topic branch is really, really messy, with lots of
> commits, tests, commits, tests, undo, test, change, test, repeat. And
> I really don't want to toss that in as a fast forward. And,
> apparently, using "no-ff" breaks bisect and blame (1). That means
> either a squash commit (which I've managed to mess up once in two
> uses), or something else. As this was just a single file, I thought
> this would be a simple way to move one file cleanly onto master.

The question is: is it okay for you to make all the changes made to
that file appear as a single commit on master?

If the answer is yes, then:

  $ git checkout master
  $ git checkout devel path/to/the/file
  $ git add -u

The second command will update the specified file with its content
it currently has at the "devel" branch.
So the only thing left is to add the new contents of the file to
the index and commit.

If the answer is no, things are harder.  A squash commit is typically a
way to do to make a clear history, but if you need to apply to
"master" only a series of commits which touched that single file, a
manual approach is called for, and there are several of them possible.

If the number of commits is not large, I'd go with cherry-picking:

1) Write down the commits which changed the file:

   $ git log --oneline master..devel path/to/the/file

2) Switch onto master and cherry-pick these commits one-by-one
   using the --no-commit command-line option and resetting everything
   not touching that file before recording a commit.

Tedious but should work.

> Also: Every tutorial on branching I've seen says to branch from as
> far back in history as makes sense; what you said seems to be the
> opposite, branch from the tip of history, not from back in history.
> What am I misunderstanding?

I'm afraid you're trying to blindly follow some "best practice" here.

A branch should start from whatever point which makes sense for your
current situation.  For instance, suppose you have two active branches:
"master", which is stable, and "a_crazy_idea_im_exploring_right_now".
Now suppose you're told to fix a bug on "master"  Sure, it makes sense
to fork your bugfix branch off "master" rather than that experimental
branch, right?

Sometimes you see your current experimentation might be leading to a
dead-end, and you want to try some other approach to the problem at
hand; at that point it makes sense to branch from some older commit on
your experimental branch -- if things will go in a more sensible way,
you will be able to replace your original branch with the new head
(`git branch -M`).

Sometimes you might be asked to, say, fix a bug in a product's version
released long time ago, and you'll fork off its maintenance branch,
merge your work there and then merge of cherry-pick that change(s) on
master, if needed.

IOW, the point to branch at has to be determined on a case-by-case
basis.  Do not follow some single rule here; use a common sense.

You received this message because you are subscribed to the Google Groups "Git 
for human beings" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to git-users+unsubscr...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Reply via email to