vsbehere
vsbehere

Reputation: 666

git fetch is not fetching changes that are present in remote specified

I have added github remote to my git repository using:

git remote add --mirror=push github repository_path.git

I am trying to fetch from remote using:

git fetch github

where, github is remote I have added in previous step.

I tried to git fetch for two repositories, in case of first repo, it is able to fetch changes that are present in remote. But in case of second repo git fetch is not able to fetch changes that are present in remote.

The difference between both repositories is, the repository in which fetch is working is created by me, and repository in which fetch is not working is created by other user, but I have permission to do pull and push in that repository

Can anyone help me to solve this issue?

Upvotes: 0

Views: 1439

Answers (1)

torek
torek

Reputation: 490068

TL;DR: --mirror=push defeated you

You probably did not want that option. It's not at all clear what options, if any, you did want.

Discussion: commits and refspecs

First (though not so important here), git fetch fetches commits, not changes. The (Git-specific) difference between a commit and a "change" is that a commit is a snapshot: a complete copy of a work-tree (or more precisely, the parts of the work-tree that were in the index at the time the commit was made). To turn a snapshot into a change, you must compare (i.e., git diff) it against some other snapshot. (And in fact, this is not quite accurate either: git fetch fetches objects, which include commits, but also include annotated tags, trees, and blobs. In general, though, we're mostly concerned with the commits.)

The most important part here, though, is this: the set of commits (and other objects) that git fetch fetches is controlled by refspecs. A refspec, in its second-simplest form, looks like this:

master:origin/master
refs/heads/*:refs/remotes/foo/*

These would be typical fetch refspecs for a remote named origin or foo. Yours is named github, so you might want, e.g.:

refs/heads/*:refs/remotes/github/*

as your default refspec. This tells your Git that, when the other Git—the foreign Git you are calling up over the Internet-phone, at the URL you specified earlier—offers refs/heads/master, refs/heads/branch, and refs/notes/commits, your Git should take it up on its master and branch branches, and ignore the commits notes.

The left hand side of each colon-separated pair is the name you would like your Git to match. You have no control over what their Git offers, but you have complete control over what you take (i.e., match).

The right hand side is the name you would like your Git to use in your repository. As you can see here, you do not have to use the same name that the other Git uses. You can use the same name, if that's what you want, but you do not have to, and usually you do not want to. Usually, you want to take their branch names, such as their master, and rename them to be your remote-tracking branch names: origin/master, for instance.

I mentioned above that this is the second simplest form of refspec. The simplest is one without a colon, such as master. If you use this colon-free form of refspec, git fetch does not give the fetched objects any name in your repository. This is what you are seeing.

(This is once again not 100% accurate: any git fetch writes to FETCH_HEAD. However, FETCH_HEAD is a remarkably temporary name. Specifically, the very next git fetch overwrites it, since any git fetch writes to FETCH_HEAD. So it might as well be "no name": it's only good until the next fetch. That's all that the original git pull script needed, back when git fetch was merely plumbing for the user-oriented pull script, before "remotes" were invented in the first place. But it's no good these days, when we want our remote-tracking branches updated.)

Why git fetch needs some default refspecs

If you run git fetch remote, you are running it with no refspec arguments. In this case, git fetch looks up the default refspecs in your Git configuration. Normally, git remote add sets these up for you:

$ git remote add foo ssh://host.dom.ain/path/to/repo.git

creates remote "foo" with two configured items:

$ git config --get remote.foo.url
ssh://host.dom.ain/path/to/repo.git
$ git config --get-all remote.foo.fetch
refs/heads/*:refs/remotes/foo/*

(We should always use --get-all here, in case there is more than one configured fetch line, but in fact git remote add only sets up one.)

You used git remote add --mirror=push, though, and that changes what git remote add sets up for the new remote. Instead of adding remote.foo.fetch set to refs/heads/*:refs/remotes/foo/*, it adds remote.foo.mirror set to true:

$ git config --get-regexp '^remote\.foo\.'
remote.foo.url ssh://host.dom.ain/path/to/repo.git
remote.foo.mirror true

In particular, there are no remote.foo.fetch entries.

(This --get-regexp lets us examine everything under a [remote "foo"] section. It is an alternative to --get-all that accepts an arbitrary regular expression, and we gave it one matching remote.foo. with a left anchor ^ and the two dot characters quoted to make sure they match a literal dot rather than any single character. Note that regular expressions are different from, and more powerful than, shell style globs: the fetch refspecs are shell-style globs, not regular expressions. None of this is stuff you need to know, it's just useful side information.)

Without refspecs, git fetch is hamstrung

Without either a command-line refspec:

git fetch github 'refs/heads/*:refs/remotes/github/*'

or a default remote.github.fetch, git fetch falls back on a weak and flimsy built-in default. It asks the other Git: "What's your most significant branch? Specifically, what commit is your HEAD?" It then fetches that commit (and its history as needed), and calls that FETCH_HEAD, in your repository:

From <url>
 * branch                HEAD       -> FETCH_HEAD

You now have the time from now until your next git fetch to do something with FETCH_HEAD.

Figure out why you used --mirror=push, then come up with the option you really want

Setting remote.remote.mirror to true has the same effect as specifying --mirror on the command line: it force-pushes all local refs to a ref of the same name on the remote, as if you used +refs/*:refs/*.1 But if the remote is a push mirror when compared to the local repository, you probably never want to fetch from it at all. (In other words, this combination—push mirror, but fetch-able—makes no sense.)


1Note that push refspecs differ somewhat from fetch refspecs. Both use the same syntax: an optional force flag, a source ref name or single-asterisk glob pattern, a colon, and a destination ref name or similar glob pattern. The source and destinations are reversed, though: for fetching, the source is the remote repository / other Git, while for pushing, the source is the local repository / our Git. The meaning of a missing destination differs as well: for fetch, a missing destination means use only FETCH_HEAD, but for push, a missing destination means use the same name, e.g., git push master means push (our) master to (their) master. Finally, fetch does not allow an empty source, but push does: that means ask the remote Git to delete the reference entirely.

Upvotes: 1

Related Questions