> I assume you mean "merge = refs/heads/main" from your examples You're correct; I manually rewrote `master` to `main` in my initial email and missed that one instance. > - instead of branch.*.pushremote, I usually set remote.pushdefault, so > that it covers all branches (i.e., I'd never want to push to the > upstream remote, which I do not even have access to). > > - in a workflow like this, I generally have push.default set to > "current" > > - I make frequent use of @{push} to refer to the matching branch on my > remote (e.g., after doing some local work, I might use "git > range-diff upstream @{push} HEAD" to examine the changes before > pushing them up). This makes sense. I rarely actually write `FETCH_HEAD` manually; in my email, I expanded the value of aliases I use. Specifically, my "get everything up-to-date" alias [0] uses `FETCH_HEAD` -- changing this to `@{u}` makes a lot of sense. [0]: https://github.com/sudoforge/dotfiles/blob/trunk/git/.config/git/config#L104 > > however `git-pull --rebase=true` [works] > > That is running a new git-fetch under the hood, which will overwrite > FETCH_HEAD (and will only fetch from upstream, since that's what's in > branch.main.remote). D'oh -- I wasn't focusing on the fact that this would only fetch the branch's configured remote; I blame it on tunnel vision. To the rest of your message: I did not understand the behavior of `fetch --all`; what you've described makes sense to me (although I would agree that it sounds like there's a bug). Between the following steps I'll take, I think my fork workflow is "fixed": * Refactor away from usage of FETCH_HEAD * Set `remote.pushdefault = origin` * Set `push.default = current` (instead of `simple`, and is what my global config sets this to) I appreciate your insight and the identification of the problem. -- Ben Denhartog ben@xxxxxxxxxxxxx On Fri, Dec 4, 2020, at 03:13, Jeff King wrote: > On Thu, Dec 03, 2020 at 06:26:15PM -0700, Ben Denhartog wrote: > > > I have a few repositories on my system that exist primarily as local copies of remote repositories, in that I normally just want to track and follow the upstream project (however, I periodically contribute back upstream so they are technically forks -- origin is my remote, upstream is theirs). > > > > In these repositories, I set the following configuration: > > > > ``` > > [remote "origin"] > > url = https://git.foo.com/me/bar.git > > fetch = +refs/heads/*:refs/remotes/origin/* > > [remote "upstream"] > > url = https://git.foo.com/them/bar.git > > fetch = +refs/heads/main:refs/remotes/upstream/main > > tagopt = --no-tags > > [branch "main"] > > remote = upstream > > pushRemote = origin > > merge = refs/heads/master > > rebase = true > > ``` > > I use a similar setup myself, and it works well for this kind of > triangular flow. A few notes: > > - I assume you mean "merge = refs/heads/main" from your examples > > - instead of branch.*.pushremote, I usually set remote.pushdefault, so > that it covers all branches (i.e., I'd never want to push to the > upstream remote, which I do not even have access to). > > - in a workflow like this, I generally have push.default set to > "current" > > - I make frequent use of @{push} to refer to the matching branch on my > remote (e.g., after doing some local work, I might use "git > range-diff upstream @{push} HEAD" to examine the changes before > pushing them up). > > > Based on my understanding of the branch configuration options, this > > should effectively force my local `main` branch to track against > > `upstream/main`, but push to `origin/main`. I notice what I believe to > > be odd behavior when fetching: that FETCH_HEAD doesn't resolve to > > `upstream/main` as I would expect: > > > > ➜ git fetch --all > > Fetching origin > > Fetching upstream > > remote: Enumerating objects: 23, done. > > remote: Counting objects: 100% (23/23), done. > > remote: Total 32 (delta 23), reused 23 (delta 23), pack-reused 9 > > Unpacking objects: 100% (32/32), 12.97 KiB | 949.00 KiB/s, done. > > From https://git.foo.com/them/bar > > 63f7159..e65b80e main -> upstream/main > > The culprit here is using "git fetch --all". It triggers the sub-fetches > with --append, so they'll each add to FETCH_HEAD instead of overwriting > it. We do truncate it before the first one, so after this completes it > should have the complete set of refs fetched from both remotes (even if > it was a noop to fetch one of them, anything mentioned in the refspecs > shows up in FETCH_HEAD). > > Which is what you're seeing here: > > > ➜ cat .git/FETCH_HEAD > > 23e6881719f661c37336d9fcf7a9005a7dfce0cf not-for-merge branch 'main' of https://git.foo.com/me/foo > > e65b80edd2a2162f67120a98e84bb489f15fcf97 branch 'main' of https://git.foo.com/them/foo > > FETCH_HEAD is not just a ref, but contains some magic instructions that > get interpreted by git-pull. But when programs besides git-pull try to > resolve it to a single object, they just pick whichever value is first. > > So I do think there's a bug there, or at least something not > well-thought-out in the way that "fetch --all" appends. Normally fetch > tries to put the merge branch (or branches) first in the file, exactly > so this naive "take the first one" lookup will do the most sensible > thing. But when running multiple fetches that all append, we get their > individual outputs in the order of fetch (which in turn is the same as > the order in the config file). > > Perhaps the parent "git fetch --all" triggering the sub-fetches should > reorder FETCH_HEAD after they've all finished (to pull any merge heads > up to the top, regardless of which remote they came from). > > But an obvious workaround, if you know you'll always be merging from > upstream, is to just reorder your config stanzas so that it's fetched > first (likewise you can run "git fetch --multiple upstream origin" to > specify the order manually, or define a group with remotes.<group>). > > One reason I suspect nobody has come across this before is that there's > not much reason to use FETCH_HEAD in this setting. If you know you want > to rebase again upstream/main, then there are a number of ways you can > refer to that: > > - upstream/main > > - upstream, if your refs/remotes/upstream/HEAD is set up (if you > didn't clone, you may want to run "git remote set-head upstream -a") > > - @{upstream}, if it's configured as your upstream (which it is here) > > - @{u}, a shorter synonym > > - nothing at all, since rebasing against the upstream is the default > for git-rebase these days > > - you can say that (or even just "upstream", if your > refs/remotes/origin/HEAD is set up) > > > Curiously, `git rebase FETCH_HEAD` seems to think the local branch is > > up to date (erroneously), > > That's what I'd expect, since it is doing the naive "what is the first > one in the file" lookup. > > > however `git-pull --rebase=true` [works] > > That is running a new git-fetch under the hood, which will overwrite > FETCH_HEAD (and will only fetch from upstream, since that's what's in > branch.main.remote). > > > `git-merge FETCH_HEAD` both work as expected and merge/rebase with > > `upstream/main`. > > I think git-merge, like git-pull, understands the magic FETCH_HEAD > format. > > > Am I going about this incorrectly? The main purpose behind configuring > > my "mostly just a fork" repository is that it simplifies tracking > > against an upstream remote for projects which I do not work on > > actively. Of course, you might argue that I don't need to keep my > > remote around for this purpose and can just use a straightforward > > `git-clone` here -- but I'd rather not, and would prefer responses > > addressing the perceived bug rather than suggesting this particular > > alternative workflow. > > I think your workflow is perfectly reasonable. > > -Peff >