Trouble with Git mirroring

1,303 views
Skip to first unread message

jeff....@intexx.com

unread,
Jun 22, 2018, 5:12:14 PM6/22/18
to git-for-windows
I'm running into problems keeping a second repo mirrored with the first, as detailed here.

I've since tried completely deleting and recreating from scratch both R1 and R2 repos, but curiously that doesn't help. It seems no matter what I do and how I do it, R1 refuses to send new changes to R2.

Is there a way to troubleshoot this? (Caution: Git beginner on the loose.)

Thanks,
Jeff Bowman

Konstantin Khomoutov

unread,
Jun 23, 2018, 2:03:40 PM6/23/18
to jeff....@intexx.com, git-for-windows
On Thu, Jun 21, 2018 at 05:48:28PM -0700, jeff....@intexx.com wrote:

Hi, Jeff!

> I'm running into problems keeping a second repo mirrored with the first, as
> detailed here <https://stackoverflow.com/q/50978056/722393>.
>
> I've since tried completely deleting and recreating from scratch both R1
> and R2 repos, but curiously that doesn't help. It seems no matter what I do
> and how I do it, R1 refuses to send new changes to R2.
>
> Is there a way to troubleshoot this? (Caution: Git beginner on the loose.)

(For a start, next time please take your time to actually copy-and-paste
stuff from the original place so everyone has the context to quote in
their replies. Also please note that many people are just lazy to follow
links or read their mail in a MUA while disconnected from the 'net.)


For me, the situation is somewhat complex to comprehend extensively, and
the fact you appear to use non-vanilla Git on your remotes (I have no
clear idea what is TFS and VSTS — besides the fact these are some
tools from Microsoft to do version control). Still, let's try to may be
at least move you a bit further in your quest. ;-)

For a start the pictures for "R1 state" [1] and "R2 state" [2] sadly do
not say anything about which _branch_ was considered on it.
By correlating what's on the images with what's on the captures of the
`git push` commands run, I guess you were looking at either "master" or
"Application/master".


I'm going to offer two insights on how to approach the situation.


First, I think you might have a wrong mental model of how branches
(and everything else) is managed in Git — especially when there are
both regular repos (those used on workstations, supposedly what you have
at W) and bare repos (those used on servers, supposedly what you have on
R1 and R2).

Since Git is a distributed VCS, each Git repo is completely
free-standing — in the sense it does not depend on any other repo, and
any "linking" between different repos — in the form of "remote repos" —
is merely a policy thing.
This means, even if names are the same, a branch "master" in a local
repository does not in any way inherently depend on the same-named
branch in any other repository you are able to fetch from or push to.

In W, you appear to have a remote named "origin" — which is typically
created by `git clone` when you clone an already existing repository
(that's why it called "origin" after all).
For remote repositories, Git typically manages a set of so-called
"remote branches". Remote branches are best thought of as "bookmarks"
to the state of the branches as found in their remote repository the
last time it was fetched from. Git has certain helpers in place which
allow your local braches — those you actually work on — to be linked
with certain remote branches. If a local branch is linked this way with
a remote branch, it is said the local branch "tracks" the remote branch.

As the most common example of this, after fetching all the data, the
`git clone` command typically creates a single local branch "master"
pointing at the just-created remote branch "origin/master", and makes
the local "master" track "origin/master".

Suppose you now run `git fetch origin` — Git will fetch everything the
local repository does not yet have, and if the branch "master" was
updated in the remote repo (compared to the state of "origin/master"
locally), the "origin/master" remote branch will be updated to match
that state. (Note this will make nothing with the local "master"
branch — as I've said, all this linking is a policy thing because you
may have any number of remote repositories to contact with, and each of
them is free to has its own branch "master".)

If you so wish, you might now consider integrating what's there in
"origin/master" into your local "master" — in one way or another.
You might merely merge "origin/master" into "master", or rebase "master"
on "origin/master" or whatever.
When you push your local "master" to update "master" in the remote
repository known as "origin", Git also updates the "origin/master"
bookmark.

I highly recommend to read and absorb [3] to get a firm grasp on this
idea.


So, we're going to arrive at the first point I wanted to highlight:
if you want to _mirror_ W to R1 — as in rewrite everything at R1
with whatever there is on W, — there's no point in using
`git push --mirror`: it also sends your remote branches to the remote
repository which has quite little sense and may confuse you when you
look at R1.

A better approach is to drop "--mirror" and use explicit "refspecs"
for `git push` telling it to force-push all the branches and tags,
and also remove at the remote the branches which names match those
of the branches deleted locally:

git push --force --prune --tags origin "refs/heads/*:refs/heads/*"

It's like `git push --mirror origin` but omits the hierarchy
'refs/remotes/origin/*' from pushing.


Before or after switching to this new approach you should possibly get
rid of the pushed remote branches in both R1 and R2 — to lessen the
amount of cruft in them. You can do this by running

git push ":refs/remotes/*"

Not pushing the remote branches would get rid of those "origin/..."
thingies in

| POST git-receive-pack (744 bytes)
| Pushing to https://Personal%20Access%20Token: [...]
| To https://customer.visualstudio.com/Applications/_git/Application
| = [up to date] master -> master
| = [up to date] origin/develop -> origin/develop
| - [deleted] Application/master
| c13bdc5..81ddbe0 origin/master -> origin/master
| updating local tracking ref 'refs/remotes/Application/master'

See [4] for more info.


The second point I'd like to make is that there exists useful commands
which allow to _directly_ check what's there in any given remote
repository. This may help enormously to rule out possible problems you
may have with inspecting the state of R1 and R2 using whatever
TFS- and VSTS-native things exists for this.

The first one is `git ls-remote <URL_or_remote_name>` which contacts the
remote repository, asks it to tell what it has and lists that in the
form of pairs "commit hash + ref name".
For example:

| $ git ls-remote fork
| 8fed8eb1c43d9c97bd5157a8760658438a0b3bd6 HEAD
| 8fed8eb1c43d9c97bd5157a8760658438a0b3bd6 refs/heads/kaboom
| 8fed8eb1c43d9c97bd5157a8760658438a0b3bd6 refs/heads/master

So you may easily verify two things:

- The remote repo of interest does really have a branch of interest.
- The tip commit of that branch is expected.

(Note that Git call everything which can be "named" "a ref", which is
short for "reference", and the internal term for branch is "head".
That's why branches live in the "refs/heads/" namespace, and tags
live in "refs/tags".)

Another — more high-level — command is

$ git remote [-v] show <remotename>

It does not show you commit names but it lays out which, if any,
tracking policies are configured for the local branches against that
remote.


So, I'd go this way:

1) Switch to not using "full-mirror" pushing for W->R1 updates.

2) Delete all remote branches pushed from W to R1.

3) Afterwards, use `git ls-remote` to verify the contents of R1
is expected as compared to the set of local branches in W.

4) When synchronizing R1 to R2, make sure the remote branches "poisoned"
R1 from W are also deleted in R2 (in theory, plain
`git push --mirror` should take care of that; if not, you may use
that "push nothing" call I shown before to take care of that).

5) Use `git ls-remote` to verify R2 really mirrors the state of R1.


To see which branches the local repository has, and at which commits
they point, use something like

git branch --list -v --no-abbrev

You might also throw in "--all" or "--remote" to see local _and_ remote
branches or only the remote branches.

1. https://i.stack.imgur.com/o7dkK.png
2. https://i.stack.imgur.com/yGg8C.png
3. https://git-scm.com/book/en/v2/Git-Branching-Remote-Branches
4. https://public-inbox.org/git/xmqqpoec...@gitster.mtv.corp.google.com/t/

Jeff Bowman

unread,
Jun 23, 2018, 2:45:27 PM6/23/18
to Konstantin Khomoutov, git-for-windows
> (For a start, next time please take your time to actually copy-and-paste
> stuff from the original place so everyone has the context to quote in their
> replies. Also please note that many people are just lazy to follow links or
> read their mail in a MUA while disconnected from the 'net.)
>
Yes, you are correct. And in fact, I knew better as I was doing it. I don't know what came over me! Thank you for the kind suggestion. I'll be more careful about it in the future.





> For me, the situation is somewhat complex to comprehend extensively, and the
>
For YOU?? :-)

This is quite a lot for me to digest. Thank you very much for such a comprehensive reply. May a take a few days to get back to you?

Thanks,
Jeff Bowman
Fairbanks, Alaska



Jeff Bowman

unread,
Jun 25, 2018, 10:36:57 PM6/25/18
to Konstantin Khomoutov, git-for-windows
I have some clarifications for you, based on a better understanding of what you've written after a closer reading. This new information may alter your suggestions.

First, for the context of our conversation here, TFS is a repository host and a build server for installation on a server on the LAN. It's similar to Jenkins, TeamCity, Jira, etc. VSTS is Microsoft's "cloud" (that word irritates me) offering of TFS. Build (and Release) steps are simply configurable tasks that run given commands. (PowerShell, DOS, etc.) So yes, both TFS and VSTS are running vanilla Git.

Second, I'm not using --mirror to go from W to R1 (but I was for my R1-to-R2 attempts). I'm developing using Visual Studio (VS), which silently sends Git commands to the repo configured in its connection settings. (I'm still working out how to get a log of those commands for you. MS isn't making this part very easy, unfortunately.)

Next up, here's my solo-dev workflow:

1. Create a new repo on W
2. Create a new, empty repo on TFS (R1)
3. Branch, write code, commit, etc.
4. Use VS to push to the empty repo to TFS (at which point VS creates an origin remote on W's repo, pointing to R1)
5. Every time I push, TFS triggers a build and runs the steps that I've configured (to keep it simple let's leave Release out of the conversation and just say Build)
6. It's in one of those build steps that I need to sync an exact copy of R1's repo to R2

As such, I don't have any control over the Git command sequence when pushing from W to R1 (don't need to, really--that part is working just fine). I realize that I could create another connection in VS to go from W to R2, but that adds a cumbersome manual step to my operations. I wish to automate the process, so that everything gets synced as a result of pushing to R1.

Also, [git clone] isn't a part of this picture. (But I'm sure you noticed that.)
I read it and absorbed it as best I could. Thanks for the link.



> So, we're going to arrive at the first point I wanted to highlight:
> if you want to _mirror_ W to R1 — as in rewrite everything at R1 with
> whatever there is on W, — there's no point in using `git push --mirror`: it
> also sends your remote branches to the remote repository which has quite
> little sense and may confuse you when you look at R1.
>
> A better approach is to drop "--mirror" and use explicit "refspecs"
> for `git push` telling it to force-push all the branches and tags, and also
> remove at the remote the branches which names match those of the branches
> deleted locally:
>
> git push --force --prune --tags origin "refs/heads/*:refs/heads/*"

Given the above clarifications, would I instead run that on TFS to sync R1 to R2? And also, would the command be this instead:

[git push --force --prune --tags vsts "refs/heads/*:refs/heads/*"]

This of course assumes that there's a remote [vsts] on R1.



> It's like `git push --mirror origin` but omits the hierarchy
> 'refs/remotes/origin/*' from pushing.
>
>
> Before or after switching to this new approach you should possibly get rid of
> the pushed remote branches in both R1 and R2 — to lessen the amount of cruft
> in them. You can do this by running
>
> git push ":refs/remotes/*"

Not a problem. I can delete and recreate the repo with impunity.



> Not pushing the remote branches would get rid of those "origin/..."
> thingies in
>
> | POST git-receive-pack (744 bytes)
> | Pushing to https://Personal%20Access%20Token: [...] To
> | https://customer.visualstudio.com/Applications/_git/Application
> | = [up to date] master -> master
> | = [up to date] origin/develop -> origin/develop
> | - [deleted] Application/master
> | c13bdc5..81ddbe0 origin/master -> origin/master updating local
> | tracking ref 'refs/remotes/Application/master'
>
> See [4] for more info.

I tried, but that one's a bit over my head I'm afraid.



> The second point I'd like to make is that there exists useful commands which
> allow to _directly_ check what's there in any given remote repository. This
> may help enormously to rule out possible problems you may have with
> inspecting the state of R1 and R2 using whatever
> TFS- and VSTS-native things exists for this.
>
> The first one is `git ls-remote <URL_or_remote_name>` which contacts the
> remote repository, asks it to tell what it has and lists that in the form of
> pairs "commit hash + ref name".
> For example:
>
> | $ git ls-remote fork
> | 8fed8eb1c43d9c97bd5157a8760658438a0b3bd6 HEAD
> | 8fed8eb1c43d9c97bd5157a8760658438a0b3bd6 refs/heads/kaboom
> | 8fed8eb1c43d9c97bd5157a8760658438a0b3bd6 refs/heads/master

Doesn't this create a fork somewhere?



> So you may easily verify two things:
>
> - The remote repo of interest does really have a branch of interest.
> - The tip commit of that branch is expected.
>
> (Note that Git call everything which can be "named" "a ref", which is short
> for "reference", and the internal term for branch is "head".
> That's why branches live in the "refs/heads/" namespace, and tags live in
> "refs/tags".)
>
> Another — more high-level — command is
>
> $ git remote [-v] show <remotename>
>
> It does not show you commit names but it lays out which, if any, tracking
> policies are configured for the local branches against that remote.
>
>
> So, I'd go this way:
>
> 1) Switch to not using "full-mirror" pushing for W->R1 updates.
>
> 2) Delete all remote branches pushed from W to R1.
>
> 3) Afterwards, use `git ls-remote` to verify the contents of R1
> is expected as compared to the set of local branches in W.
>
> 4) When synchronizing R1 to R2, make sure the remote branches "poisoned"
> R1 from W are also deleted in R2 (in theory, plain
> `git push --mirror` should take care of that; if not, you may use
> that "push nothing" call I shown before to take care of that).
>
> 5) Use `git ls-remote` to verify R2 really mirrors the state of R1.

Do your recommendations change with my clarifications?



> To see which branches the local repository has, and at which commits they
> point, use something like
>
> git branch --list -v --no-abbrev
>
> You might also throw in "--all" or "--remote" to see local _and_ remote
> branches or only the remote branches.

That makes sense, thanks. I'll give it all a go. (But not until I hear from you on the potential changes.)

One other thing: in the comment on my SO Q&A (https://stackoverflow.com/q/50978056/722393), Andy Li suggests this technique:

How to mirror a git [sic] repo to a new remote
https://makandracards.com/makandra/48818-how-to-mirror-a-git-repo-to-a-new-remote

But this strikes me a something to be used for a one-time move, not an ongoing sync. Would you agree?

Jeff Bowman

unread,
Jun 26, 2018, 10:55:13 PM6/26/18
to Konstantin Khomoutov, git-for-windows
> -----Original Message-----
> From: Jeff Bowman
> Sent: Monday, June 25, 2018 6:37 PM
> To: 'Konstantin Khomoutov' <kos...@bswap.ru>
> Cc: git-for-windows <git-for...@googlegroups.com>
> Subject: RE: [git-for-windows] Trouble with Git mirroring
>
> That makes sense, thanks. I'll give it all a go. (But not until I hear from
> you on the potential changes.)

Like an impatient kid at Christmas who thinks he can't wait for morning, I got up in the middle of the night and tiptoed down to see what Santa Claus had brought.

I ran this from a console window on R1:

git push --force --prune --tags vsts refs/heads/*:refs/heads/*

Alas, however, my joy was short-lived. R2 was still several commits behind R1.

So I tried your next suggestion:

git push :refs/remotes/*

I got this:

fatal: You are not currently on a branch.
To push the history leading to the current (detached HEAD)
state now, use

git push :refs/remotes/* HEAD:<name-of-remote-branch>

So then I did this:

git checkout master

...and got this:

Previous HEAD position was f5b3f34 Build test
Switched to branch 'master'
Your branch is behind 'origin/master' by 35 commits, and can be fast-forwarded.
(use "git pull" to update your local branch)

Now we're starting to get somewhere!

Next:

git pull

...which produced this piece of wonderful, all-is-right-with-the-world goodness:

remote: Microsoft (R) Visual Studio (R) Team Foundation Server
remote: Found 3 objects to send. (8 ms)
Unpacking objects: 100% (3/3), done.
From http://server:8080/tfs/Collection/Prod/_git/Application
f5b3f34..4113145 master -> origin/master
Updating 67e6c4f..4113145
Fast-forward
.gitignore | 1 +
Controller/App.config | 8 +-
Controller/Classes/Reader/Client.vb | 2 +-
Controller/Classes/Utils.vb | 28 ++-
Controller/Classes/Writer/Client.vb | 20 ++
.../Writer/Controller.Writer.LogEntry.datasource | 10 +
.../Connected Services/Writer/HydraWriter.disco | 4 +
.../Connected Services/Writer/HydraWriter.xsd | 14 ++
.../Connected Services/Writer/HydraWriter1.xsd | 42 +++++
.../Connected Services/Writer/HydraWriter2.xsd | 16 ++
.../Connected Services/Writer/Reference.svcmap | 37 ++++
Controller/Connected Services/Writer/Reference.vb | 196 ++++++++++++++++++++
Controller/Connected Services/Writer/Service.wsdl | 39 ++++
.../Writer/configuration.svcinfo | 10 +
.../Writer/configuration91.svcinfo | 201 +++++++++++++++++++++
Controller/Controller.vbproj | 27 +++
Controller/Forms/Main.Designer.vb | 24 +++
Controller/Forms/Main.vb | 135 ++++++++------
CompanyInfo.vb | 2 +-
Queue/Classes/Dequeue.vb | 15 --
Reader/Modules/Main.vb | 54 +++---
Writer/App.config | 2 +
Writer/Classes/Registry.vb | 8 +-
Writer/Classes/Sender.vb | 6 +-
Writer/Classes/Service.vb | 6 +-
Writer/Classes/Utils.vb | 10 +-
Writer/Interfaces/IService.vb | 3 +-
Writer/Modules/Main.vb | 38 +---
Writer/My Project/Settings.Designer.vb | 92 +++++-----
Writer/My Project/Settings.settings | 6 +-
Writer/Writer.vbproj | 1 +
31 files changed, 856 insertions(+), 201 deletions(-)
create mode 100644 Controller/Classes/Writer/Client.vb
create mode 100644 Controller/Connected Services/Writer/Controller.Writer.LogEntry.datasource
create mode 100644 Controller/Connected Services/Writer/HydraWriter.disco
create mode 100644 Controller/Connected Services/Writer/HydraWriter.xsd
create mode 100644 Controller/Connected Services/Writer/HydraWriter1.xsd
create mode 100644 Controller/Connected Services/Writer/HydraWriter2.xsd
create mode 100644 Controller/Connected Services/Writer/Reference.svcmap
create mode 100644 Controller/Connected Services/Writer/Reference.vb
create mode 100644 Controller/Connected Services/Writer/Service.wsdl
create mode 100644 Controller/Connected Services/Writer/configuration.svcinfo
create mode 100644 Controller/Connected Services/Writer/configuration91.svcinfo

Excited, I quickly ran this again:

git push --force --prune --tags vsts refs/heads/*:refs/heads/*

...and guess what? Both repos were now in sync (on the Master branch, that is).

It even worked with the older mirroring command I was using:

Git push --mirror vsts

Finally, success!

Here're a few more things about TFS behaviors that I've learned today:

1. TFS stores the actual repository in its internal databases, not anywhere that's reachable from a command line or a script
2. The very first step of a given build is built-in; it gets the files from the repo (using git fetch) and copies them to a local folder so they can be reached by scripts
3. For some reason the local folder repo isn't getting fully updated; thus the reason I had to git pull (I think I'm going to want fetch, though, as I don't want any merging going on)

Here's what TFS does to get those files:

2018-06-26T00:56:07.5629921Z ##[section]Starting: Get Sources
2018-06-26T00:56:07.6190017Z Syncing repository: Application (TfsGit)
2018-06-26T00:56:07.6259987Z Prepending Path environment variable with directory containing 'git.exe'.
2018-06-26T00:56:07.6349957Z ##[command]git version
2018-06-26T00:56:07.8820285Z git version 2.17.1.windows.2
2018-06-26T00:56:07.8960295Z ##[command]git config --get remote.origin.url
2018-06-26T00:56:08.2116369Z ##[command]git config gc.auto 0
2018-06-26T00:56:08.4342432Z ##[command]git config --get-all http.http://server:8080/tfs/Collection/Prod/_git/Application.extraheader
2018-06-26T00:56:08.6892599Z ##[command]git config --get-all http.proxy
2018-06-26T00:56:09.1301402Z ##[command]git -c http.extraheader="AUTHORIZATION: bearer ********" fetch --tags --prune --progress origin
2018-06-26T00:56:10.1637238Z remote: Microsoft (R) Visual Studio (R) Team Foundation Server
2018-06-26T00:56:10.1697239Z remote:
2018-06-26T00:56:10.1707193Z remote: Found 3 objects to send. (8 ms)
2018-06-26T00:56:10.5821073Z From http://server:8080/tfs/Collection/Prod/_git/Application
2018-06-26T00:56:10.5831029Z fd96e91..f5b3f34 master -> origin/master
2018-06-26T00:56:10.6611095Z ##[command]git checkout --progress --force f5b3f34a2a84ffd530b55db82413ae6eca02b008
2018-06-26T00:56:10.9599216Z Previous HEAD position was fd96e91 Build test
2018-06-26T00:56:10.9599216Z HEAD is now at f5b3f34 Build test
2018-06-26T00:56:10.9669692Z ##[section]Finishing: Get Sources

So I think I've got this little bit figured out now. It looks like my build step script (running on R1) has to:

1. cd to the repo's local folder
2. git checkout the branch that triggered the build (this info is available via TFS environment variables)
3. git fetch from TFS (not sure why the first one didn't work, any ideas?)
4. git push to R2

I haven't tested it yet, but it seems to make sense given these new discoveries. I plan to test tomorrow, after which I'll post the results here.

Jeff Bowman

unread,
Jun 27, 2018, 11:04:54 PM6/27/18
to Konstantin Khomoutov, git-for-windows
> -----Original Message-----
> From: Jeff Bowman
> Sent: Tuesday, June 26, 2018 6:55 PM
> To: 'Konstantin Khomoutov' <kos...@bswap.ru>
> Cc: 'git-for-windows' <git-for...@googlegroups.com>
> Subject: RE: [git-for-windows] Trouble with Git mirroring
>
> So I think I've got this little bit figured out now. It looks like my build step script (running on R1) has to:
>
> 1. cd to the repo's local folder
> 2. git checkout the branch that triggered the build (this info is available via TFS environment variables)
> 3. git fetch from TFS (not sure why the first one didn't work, any ideas?)
> 4. git push to R2
>
> I haven't tested it yet, but it seems to make sense given these new
> discoveries. I plan to test tomorrow, after which I'll post the results here.

OK, I've got it working using these steps (with one significant difference, which may be a problem--see below). It even works with the original mirroring approach, indicating that the problem wasn't with mirroring at all but rather was with the states of my branches.

The difference is that [git fetch] doesn't work for this. For some reason the latest commits aren't fetched from the database-hosted TFS repo (let's call that R0) into the local repo (R1) in the folder hierarchy on the server. I don't why, but I have to use [git pull] to get that.

However, doesn't the [git pull] with its merges put R1 into a different state than W? If so, then the sync from R1 to R2 also places R2 into a different state than W. Since I'm using it for backup purposes only it may not matter, but I'm enough of a purist to at least wonder why this is the case.

Here're the commands and responses from my script:

. git checkout feature/edit-code-comment
Switched to branch 'feature/edit-code-comment'
Your branch is behind 'origin/feature/edit-code-comment' by 1 commit, and can be fast-forwarded.
(use "git pull" to update your local branch)

. git -c http.extraheader="AUTHORIZATION: bearer ********" pull
Updating 78ff2da..562df86
Fast-forward
OitInfo.vb | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)

. git push --mirror -v vsts
POST git-receive-pack (193 bytes)
Pushing to https://customer.visualstudio.com/Applications/_git/Application
To https://customer.visualstudio.com/Applications/_git/Application
= [up to date] develop -> develop
= [up to date] master -> master
= [up to date] origin/develop -> origin/develop
= [up to date] origin/feature/edit-code-comment -> origin/feature/edit-code-comment
= [up to date] origin/master -> origin/master
= [up to date] vsts/develop -> vsts/develop
= [up to date] vsts/feature/edit-code-comment -> vsts/feature/edit-code-comment
= [up to date] vsts/master -> vsts/master
78ff2da..562df86 feature/edit-code-comment -> feature/edit-code-comment
updating local tracking ref 'refs/remotes/vsts/develop'
updating local tracking ref 'refs/remotes/vsts/feature/edit-code-comment'
updating local tracking ref 'refs/remotes/vsts/master'

Like I said, isn't R1 now in a different state than W and R0?
Reply all
Reply to author
Forward
0 new messages