Was: [Mystery solved, I owe you an apology] (was: [git-users] how to
remove two commits from a remote server)

On Wed, Dec 1, 2021 at 12:18 PM Uwe Brauer <o...@mat.ucm.es> wrote:
> As I said I am one of the maintainer of
> matlab-mode(emacs), a git repository in sourceforge. Since I know
> mercurial relatively well but don't really understand git (to put it
> [...]
> It never occurred to me, that there would be two
> type of branches (in mercurial there is only one type of branch, it
> commits can be on remote or on local or on both but there is no need to
> distinguish both).
> Yes I know git branch -a shows branches, I never really understand them
> all.

I think this point deserves a bit further discussion.

As I wrote at https://stackoverflow.com/a/11223644/334451 it's
actually the other way around. Mercurial has multiple things that are
logically branches: unnamed branches, named branches and bookmarks.
Git only has branches but it uses namespaces for branch names
(basically path for a branch + the name). Git users often speak about
"master" branch (or "main" nowadays) and you have to decipher from the
context if they actually mean refs/heads/master or
refs/remotes/origin/master or something else. Git is also smart enough
to guess the correct path if you use just the last part of the full
name and in some context the interpretation of short names is
explicitly defined. For example, when you say

    git push origin foo:bar

it will actually execute (assuming foo is local branch and not a tag)

    git push origin refs/heads/foo:refs/heads/bar

which means 'take the local "refs/heads/foo" and push it to remote
server "origin" using name "refs/heads/bar" on the remote server'. You
can use commit SHA-1 for the source as long as the target is a branch
that already exists (otherwise git cannot know if you want to create a
new tag or branch). I prefer to use this syntax if I ever need to
force (overwrite) things on the remote server so that I can accurately
specify the new state of the target branch.

As everything actually has full namespaced name ("refname"), you can
also do stuff like having branch master (actually refs/heads/master)
and tag master (actually refs/tags/master) but that's just asking for
trouble. Git always uses the full refnames under the hood but allows
using shorter names in the user interface.

By default, when you run "git branch" it lists only refs/heads/*
without the full "refname". For full branch names you have to run
something like

    git branch --format="%(refname)"

or for all branches, local or remote no matter how many remote servers
you've configured

    git branch -a --format="%(refname)"

which will list full names of all the known branches. All those names
are actually file paths under the directory .git/refs/ in your working
directory so the whole system is actually really simple (the file at
the end of that path contains just the SHA-1 of the commit that's the
tip of that branch). When you "create a new branch" in Git, you
literally create one new file with 41 bytes containing the currently
checked out commit SHA-1 (output of "git rev-parse HEAD") with
trailing linefeed and the name of the file is the name of the branch
you created. The file .git/HEAD contains textual reference to
currently checked out commit or head or tag in your working directory.

Git tags are implemented similarly but those are stored in
.git/refs/tags/ and will not be automatically modified when you create
new commits. Note that tags are not kept in separate namespaces but
when you fetch changes, you automatically get all the tags, too, and
those are always in the prefix refs/tags/.

You can list all known tags with full refnames using command

    git tag --format='%(refname)'

Note that "git tag -a" does exist but it doesn't mean "list all" but
"create annotated tag" (a tag that has more info attached to it
instead of just the name) because tags do not have namespaces, so
there's no need for "list all tags".

The branches starting with refs/remote/ will be updated automatically
when you run "git fetch" (or do "git pull" which will run "git fetch"
behind your back).

Git will be much easier to understand if you never ever use "git pull"
for anything.  Always run "git fetch" (or "git fetch --all" if you
have multiple remote servers) instead and it will only update the
refs/remote/ hierarchy only and download the required pack/object
files to actually know what all those SHA-1's mean. After you have
executed "git fetch" you can use "gitk --all", "gitg" or some other
repository viewer that can show both local and remote branches. If you
don't have any GUI tools, you can run something like

    git log --oneline --decorate --graph --all

or (everything on one line)

    git log --graph --all --pretty=format:"%C(auto)%h%d%Creset %s
%Cgreen(%cr)%Creset"

Then you can sanely decide if you want to merge, rebase or do something else.


As a party trick, you can also do stuff like

    git push . HEAD:foo

which means push to local repository, update local branch HEAD as the
new value for branch "foo" (fast forward), where HEAD is currently
checked out version as usual. You can use SHA-1 here, too. This is
mostly helpful when you use feature branches and want to include your
current feature branch to local "master" branch. Instead of checking
out master and "merging in" the changes from your feature branch, you
can simply push current branch to local master branch. This is better
than forcing master branch to HEAD because git will show an error if
the change wouldn't be fast-forward. I have aliased "git
update-master" to "git push . HEAD:master" just for this purpose.

-- 
Mikko

-- 
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.
To view this discussion on the web visit 
https://groups.google.com/d/msgid/git-users/CAM2wTFZ-cWHu9jzHrukJ6%2BW7w7kScePMDMc7HWaHfF9SAviRFA%40mail.gmail.com.

Reply via email to