0

My question is similar to this one.

I pushed a local branch to a server, creating it for the first time on that server and setting it as the local branch's upstream with --upstream. I am having trouble extracting tracking information programatically from the upstream without making unecessary changes to the local git repository. For example, I would like to know the commit hash that the HEAD of the upstream is pointing to.

Here's how I created that remote branch and set as upstream:

$ git push --set-upstream sshname:/user/project.git

I can git pull, git push etc. just fine. However, I have a script in my .bashrc that inspects the hash of the latest commit on the upstream server and compares it with my current branche's hash. (This is used to prettify the prompt with a colorscheme that help me quickly see whether I remembered to push a repo to the server, or whether I'm on a detached head, etc). The script works fine on most local repositories, where it gets that remote HEAD hash with

$ git rev-parse "$(git branch --show-current 2> /dev/null)"@{upstream}

> fatal: upstream branch 'refs/heads/master' not stored as a remote-tracking branch

As you see, it fails with the claim that there the branch on the remote is not stored locally as a remote tracking branch.

I looked for various suggestions on StackExchange on how to get around this, including How to create git Remote-Tracking Branch and learning Git: tracking vs. setting upstream (-u) for remotes?. Nothing helps. Many of the suggestions are just wrong, with people conflating remotes, remote tracking branches and upstream urls.


Here is the output of various commands that people might suggest:

$ git remote -vv
> [no output]
$ git branch -vva
> * master <HASH> <COMMIT_MSG>
$ git fetch -a
> From sshname:/user/project
 * branch            master     -> FETCH_HEAD

This is the first one that shows the information I'm looking for. But it's not in a machine-parseable format and there's no --porcelain. It's also wrong. The real URL ends in .git. And the server does actually need this .git suffix; it cannot be ignored.

$ git ls-remote
> From sshname:/user/project.git
<HASH>        HEAD
<HASH>        refs/heads/master

The correct URL now, but again not machine-parseable.

I can also run 'destructive' operations (not OK for scripting) in order to fetch this information indirectly in a similar fashion:

$ git pull
> From sshname:/user/project
 * branch            master     -> FETCH_HEAD
Already up to date.

Wrong URL again.

$ git push
> Pushing to sshname:/user/project.git
To gitea:/murdock/dupmaster.git
 = [up to date]      master -> master
Everything up-to-date

Needless to say, I can hardly trust such inconsistent output for scripts. I need a porcelain-like output.

Now the closest solution I found was:

$ git config -l
> ...
branch.master.remote=sshname:/user/project.git
branch.master.merge=refs/heads/master

Indicating that I could get the upstream 'URL' this way:

$ git config --get branch.master.remote
> sshname:/user/project.git

But still, I cannot probe the remote tracking information. Manually, I could add a git remote based on this 'URL' and then run git fetch -a and that (probably) would resolve my issue. But this is a 'destructive' process which alters the git repo. I'm trying to script something which doesn't make unecessary changes to the repos.

How can I programatically fetch tracking information in a reliable way, without altering the git repo?

I would settle for automatically creating remote tracking branches, if that were possible. But I do not want to create remotes (how would I name them automatically? etc).

Myridium
  • 789
  • 10
  • 20

1 Answers1

2

The problem here is that you have set up a Git repository in which there is no remote, as seen here:

$ git remote -vv
> [no output]

With no remote, there can be no remote-tracking names. A remote-tracking name is produced by taking a branch name from some other Git repository, as seen on a repository found by accessing it through a "remote", and running it through the mapping provided under the given remote.

To add a remote to this repository:

git remote add origin <url>

(assuming you'd like to use the name origin; use any name you prefer here, but origin is the standard first one). This will create the remote. The remote now stores the URL, so that this:

$ git config --get branch.master.remote
> sshname:/user/project.git

will change.

To make that change happen, instead of (or after):

$ git push --set-upstream sshname:/user/project.git

use:

git push --set-upstream origin master

Now instead of:

$ git config --get branch.master.remote
> sshname:/user/project.git

you will find that branch.master.remote simply contains origin.

The remote itself provides a default fetch refspec:

git config --get remote.origin.fetch

which will read:

+refs/heads/*:refs/remotes/origin/*

Subsequent git fetch origin operations will create or update the names in the refs/remotes/origin/ namespace: the remote-tracking names for the remote named origin, whose URL is stored in remote.origin.url.

(What's going on is that you're using the setup from Primeval Git, back in 2005 or so, before the concept of a remote was invented. Without a remote, there can be no remote-tracking names. Git still supports this mode, but it's unwise to use it since—as you've discovered—a lot of modern software really wants the features provided by having a remote.)

torek
  • 448,244
  • 59
  • 642
  • 775
  • Thanks for clarifying. So, would you say it's not recommended to use `git push -u host.name:/path/to/repo` then? And instead our workflow should be to always add a remote, then push with `git push -u remote/branch`? – Myridium Dec 12 '21 at 23:57
  • @Myridium: yes. Use a raw Git URL only when you are not going to use it further, otherwise save it as a `git remote add` step. – torek Dec 13 '21 at 00:28