user12861522
user12861522

Reputation:

How to generically fetch just the current branch

I want to generically fetch the current branch only:

git fetch origin HEAD

but how do I determine the default remote?

git fetch DEFAULT_REMOTE HEAD

I assume HEAD is the most generic way to refer to the current branch, but I also need a generic way to refer to the default remote for the current branch?

Note that doing this:

git fetch HEAD

give me this problem:

fatal: 'HEAD' does not appear to be a git repository

fatal: Could not read from remote repository.

Upvotes: 1

Views: 486

Answers (2)

Bernardo Duarte
Bernardo Duarte

Reputation: 4264

Answer

git fetch origin $(git rev-parse --abbrev-ref HEAD)

With Generic Remote

git fetch $(git config --local branch.$(git rev-parse --abbrev-ref HEAD).remote) $(git rev-parse --abbrev-ref HEAD)

Explanation

As per the docs you can issue git fetch from a specific refspec.

Using the following format of git fetch, you can ask for a specific ref: git fetch [<options>] [<repository> [<refspec>…​]]

< refspec >

Specifies which refs to fetch and which local refs to update.

This subshell $(git rev-parse --abbrev-ref HEAD) comes from this answer.

The other one I've wrote myself and it's the following $(git config --local branch.$(git rev-parse --abbrev-ref HEAD).remote), it uses the first one to increase generalization, but what it does is to return the name of the remote associated with the branch in your local config, following this pattern git config --local branch.<branch_name>.remote.

Upvotes: 1

torek
torek

Reputation: 489828

I assume HEAD is the most generic way to refer to the current branch ...

It is, yes. There's a pitfall here though, as we will see in a moment.

but I also need a generic way to refer to the default remote for the current branch?

There are a lot of ways to do this, and each has its own possible pitfalls, but I think the simplest is to use git config to read back half of the current branch's upstream setting.

The obvious pitfall here is that the current branch may not have an upstream.

If you have a modern Git, git rev-parse can return information about the upstream of any branch, including the current branch:

$ git rev-parse --symbolic-full-name HEAD
refs/heads/master
$ git rev-parse --symbolic-full-name HEAD@{u}
refs/remotes/origin/master

Here, the current branch is master and its upstream is origin/master. These use the full spelling of each ref, which is a good idea if you're writing scripts, since the abbreviated names (master and origin/master) could mis-parse: e.g., master might turn into refs/tags/master, if someone has created a tag named master.

The upstream, if there is one—git branch --unset-upstream master will remove the upstream for master, for instance—is set as two parts:

$ git config --get branch.master.remote
origin
$ git config --get branch.master.merge
refs/heads/master

The branch.master.remote setting is precisely what you want here: the name of the remote, or in this case, origin. So if you first find the current branch name:

$ git symbolic-ref --short HEAD
master

you can use that to grab the appropriate remote name:

branch=$(git symbolic-ref --short HEAD) || die ...
remote=$(git config --get branch.$branch.remote) || die ...

You can now run:

$ git fetch $remote HEAD

But now we hit the first pitfall I mentioned:

remote: Enumerating objects: 928, done.
remote: Counting objects: 100% (928/928), done.
remote: Compressing objects: 100% (401/401), done.
remote: Total 928 (delta 601), reused 809 (delta 526)
Receiving objects: 100% (928/928), 843.84 KiB | 1.62 MiB/s, done.
Resolving deltas: 100% (601/601), completed with 74 local objects.
From [url]
 * branch                  HEAD       -> FETCH_HEAD

Note that, although I have a modern(ish) Git here (2.24.0), I did not get my origin/master updated. To do that I must use instead:

$ git fetch $remote $branch
From [url]
 * branch                  master     -> FETCH_HEAD
   c7a6207591..51ebf55b93  master     -> origin/master

This time, my origin/master got updated appropriately.

Edit: I didn't mention it originally, but we can see here that my Git asked their Git for their HEAD. Their HEAD is their master; had I been on some other branch, I would have still gotten their HEAD, i.e., their master, in my .git/FETCH_HEAD. So this is not merely cosmetic: you must feed your own Git a branch name here, rather than the symbolic name HEAD. Compare with git push, where you can safely use HEAD for your side, because with git push it is your Git, not theirs, that resolves the symbolic name HEAD to a branch name.

Conclusion and one more pitfall

You should first obtain the current branch name. Remember that this may fail: there may be no current branch name. Then, use git config --get to get the remote part of the upstream setting. (The remaining part requires mapping through the remote's default fetch refspec mapping, but we don't need it—git fetch will handle this—so don't bother picking up branch.$branch.merge at all.)

The last pitfall that is worth mention here is this: the sample code above uses git config --get ... || die ..., where die is (presumably) a shell function to quit the script entirely. However, if you don't have an upstream set for the current branch, git fetch itself won't quit at this point. It will simply use the hardcoded name origin. So, depending on how slavishly you want to imitate raw git fetch, you might want:

remote=$(git config --get branch.$branch.remote) || remote=origin

instead. Since you are writing the script, you get to control the edge and error cases here.

Upvotes: 0

Related Questions