Hi all,
//for tldr; see EOM
I recently setup git for my team and we ran into a small annoyance. Though this may be solved with a simple alias, I decided to spend this Saturday afternoon exploring the Git source to find out how easy an actual patch could be. Unfortunately, the fix isn't trivial and I'm not familiar with the Git code base. I'd definitely prefer fixing issue properly in the source since the behaviour is, in my opinion, inconsistent, so I'll need some insights from experienced git developers.

Let me first describe the issue. Within a single repository, multiple ref name spaces can be defined to hold, for example, user branches. For example, the main development branches can be located under refs/heads/[master | develop] while users push their personal branches to refs/user/[bob | alice]/heads/* in order to save their work or collaborate with one another. The problem arises when fetching from configured fetch refspecs. The behaviour isn't consistent with the push behaviour. Let's look at an example:

Alice has a repository with a configured remote named foo:
[remote "foo"]
    url = ...
    fetch = +refs/heads/*:refs/remotes/foo/*
    fetch = +refs/users/bob/heads/*:refs/remotes/foo/bob/
    push = refs/heads/*:refs/users/alice/heads/*

Let's say the following refs exist on the remote:

If Alice has a master and feature_a branch, 'git push foo' would result in:
master -> refs/users/alice/heads/master
feature_a -> refs/users/alice/heads/feature/a

'git push foo feature/a' would result in:
feature/a -> refs/users/alice/heads/feature/a

'git fetch foo' would result in:
[new ref] refs/users/bob/heads/master -> foo/bob/master //OK
[new ref] refs/users/bob/heads/develop -> foo/bob/develop //OK
[new ref] refs/heads/master -> foo/master //OK

'git fetch foo refs/users/bob/heads/develop' would result in (FETCH_HEAD omitted):
[new ref] refs/users/bob/heads/develop -> foo/bob/develop //OK

'git fetch foo refs/heads/master' would result in (FETCH_HEAD omitted):
[new ref] refs/heads/master -> foo/master //OK

'git fetch foo develop' would result in:
fatal: Couldn't find remote ref test2 //Not OK, (case 1)

'git fetch foo master' would result in (FETCH_HEAD omitted):
[new ref] refs/heads/master -> foo/master //OK, but missing another ref! (case 2)
//It should also fetch refs/users/bob/heads/master -> foo/bob/master

If you remove this configuration line: fetch = +refs/heads/*:refs/remotes/foo/*
Then you run 'git fetch foo master', this would result in:
* branch master -> FETCH_HEAD //Debatable whether this is OK or not, but it's definitely missing bob's master! (case 3)

case 1: The ref abbreviation is received in builtin/fetch.c: get_ref_map and passed to remote.c:get_fetch_map with missing_ok = 0. The abbreviated ref is then evaluated through remote.c:get_remote_ref --> remote.c:find_ref_by_name_abbrev to finally end up being matched in refs.c:ref_rev_parse_rules. Since in this case, develop doesn't exist under 'develop', 'refs/develop', 'refs/tags/develop', 'refs/heads/develop', 'refs/remotes/develop' or 'refs/remotes/develop/HEAD', no ref is matched and the command dies (due to missing_ok=0). All of this happens without ever looking at the configured fetch refspec.

case 2: Starts like case 1, except that the ref_rev_parse_rules get a match and only this match is used. The destination is then mapped in a later call to get_fetch_map, from builtin/fetch.c@311

case 3: Same as 2, but it maps to HEAD.

In my opinion, these issues can be resolved with the following rules:
1. Explicit refs (starts with refs/) are fetched 'as-is' and put into FETCH_HEAD and into every configured refspec->dst for which refspec->src matches the input ref. (The push command should be made the same for consistency. It currently only pushes to the first matching refspec.) 2. Abbreviated refs (e.g. 'master') are matched only against the remote_refs matching remote->fetch_refspec (I think remote.c get_expanded_map can provide this). If multiple matches exist, all of them are fetched to their corresponding refspec->dst (and FETCH_HEAD). If multiple matches exist and have conflicting refspec->dst, error out (or should we honour the first matching refspec?).

Note that for the 2nd rule, if remote->fetch_refspec doesn't include refs/heads/*, it means I went through the trouble of removing it from the configuration. Therefore, I think it shouldn't even be fetched. However, we need to make sure to handle 'git fetch <URL> master', such that it is still evaluated by ref_rev_parse_rules.

Any input is be appreciated.
Lewis Diamond

To unsubscribe from this list: send the line "unsubscribe git" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Reply via email to