Home > Net >  Git: can you pull from the "--push" URL of your remote?
Git: can you pull from the "--push" URL of your remote?

Time:11-26

To avoid excess network traffic and to speed up git operations for users, we have read-only git mirrors set up as follows:

$> git remote -v
origin ssh://[email protected]/repo.git (fetch)
origin ssh://[email protected]/repo.git (push)

And it works really well.

However, to push, you your local repo must be up-to-date and delays in mirroring from the central server to the local mirror can mean that despite doing a git pull you are not up to date with the central server. Even if that mirroring delay is just 12s, if somebody tries to git pull && git push at the wrong time, they'll get quite frustrated.

Question: If only they could git pull --pull-from-push-url then the problem would be essentially gone. Is there some way to achieve that?

(Yes, you can run git fetch <url of --push server> but I don't think that actually helps. I don't think it gets your commit merged/rebased or your remotes/origin/<branch> ref updated or anything.)

Update

Based on @jthill's response, I've implemented the following infetch part of the script, fetching just the current branch for speed, and allowing for the case of remotes that do not have a push url.

git fetch "$(git config remote.origin.pushurl || git config remote.origin.url)" \
          "$(git rev-parse --symbolic-full-name --abbrev-ref @{upstream}  |
              sed -r -e 's=^origin/(.*)= refs/heads/\1:refs/remotes/origin/\1=')"

CodePudding user response:

Yes, you can do this. Fetch works off "refspecs", specifications of what source commits (usually identified by a refname pattern) to send and (optionally, but usually) what destination refnames to give them.

The factory-default refspec for an ordinary clone is

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

and it means the source commits we don't have yet that refnames at the source identify with refs/heads/ names get fetched, and given destination names identified with refs/remotes/origin/ names; the means you don't care if updating the destination refs abandons history (since these are remote-tracking refs).

Now, here's the thing: remotes are just conveniences, they're a way of remembering how you like to do your fetches and pushes, and git pull is just a convenience for doing what you like to do after fetching.

Remotes and config options and such can't reflect every possible use of the underlying machinery as options, abstractions are great for illuminating and organizing your view of the territory but they're streetlights. They can't cover it all. The Git way is to provide conveniences to automate the usual cases, options to select them, and config items to fill in the usual details; for the corner cases, the oddball situations, you just use the core commands directly -- which is what the convenience commands are doing. They all started out as shell scripts invoking core commands and could still be implemented that way, they'd just be a little (sometimes imperceptibly) slower.

git fetch    # get up to date with the local mirror
git fetch $( # get up to date with the real remote
        git config remote.origin.pushurl) $(
        git config remote.origin.fetch)
git merge  # or git rebase, whatever it is you like to do after fetching

CodePudding user response:

You can set up the central repo and the mirror as two separate remotes, then use one for the branch’s remote setting and the other for the pushRemote setting.

Here’s how that would look like in .git/config:

[remote "origin"]
    url = [email protected]:repo/
    fetch =  refs/heads/*:refs/remotes/mirror/*
[remote "central"]
    url = [email protected]:repo/
    fetch =  refs/heads/*:refs/remotes/central/*
[branch "master"]
    remote = origin
    pushRemote = origin-central
    merge = refs/heads/master`

This way, if you end up with a conflict due to an out-of-date branch pointer on the mirror, you can always fetch from the central server and rebase your commits on top of the central server’s branch.

You can even set up both remotes to update a common remote-tracking branch pointer:

[remote "origin"]
    url = [email protected]:repo/
    fetch = refs/heads/*:refs/remotes/origin/*
    fetch =  refs/heads/*:refs/remotes/mirror/*
[remote "central"]
    url = [email protected]:repo/
    fetch =  refs/heads/*:refs/remotes/origin/*
    fetch =  refs/heads/*:refs/remotes/central/*

The prefix is omitted from the mirror’s setting, to prevent fetches from the outdated mirror from updating the branch pointer backwards (i.e. non-fast-forward update).

The pushRemote setting has to be set up for each branch separately. An alternative, mixed solution would be to set up the central remote to update the same remote-tracking branch pointer as the mirror remote:

[remote "origin"]
    url = [email protected]:repo/
    pushurl = [email protected]:repo/
    fetch = refs/heads/*:refs/remotes/origin/*
[remote "origin-central"]
    url = [email protected]:repo/
    fetch =  refs/heads/*:refs/remotes/origin/*

This avoids having to set up pushRemote for each branch.

  • Related