Search code examples
gitgit-push

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


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://git@local.mirror.company.com/repo.git (fetch)
origin ssh://git@central.server.over.slownetwork.company.com/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=')"

Solution

  • 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