Home > other >  Why does the "From" line in a "git pull" response not list the bare repo's
Why does the "From" line in a "git pull" response not list the bare repo's

Time:04-11

I'm rather new to git, and I'm trying to understand the lines

...
From /tvm
   f8322345..9837f82f  master     -> origin/master
...

in git's response to a git pull with branch dev checked out.

I've got a git bare repo, and two repos that push/pull to that bar repo; all on a single server. The configuration is as follows:

/
|
 -- tvm.git           The bare repo
|
 -- htdocs
      |
       -- dev         The development repo. Has both master and dev branches
      |
       -- website     The production repo. A clone of the master branch.

This setup is used to manage a Joomla based web site. The database changes as people are writing new articles, etc., so the master branch in the production repo changes with this.

I need to merge the master branch into the dev branch in the development repo to bring dev up to date. While in /htdocs/website (which always has branch master checked out), I first unload the DB, then git commit, and git push. Git's response is (some lines deleted for brevity):

Enumerating objects: 5, done.
...
Total 3 (delta 2), reused 0 (delta 0), pack-reused 0
To /tvm.git
   5f08eef0..9837f82f  master -> master

I understand the last two lines to mean: Git has pushed the changes to the bare repo (To /tvm.git) and this was branch master to master.

Next, I change to the devlopment directory, which has branch dev checked out.

/htdocs/website $ cd ../dev/
/htdocs/dev $ git status
On branch dev
Your branch is up to date with 'origin/dev'.

nothing to commit, working tree clean

Just to be sure, I pull from the bare with git pull. The response is (again some lines deleted):

remote: Enumerating objects: 17, done.
...
Unpacking objects: 100% (13/13), 32.46 KiB | 21.00 KiB/s, done.
From /tvm
   f8322345..9837f82f  master     -> origin/master
Already up to date.

I don't understand, firstly, why does git tell me From /tvm and not From /tvm.git? Is this just inconsistency? Secondly, why is git writing master -> origin/master? Why master and not dev? And thirdly, why is git writing Already up to date.? The master branch is not up to date; I had just commited and pushed changes to the master branch in the bare repo.

The git config files contain ...

  • In the development repo:
[core]
        repositoryformatversion = 0
        filemode = true
        bare = false
        logallrefupdates = true
[remote "origin"]
        url = /tvm.git
        fetch =  refs/heads/*:refs/remotes/origin/*
[branch "master"]
        remote = origin
        merge = refs/heads/master
[branch "dev"]
        remote = origin
        merge = refs/heads/dev
  • In the production repo:
[core]
        repositoryformatversion = 0
        filemode = true
        bare = false
        logallrefupdates = true
[remote "origin"]
        url = /tvm.git
        fetch =  refs/heads/*:refs/remotes/origin/*
[branch "master"]
        remote = origin
        merge = refs/heads/master
  • In the bare repo_:
[core]
        repositoryformatversion = 0
        filemode = true
        bare = true

CodePudding user response:

I don't understand, firstly, why does git tell me From /tvm and not From /tvm.git?

Git reads the url = setting from origin (in this case) and uses that, but it does have a tendency to strip .git from the end of such URLs. There's no particularly good or bad reason for this. Git just does it.

Secondly, why is git writing master -> origin/master? Why master and not [the branch I am on, i.e.,] dev?

The git pull command consists of running two other Git commands:1

  • First, git pull runs git fetch.
  • Then it runs another command, which we'll leave out for a moment.

(Most but not all of the arguments you pass to git pull go directly to git fetch, if you do give any. You didn't, so we get to bypass all the gory details here.)

The git fetch command is the one producing these lines of output. The fetch command:

  1. Calls up some other Git repository: whatever answers at the URL provided. Typically the URL starts with https:// or ssh:// so that Git goes over the network, sort of like making an Internet phone call. A web or ssh server will answer and transfer the "phone call" to other Git software, or at least, other software that speaks the Git protocol, so that there are now two separate Git commands running: yours, on your repository, and theirs, on their repository.

    In your case the URL is for a file, so instead of calling someone to see who answers, your own Git software plays the role of the other Git software. (Your Git may or may not spawn a second round of Git software, depending on what version of everything you're using. Traditional C Git does, or at least did, have to spawn another one because there are, or at least were, too many global variables so that only one repository could be "in play", being either read—the Git serving the fetch—or written; this has at-least-mostly-fixed over time so the details may vary depending on Git version.) The principle is the same either way: the server your Git "calls up" reads their repository, and your Git writes to your Git repository.

  2. Their Git (their software on their repository) lists out their branches and tags and other such names and the commit hash IDs that go with these. Your Git picks out the ones it "likes". For a default git fetch, your Git likes everything, so any commit hash IDs your Git sees that your Git does not already have, your Git asks their Git to please send that over. This obliges them to offer that commit's parent(s); your Git checks to see if you have these commits, and if not, your Git asks for those, which makes their Git offer more parents, and so on.

    The end result of this phase of the conversation is that your Git learns about every commit they have that you don't, and they learn about the overlap: which commits you both have. They then use that information to package up all the commits and supporting objects that your Git will need, so that you will have everything they have plus any commits of your own that you have never sent out.

    This covers the counting and enumerating and compressing and receiving phases. Your Git now has all the necessary commits and supporting objects so that you have every commit. Your Git saves these away, expanding ("unpacking" and "verifying" and so on) things if/as necessary, using the objects you already have, that they learned about during the have/want conversation phase. All the communications are done now, and your Git software can disconnect from theirs.

  3. Now that you have all of their commits, your Git takes each of their branch names and changes those into your remote-tracking names: their main becomes your origin/main, their dev becomes your origin/dev, and so on. Remember, at the start of this, they listed out all their branch names and all the commit hash IDs that those names represented.

    Your Git now checks to see which of your origin/* names need creating or updating. This is where the:

       f8322345..9837f82f  master     -> origin/master
    

    style lines come out. This means their master—your origin/master—used to name commit f8322345, but now their master names commit 9837f82f. Your Git therefore needs to update your own origin/master.

To make complete sense of this, you need one more fact: the hash ID of any commit is universally unique. If you have commit 9837f82f, your Git calls that 9837f82f, their Git calls that 9837f82f, and every other Git in the universe that has that commit calls it 9837f82f. (This 9837f82f is actually shortened from the full hash ID, which has to be huge so that it can be universally unique.) So your Git and their Git can tell which commits you both have, just by examining the number. (The hash ID is a hexadecimal representation of a large cryptographic checksum over the commit's content.2)

There are a few other things to observe about the line:

   f8322345..9837f82f  master     -> origin/master

First, note the two dots, and the fact that the line does not end with (forced update). That means that commit f8322345 is an ancestor of commit 9837f82f: parent, or grandparent, or great-grand-parent, or greatn-grand-parent for n > 1. If master had been rewound and rewritten, you'd see a plus sign, three dots, and the added forced update remark.

If your own Git did not have an origin/master yet, so that the existence of their master (your origin/master) were new to your repository, you would get:

 * [new branch]            master     -> origin/master

and if you run git fetch with the prune option enabled and they have deleted some branch name they used to have but no longer do, you would get:

 - [deleted]               (none)     -> origin/foo

These lines therefore tell you a lot about what has happened since the last time you collected updates from a given named remote like origin.

And thirdly, why is git writing Already up to date.?

We now get into the second command that git pull will run. You get to choose this command, but it (currently, or previously at least3) defaults to being git merge. Your other option is git rebase, but you're getting git merge.

Unlike git fetch, which fetches all the other Git's branch names,4 the second Git command—regardless of which one you choose—will only operate on the current branch. Your current branch is dev and its upstream is origin/dev. The git status and git branch -vv commands will show you the upstream setting:

$ git status
On branch master
Your branch is up to date with 'origin/master'.

Here I'm on my master for my Git repository for Git, and it's in sync with origin/master. Since I just ran git fetch, that means origin's master identifies the same commit:

$ git rev-parse master origin/master
ab1f2765f78e75ee51dface57e1071b3b7f42b09
ab1f2765f78e75ee51dface57e1071b3b7f42b09

We see here that both names identify commit ab1f2765f78e75ee51dface57e1071b3b7f42b09, which is Git version 2.36.0-rc1.

Your git fetch updated your origin/master—that's what you saw in the git fetch output from git pull—but did not update your origin/dev. Your existing dev branch's tip commit is either the same as, or ahead of, your origin/dev commit. This means git merge has no work on their side to combine with any work you may have done on your side. Your dev is already up to date with your origin/dev. And that's what Git told you here.


1In the past, git pull was literally just a shell script that actually ran git fetch, then actually ran a second Git command. These days it's a big hairy C program that at compile time, includes the same code as git fetch and both other programs, so that instead of invoking the other programs as commands, it invokes them as subroutine calls. The principle is the same, though, and to keep the "backwards" in "backwards compatibility", the C code doggedly goes through every last silly little wrinkle that used to be required because these were separate programs.

2This is currently a 160-bit SHA-1 hash, but it turns out that 160 bits of SHA-1 is not cryptographically strong after all, and Git is moving towards 256-bit SHA-256. See also Git and SHA-256.

3This default turns out to be bad/wrong for many, and Git is now starting to require that you configure a default. If you get a complaint about needing to configure pull.ff and/or pull.rebase, you have a version of Git that's pushing you to pick something consciously, rather than just accepting some default that might be wrong for you.

4This actually depends on multiple details, but the default setup is that you get all branches, provided you run git fetch without options. Watch out for answers that talk about using git pull --all: this passes --all to git fetch, where it does not mean all branches since that's already the case. Instead, it means all remotes. This is not harmful, but it does not do what people think it does, so answers recommending --all should be treated with caution, lest the person who wrote them have other misunderstandings that are harmful.


Conclusion

git pull is a big command that does a lot. It runs two other Git commands, each of which is also a big command that does a lot: git fetch, then one of git merge or git rebase. It's a very good idea to study all three of these run-by-git pull commands first, and in my opinion, a good idea to avoid git pull in favor of running the individual commands, until you have a good working knowledge of what each one does.

Once you know what each command does, you may find that git pull's convenience short-cut of running git fetch and then immediately—without giving you a chance to observe what git fetch fetched—running a second command is what you want. But you won't actually know that until you know what you want, which you can't know until you know what each of these does. Worse, when one of the commands fails, you won't know how to recover. A huge part of being skilled with anything (software, cooking, woodworking, nuclear reactor operation, and so on) lies in knowing what to do when something didn't work. When everything works as designed, any fool can do it; the pros know what to do when it doesn't.

  • Related