* Should --update-refs exclude refs pointing to the current HEAD? @ 2023-04-17 8:21 Stefan Haller 2023-04-17 8:30 ` Stefan Haller ` (4 more replies) 0 siblings, 5 replies; 18+ messages in thread From: Stefan Haller @ 2023-04-17 8:21 UTC (permalink / raw) To: git; +Cc: Derrick Stolee, , Elijah Newren, , Phillip Wood The --update-refs option of git rebase is so useful that I have it on by default in my config. For stacked branches I find it hard to think of scenarios where I wouldn't want it. However, there are cases for non-stacked branches (i.e. other branches pointing at the current HEAD) where updating them is undesirable. In fact, pretty much always, for me. Two examples, both very similar: 1. I have a topic branch which is based off of master; I want to make a copy of that branch and rebase it onto devel, just to try if that would work. I don't want the original branch to be moved along in this case. 2. I have a topic branch, and I want to make a copy of it to make some heavy history rewriting experiments. Again, my interactive rebases would always rebase both branches in the same way, not what I want. In this case I could work around it by doing the experiments on the original branch, creating a tag beforehand that I could reset back to if the experiments fail. But maybe I do want to keep both branches around for a while for some reason. Both of these cases could be fixed by --update-refs not touching any refs that point to the current HEAD. I'm having a hard time coming up with cases where you would ever want those to be updated, in fact. Any opinions? -Stefan ^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: Should --update-refs exclude refs pointing to the current HEAD? 2023-04-17 8:21 Should --update-refs exclude refs pointing to the current HEAD? Stefan Haller @ 2023-04-17 8:30 ` Stefan Haller 2023-04-17 8:34 ` Kristoffer Haugsbakk ` (3 subsequent siblings) 4 siblings, 0 replies; 18+ messages in thread From: Stefan Haller @ 2023-04-17 8:30 UTC (permalink / raw) To: git; +Cc: Derrick Stolee, Elijah Newren, Phillip Wood On 17.04.23 10:21, Stefan Haller wrote: > The --update-refs option of git rebase is so useful that I have it on by > default in my config. For stacked branches I find it hard to think of > scenarios where I wouldn't want it. > > However, there are cases for non-stacked branches (i.e. other branches > pointing at the current HEAD) where updating them is undesirable. In > fact, pretty much always, for me. Two examples, both very similar: > > 1. I have a topic branch which is based off of master; I want to make a > copy of that branch and rebase it onto devel, just to try if that would > work. I don't want the original branch to be moved along in this case. > > 2. I have a topic branch, and I want to make a copy of it to make some > heavy history rewriting experiments. Again, my interactive rebases would > always rebase both branches in the same way, not what I want. In this > case I could work around it by doing the experiments on the original > branch, creating a tag beforehand that I could reset back to if the > experiments fail. But maybe I do want to keep both branches around for a > while for some reason. > > Both of these cases could be fixed by --update-refs not touching any > refs that point to the current HEAD. I'm having a hard time coming up > with cases where you would ever want those to be updated, in fact. One question then is whether the behavior makes sense for the case where you have a stack of branches, and you make a copy of the topmost one and then do either of the two above scenarios with that copy. With my proposal it would leave the old top of the stack alone, but it *would* update all the inner branches of the stack. Whether that's desired or not is very unclear to me, and I would leave it up to user to add --no-update-refs in this case if they don't want it. -Stefan ^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: Should --update-refs exclude refs pointing to the current HEAD? 2023-04-17 8:21 Should --update-refs exclude refs pointing to the current HEAD? Stefan Haller 2023-04-17 8:30 ` Stefan Haller @ 2023-04-17 8:34 ` Kristoffer Haugsbakk 2023-04-17 9:22 ` Stefan Haller 2023-04-17 12:14 ` Phillip Wood ` (2 subsequent siblings) 4 siblings, 1 reply; 18+ messages in thread From: Kristoffer Haugsbakk @ 2023-04-17 8:34 UTC (permalink / raw) To: Stefan Haller; +Cc: Derrick Stolee, , Elijah Newren, , Phillip Wood, git Hi On Mon, Apr 17, 2023, at 10:21, Stefan Haller wrote: > 2. I have a topic branch, and I want to make a copy of it to make some > heavy history rewriting experiments. Again, my interactive rebases would > always rebase both branches in the same way, not what I want. In this > case I could work around it by doing the experiments on the original > branch, creating a tag beforehand that I could reset back to if the > experiments fail. But maybe I do want to keep both branches around for a > while for some reason. I would use a lightweight tag, too, since this option doesn’t touch tags.[1] Why do you want to keep both branches around? I would keep the tag around and then branch off of that if I want to make another divergent history in the future. This is interesting to me since copying branches indeed does not seem to *gel* with this git-rebase(1) option. But I never really understood the use-case for copying branches rather than using lightweight tags. † 1: I wonder why it wasn’t called `--update-branches`. On the one hand, the option ignores refs other than branches. On the other hand, the command in the todo list *will* update tags if you tell it to, and even refs like `/refs/notes/*`. But `--update-branches` seems like a better name, at least outside the todo editor. -- Kristoffer Haugsbakk ^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: Should --update-refs exclude refs pointing to the current HEAD? 2023-04-17 8:34 ` Kristoffer Haugsbakk @ 2023-04-17 9:22 ` Stefan Haller 2023-04-18 2:00 ` Felipe Contreras 0 siblings, 1 reply; 18+ messages in thread From: Stefan Haller @ 2023-04-17 9:22 UTC (permalink / raw) To: Kristoffer Haugsbakk; +Cc: Derrick Stolee, Elijah Newren, Phillip Wood, git On 17.04.23 10:34, Kristoffer Haugsbakk wrote: > Hi > > On Mon, Apr 17, 2023, at 10:21, Stefan Haller wrote: >> 2. I have a topic branch, and I want to make a copy of it to make some >> heavy history rewriting experiments. Again, my interactive rebases would >> always rebase both branches in the same way, not what I want. In this >> case I could work around it by doing the experiments on the original >> branch, creating a tag beforehand that I could reset back to if the >> experiments fail. But maybe I do want to keep both branches around for a >> while for some reason. > > I would use a lightweight tag, too, since this option doesn’t touch tags.[1] > > Why do you want to keep both branches around? Several reasons: Maybe the original branch was pushed already, and I'm collaborating on it with a coworker. At the same time, I want to run my rebase experiment in parallel on a copy. Maybe I want to create github PRs for both of them, in order to run CI on them, or get feedback for both of them from my coworkers. Also, it just seems to be the most natural workflow for many people. I have seen my coworkers do this a lot without thinking much whether there would be a better way. My question is not so much whether copying branches is a good idea, it's more about how --update-refs should deal with copied branches *if* you decide to use them. -Stefan ^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: Should --update-refs exclude refs pointing to the current HEAD? 2023-04-17 9:22 ` Stefan Haller @ 2023-04-18 2:00 ` Felipe Contreras 0 siblings, 0 replies; 18+ messages in thread From: Felipe Contreras @ 2023-04-18 2:00 UTC (permalink / raw) To: Stefan Haller, Kristoffer Haugsbakk Cc: Derrick Stolee, Elijah Newren, Phillip Wood, git Stefan Haller wrote: > On 17.04.23 10:34, Kristoffer Haugsbakk wrote: > > On Mon, Apr 17, 2023, at 10:21, Stefan Haller wrote: > >> 2. I have a topic branch, and I want to make a copy of it to make some > >> heavy history rewriting experiments. Again, my interactive rebases would > >> always rebase both branches in the same way, not what I want. In this > >> case I could work around it by doing the experiments on the original > >> branch, creating a tag beforehand that I could reset back to if the > >> experiments fail. But maybe I do want to keep both branches around for a > >> while for some reason. > > > > I would use a lightweight tag, too, since this option doesn’t touch tags.[1] > > > > Why do you want to keep both branches around? > > Several reasons: > > Maybe the original branch was pushed already, and I'm collaborating on > it with a coworker. At the same time, I want to run my rebase experiment > in parallel on a copy. > > Maybe I want to create github PRs for both of them, in order to run CI > on them, or get feedback for both of them from my coworkers. > > Also, it just seems to be the most natural workflow for many people. I > have seen my coworkers do this a lot without thinking much whether there > would be a better way. I also do this, however, I often create a new branch to point to the previous one (`git branch foo-1`). I know I can refer to it with `foo@{1}`, but then I have to keep track if I rebase more than once, or do any other reflog operation. If I've sent the series for review with my tool `git send-series`, then I don't have to worry about that because I have refs for every version I sent. A notion of branch versions really comes in handy. Cheers. -- Felipe Contreras ^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: Should --update-refs exclude refs pointing to the current HEAD? 2023-04-17 8:21 Should --update-refs exclude refs pointing to the current HEAD? Stefan Haller 2023-04-17 8:30 ` Stefan Haller 2023-04-17 8:34 ` Kristoffer Haugsbakk @ 2023-04-17 12:14 ` Phillip Wood 2023-04-20 15:27 ` Stefan Haller 2024-03-05 7:40 ` Stefan Haller 2024-03-24 10:42 ` Stefan Haller 4 siblings, 1 reply; 18+ messages in thread From: Phillip Wood @ 2023-04-17 12:14 UTC (permalink / raw) To: Stefan Haller, git; +Cc: Derrick Stolee, Elijah Newren, Kristoffer Haugsbakk Hi Stefan On 17/04/2023 09:21, Stefan Haller wrote: > The --update-refs option of git rebase is so useful that I have it on by > default in my config. For stacked branches I find it hard to think of > scenarios where I wouldn't want it. > > However, there are cases for non-stacked branches (i.e. other branches > pointing at the current HEAD) where updating them is undesirable. In > fact, pretty much always, for me. Two examples, both very similar: > > 1. I have a topic branch which is based off of master; I want to make a > copy of that branch and rebase it onto devel, just to try if that would > work. I don't want the original branch to be moved along in this case. > > 2. I have a topic branch, and I want to make a copy of it to make some > heavy history rewriting experiments. Again, my interactive rebases would > always rebase both branches in the same way, not what I want. In this > case I could work around it by doing the experiments on the original > branch, creating a tag beforehand that I could reset back to if the > experiments fail. But maybe I do want to keep both branches around for a > while for some reason. > > Both of these cases could be fixed by --update-refs not touching any > refs that point to the current HEAD. I'd use a detached HEAD for the "experimental" rebase and then update the branch if the rebase was successful. If you really want to use another branch you could try running "git commit --amend --only" before rebasing to update the commit date so the two branches don't point to the same commit. We could add a command line option to restrict the branches that are updated by --update-refs but I'm not that enthusiastic about it. > I'm having a hard time coming up > with cases where you would ever want those to be updated, in fact. If a user using stacked branches creates a new branch and then realizes they need to fix something on the parent before creating any commits on the new branch they would want both to be updated. e.g. $ git symbolic-ref HEAD refs/heads/topic $ git checkout -b another-topic # fix a bug in topic - want topic and another-topic to be # updated $ git rebase -i --update-refs HEAD~2 Best Wishes Phillip > Any opinions? > > -Stefan > ^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: Should --update-refs exclude refs pointing to the current HEAD? 2023-04-17 12:14 ` Phillip Wood @ 2023-04-20 15:27 ` Stefan Haller 0 siblings, 0 replies; 18+ messages in thread From: Stefan Haller @ 2023-04-20 15:27 UTC (permalink / raw) To: phillip.wood, git; +Cc: Derrick Stolee, Elijah Newren, Kristoffer Haugsbakk On 17.04.23 14:14, Phillip Wood wrote: > On 17/04/2023 09:21, Stefan Haller wrote: >> Both of these cases could be fixed by --update-refs not touching any >> refs that point to the current HEAD. > >> I'm having a hard time coming up >> with cases where you would ever want those to be updated, in fact. > > If a user using stacked branches creates a new branch and then realizes > they need to fix something on the parent before creating any commits on > the new branch they would want both to be updated. e.g. > $ git symbolic-ref HEAD > refs/heads/topic > $ git checkout -b another-topic > # fix a bug in topic - want topic and another-topic to be > # updated > $ git rebase -i --update-refs HEAD~2 OK, this is indeed one situation where my proposed change would do the wrong thing. It is of course impossible for git to tell whether you were meaning to create a stack of branches here, or whether this is one of the cases where I'm creating a copy of a branch and want to "detach" it from its source branch, as in the examples I posted earlier in this thread. In my personal experience the latter is much more common than the former, and it's also easier to correct the mistake manually in your example by hard-resetting one branch to the other again, so I still think it would be a useful change. Any other opinions about this? -Stefan ^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: Should --update-refs exclude refs pointing to the current HEAD? 2023-04-17 8:21 Should --update-refs exclude refs pointing to the current HEAD? Stefan Haller ` (2 preceding siblings ...) 2023-04-17 12:14 ` Phillip Wood @ 2024-03-05 7:40 ` Stefan Haller 2024-03-05 16:22 ` Junio C Hamano 2024-03-07 7:59 ` Kristoffer Haugsbakk 2024-03-24 10:42 ` Stefan Haller 4 siblings, 2 replies; 18+ messages in thread From: Stefan Haller @ 2024-03-05 7:40 UTC (permalink / raw) To: git; +Cc: Derrick Stolee, Elijah Newren, Phillip Wood, Christian Couder On 17.04.23 10:21, Stefan Haller wrote: > The --update-refs option of git rebase is so useful that I have it on by > default in my config. For stacked branches I find it hard to think of > scenarios where I wouldn't want it. > > However, there are cases for non-stacked branches (i.e. other branches > pointing at the current HEAD) where updating them is undesirable. In > fact, pretty much always, for me. Two examples, both very similar: > > 1. I have a topic branch which is based off of master; I want to make a > copy of that branch and rebase it onto devel, just to try if that would > work. I don't want the original branch to be moved along in this case. > > 2. I have a topic branch, and I want to make a copy of it to make some > heavy history rewriting experiments. Again, my interactive rebases would > always rebase both branches in the same way, not what I want. In this > case I could work around it by doing the experiments on the original > branch, creating a tag beforehand that I could reset back to if the > experiments fail. But maybe I do want to keep both branches around for a > while for some reason. > > Both of these cases could be fixed by --update-refs not touching any > refs that point to the current HEAD. I'm having a hard time coming up > with cases where you would ever want those to be updated, in fact. Coming back to this after almost a year, I can say that I'm still running into this problem relatively frequently, and it is annoying every single time. Excluding refs pointing at the current head from being updated, as proposed above, would be a big usability improvement for me. And I now see that "git replay --contained --onto" has the same problem, which I find very unfortunate. In my opinion, "contained" should only include refs that form a stack, but not copies of the current branch. Of course, since branch stacks are only a heuristic and not a built-in concept, it's impossible for git to distinguish between a pair of copied branches and a degenerate stack whose top-most branch is (still) empty, as in the example in [1]. In my personal experience though, degenerate stacks like that are very rare, but copied branches are not, so for me it would make a lot of sense to change the behavior of both "rebase --update-refs" and "replay --contained". -Stefan [1] <https://public-inbox.org/git/ 98548a5b-7d30-543b-b943-fd48d8926a33@gmail.com/> ^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: Should --update-refs exclude refs pointing to the current HEAD? 2024-03-05 7:40 ` Stefan Haller @ 2024-03-05 16:22 ` Junio C Hamano 2024-03-06 2:57 ` Elijah Newren 2024-03-07 7:59 ` Kristoffer Haugsbakk 1 sibling, 1 reply; 18+ messages in thread From: Junio C Hamano @ 2024-03-05 16:22 UTC (permalink / raw) To: Stefan Haller Cc: git, Derrick Stolee, Elijah Newren, Phillip Wood, Christian Couder Stefan Haller <lists@haller-berlin.de> writes: >> Both of these cases could be fixed by --update-refs not touching any >> refs that point to the current HEAD. I'm having a hard time coming up >> with cases where you would ever want those to be updated, in fact. The point of "update-refs", as I understand it, is that in addition to the end point of the history (E in "git rebase --onto N O E"), any branch tips that are between O..E can be migrated to point at their rewritten counterparts. So I am not sure how it fundamentally solves much by protecting only refs that point at a single commit ("the current HEAD" in your statement). When I want to see how the rebased history would look like without touching the original, I often rebase a detached HEAD (i.e. instead of the earlier one, use "git rebase --onto N O E^0", or when rebasing the current branch, "git rebase [--onto N] O HEAD^0") and that would protect the current branch well, but --update-refs of course would not work well. There is no handy place like detached HEAD that can be used to save rewritten version of these extra branch tips. If branch tips A, B, and C are involved in the range of commits being rewritten, one way to help us in such a situation may be to teach "git rebase" to (1) somehow create a new set of proposed-A, proposed-B, and proposed-C refs (they do not have to be branches), while keeping the original A, B, and C intact, (2) allow us to inspect the resulting refs, compare the corresponding ones from these two sets, and (3) allow us to promote (possibly a subset of) proposed- ones to their counterpart real branches after we inspect them. The latter two do not have to be subcommands of "git rebase" but can be separate and new commands. ^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: Should --update-refs exclude refs pointing to the current HEAD? 2024-03-05 16:22 ` Junio C Hamano @ 2024-03-06 2:57 ` Elijah Newren 2024-03-06 21:00 ` Stefan Haller 0 siblings, 1 reply; 18+ messages in thread From: Elijah Newren @ 2024-03-06 2:57 UTC (permalink / raw) To: Junio C Hamano Cc: Stefan Haller, git, Derrick Stolee, Phillip Wood, Christian Couder [Restoring part of Stefan's earlier message so I can respond to both that piece, as well as add to the ideas Junio presents.] Hi, On Tue, Mar 5, 2024 at 8:22 AM Junio C Hamano <gitster@pobox.com> wrote: > > Stefan Haller <lists@haller-berlin.de> writes: > > >> And I now see that "git replay --contained --onto" has the same problem, > >> which I find very unfortunate. In my opinion, "contained" should only > >> include refs that form a stack, but not copies of the current branch. I wouldn't want to change the default. Even if we were to add an option, I'm not entirely sure what it should even implement. In addition to Phillip's previous response in the thread, and part of Junio's response below (which I'll add to): 1) What if there is a branch that is "just a copy" of one of the branches earlier in the "stack"? Since it's "just a copy", shouldn't it be excluded for similar reasons to what you are arguing? And, if so, which branch is the copy? 2) Further, a "stack", to me at least, suggests a linear history without branching (i.e. each commit has at most one parent _and_ at most one child among the commits in the stack). I designed `git replay` to handle diverging histories (i.e. rebasing multiple branches that _might_ share a subset of common history but none necessarily need to fully contain the others, though perhaps the branches do share some other contained branches), and I want it to handle replaying merges as well. While `git rebase --update-refs` is absolutely limited to "stacks", and thus your argument might make sense in the context of `git rebase`, since you are bringing `git replay` into the mix, it needs to apply beyond a stack of commits. It's not clear to me how to genericize your suggestions to handle cases other than a simple stack of commits, though. 3) This is mostly covered in (1) and (2), but to be explicit: `git replay` is completely against the HEAD-is-special assumptions that are pervasive within `git rebase`, and your problem is entirely phrased as HEAD-is-special due to your call out of "the current branch". Is your argument limited to such special cases? (If so, it might still be valid for `git rebase`, of course.) 4) Aren't there easier ways to handle this -- for both rebase and replay? I'll suggest some alternatives below... > >> Both of these cases could be fixed by --update-refs not touching any > >> refs that point to the current HEAD. I'm having a hard time coming up > >> with cases where you would ever want those to be updated, in fact. > > The point of "update-refs", as I understand it, is that in addition > to the end point of the history (E in "git rebase --onto N O E"), > any branch tips that are between O..E can be migrated to point at > their rewritten counterparts. So I am not sure how it fundamentally > solves much by protecting only refs that point at a single commit > ("the current HEAD" in your statement). > > When I want to see how the rebased history would look like without > touching the original, I often rebase a detached HEAD (i.e. instead > of the earlier one, use "git rebase --onto N O E^0", or when > rebasing the current branch, "git rebase [--onto N] O HEAD^0") and > that would protect the current branch well, but --update-refs of > course would not work well. There is no handy place like detached > HEAD that can be used to save rewritten version of these extra > branch tips. > > If branch tips A, B, and C are involved in the range of commits > being rewritten, one way to help us in such a situation may be to > teach "git rebase" to (1) somehow create a new set of proposed-A, > proposed-B, and proposed-C refs (they do not have to be branches), > while keeping the original A, B, and C intact, (2) allow us to > inspect the resulting refs, compare the corresponding ones from > these two sets, and (3) allow us to promote (possibly a subset of) > proposed- ones to their counterpart real branches after we inspect > them. The latter two do not have to be subcommands of "git rebase" > but can be separate and new commands. Here, Junio is suggesting one alternative, and it's already implemented in `git replay`. Let me extend upon it and add two other alternatives as well: 4a) `git replay` does what Junio suggests naturally, since it doesn't update the refs but instead gives commands which can be fed to `git update-ref --stdin`. Thus, users can inspect the output of `git replay` and only perform the updates they want (by feeding a subset of the lines to update-ref --stdin). 4b) For `git replay`, --contained is just syntactic sugar -- it isn't necessary. git replay will allow you to list multiple branches that you want replayed, so you can specify which branches are relevant to you. (This doesn't help with `git rebase`, because `--update-refs` is the only way to get additional branches replayed.) 4c) For `git rebase --update-refs`, you can add `--interactive` and then delete the `update-ref` line(s) corresponding to the refs you don't want updated. ^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: Should --update-refs exclude refs pointing to the current HEAD? 2024-03-06 2:57 ` Elijah Newren @ 2024-03-06 21:00 ` Stefan Haller 2024-03-07 5:36 ` Elijah Newren 0 siblings, 1 reply; 18+ messages in thread From: Stefan Haller @ 2024-03-06 21:00 UTC (permalink / raw) To: Elijah Newren, Junio C Hamano Cc: git, Derrick Stolee, Phillip Wood, Christian Couder On 06.03.24 03:57, Elijah Newren wrote: > 1) What if there is a branch that is "just a copy" of one of the > branches earlier in the "stack"? Since it's "just a copy", shouldn't > it be excluded for similar reasons to what you are arguing? And, if > so, which branch is the copy? This is a good point, but in my experience it's a lot more rare. Maybe I'm looking at all this just from my own experience, and there might be other usecases that are very different from mine, but as far as I am concerned, copies of branches are not long-lived. There is no point in having two branches point at the same commit. When I create a copy of a branch, I do that only to rebase the copy somewhere else _immediately_, leaving the original branch where it was. Which means that I encounter copied branches only at the top of the stack, not in the middle. Which means that I'm fine with keeping the current behavior of "rebase --update-ref" to update both copies of that middle-of-the-stack branch, because it never happens in practice for me. > 2) Further, a "stack", to me at least, suggests a linear history > without branching (i.e. each commit has at most one parent _and_ at > most one child among the commits in the stack). I designed `git > replay` to handle diverging histories (i.e. rebasing multiple branches > that _might_ share a subset of common history but none necessarily > need to fully contain the others, though perhaps the branches do share > some other contained branches), and I want it to handle replaying > merges as well. While `git rebase --update-refs` is absolutely > limited to "stacks", and thus your argument might make sense in the > context of `git rebase`, since you are bringing `git replay` into the > mix, it needs to apply beyond a stack of commits. It's not clear to > me how to genericize your suggestions to handle cases other than a > simple stack of commits, though. I don't see a contradiction here. I don't tend to do this in practice, but I can totally imagine a tree of stacked branches that share some common base branches in the beginning and then diverge into different branches from there. It's true that "rebase --update-refs", when told to rebase one of the leaf branches, will destroy this tree because it pulls the base branches away from under the other leaf branches, but this is unrelated to my proposal, it has this problem today already. And it's awesome that git replay has a way to avoid this by rebasing the whole tree at once, keeping everything intact. Still, I don't see what's bad about excluding branches that point at the same commits as the leaf branches it is told to rebase when using "replay --contains". (I suppose what I'm suggesting is to treat "--contains" to mean "is included in the half-open interval from base to tip" of the revision range you are rebasing, rather than the closed interval.) Maybe I should make this more explicit again: I'm not trying to solve the problem of making a copy of a stack of branches, and rebasing that copy somewhere else. I think this can't be solved except by making branch stacks a new concept in git, which I'm not sure we want to do. > 3) This is mostly covered in (1) and (2), but to be explicit: `git > replay` is completely against the HEAD-is-special assumptions that are > pervasive within `git rebase`, and your problem is entirely phrased as > HEAD-is-special due to your call out of "the current branch". Is your > argument limited to such special cases? (If so, it might still be > valid for `git rebase`, of course.) No, I don't think I need HEAD to be special. "The thing that I'm rebasing" is special, and it is always HEAD for git rebase, but it can be something else for replay. > 4a) `git replay` does what Junio suggests naturally, since it doesn't > update the refs but instead gives commands which can be fed to `git > update-ref --stdin`. Thus, users can inspect the output of `git > replay` and only perform the updates they want (by feeding a subset of > the lines to update-ref --stdin). At this point I probably need to explain that I'm rarely using the command line. I'm a user and co-maintainer of lazygit, and I want to make lazygit work in such a way that "it does the right thing" in as many cases as possible. > 4b) For `git replay`, --contained is just syntactic sugar -- it isn't > necessary. git replay will allow you to list multiple branches that > you want replayed, so you can specify which branches are relevant to > you. That's great, even if it means that I have to redo some of the work that --contains would already do for me, just because I want a slightly different behavior. > 4c) For `git rebase --update-refs`, you can add `--interactive` and > then delete the `update-ref` line(s) corresponding to the refs you > don't want updated. Yes, that's what I always do today to work around the problem. It's just easy to forget, and I find it annoying that I have to take this extra step every time. One last remark: whenever I describe my use case involving copies of branches, people tell me not to do that, use detached heads instead, or other ways to achieve what I want. But then I don't understand why my proposal would make a difference for them. If you don't use copied branches, then why do you care whether "rebase --update-refs" or "replay --contained" moves those copies or not? I still haven't heard a good argument for why the current behavior is desirable, except for the one example of a degenerate stack that Phillip Wood described in [1]. -Stefan [1] <https://public-inbox.org/git/ 98548a5b-7d30-543b-b943-fd48d8926a33@gmail.com/> ^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: Should --update-refs exclude refs pointing to the current HEAD? 2024-03-06 21:00 ` Stefan Haller @ 2024-03-07 5:36 ` Elijah Newren 2024-03-07 20:16 ` Stefan Haller 0 siblings, 1 reply; 18+ messages in thread From: Elijah Newren @ 2024-03-07 5:36 UTC (permalink / raw) To: Stefan Haller Cc: Junio C Hamano, git, Derrick Stolee, Phillip Wood, Christian Couder On Wed, Mar 6, 2024 at 1:00 PM Stefan Haller <lists@haller-berlin.de> wrote: > > On 06.03.24 03:57, Elijah Newren wrote: > > > 1) What if there is a branch that is "just a copy" of one of the > > branches earlier in the "stack"? Since it's "just a copy", shouldn't > > it be excluded for similar reasons to what you are arguing? And, if > > so, which branch is the copy? > > This is a good point, but in my experience it's a lot more rare. Maybe > I'm looking at all this just from my own experience, and there might be > other usecases that are very different from mine, but as far as I am > concerned, copies of branches are not long-lived. > There is no point in having two branches point at the same commit. But isn't that what you're doing? > When I create a copy of a > branch, I do that only to rebase the copy somewhere else _immediately_, > leaving the original branch where it was. If it is inherently tied like this, why not create the new branch immediately after the rebase (with active_branch@{1} as the start point), instead of creating it immediately before? > Which means that I encounter > copied branches only at the top of the stack, not in the middle. Which > means that I'm fine with keeping the current behavior of "rebase > --update-ref" to update both copies of that middle-of-the-stack branch, > because it never happens in practice for me. You've really lost me here; are you saying you're fine changing the design to add inherent edgecase bugs to the code because those edge cases "never happen in practice for me"? I've spent a lot of time dealing with built up cruft in git from partial solutions and fixes that overlooked subsets of relevant testcases, so I'm not a fan of that statement and in particular the last two words of it. Perhaps I'm reading it wrong, and if so I apologize, but it triggered unhappy memories of mine from merge-recursive.c and dir.c and elsewhere. > > 2) Further, a "stack", to me at least, suggests a linear history > > without branching (i.e. each commit has at most one parent _and_ at > > most one child among the commits in the stack). I designed `git > > replay` to handle diverging histories (i.e. rebasing multiple branches > > that _might_ share a subset of common history but none necessarily > > need to fully contain the others, though perhaps the branches do share > > some other contained branches), and I want it to handle replaying > > merges as well. While `git rebase --update-refs` is absolutely > > limited to "stacks", and thus your argument might make sense in the > > context of `git rebase`, since you are bringing `git replay` into the > > mix, it needs to apply beyond a stack of commits. It's not clear to > > me how to genericize your suggestions to handle cases other than a > > simple stack of commits, though. > > I don't see a contradiction here. I don't tend to do this in practice, > but I can totally imagine a tree of stacked branches that share some > common base branches in the beginning and then diverge into different > branches from there. It's true that "rebase --update-refs", when told to > rebase one of the leaf branches, will destroy this tree because it pulls > the base branches away from under the other leaf branches, but this is > unrelated to my proposal, it has this problem today already. And it's > awesome that git replay has a way to avoid this by rebasing the whole > tree at once, keeping everything intact. Still, I don't see what's bad > about excluding branches that point at the same commits as the leaf > branches it is told to rebase when using "replay --contains". By "leaf branches", do you mean (a) those commits explicitly mentioned on the command line for being replayed, (b) only the subset of the branches mentioned on the command line which aren't an ancestor of another commit being replayed, or (c) something else? > (I suppose > what I'm suggesting is to treat "--contains" to mean "is included in the > half-open interval from base to tip" of the revision range you are > rebasing, rather than the closed interval.) "half-open interval"? That to me again implies a simple stack, which since we're trying to address the more general case, makes me more confused rather than less. Let me re-ask my question another way. If someone runs git replay --onto A --contained ^B ^C D E F when branches G, H, & I are in the revision range of "^B ^C D E F", with G in particular pointing where D does and H pointing where E does, and E contains D in its history, and F contains commits that are in neither D nor E, how do I figure out which of D-I should be updated? > Maybe I should make this more explicit again: I'm not trying to solve > the problem of making a copy of a stack of branches, and rebasing that > copy somewhere else. I think this can't be solved except by making > branch stacks a new concept in git, which I'm not sure we want to do. Oh, I hadn't even thought of that. Yeah, that'd be even more complex. > > 3) This is mostly covered in (1) and (2), but to be explicit: `git > > replay` is completely against the HEAD-is-special assumptions that are > > pervasive within `git rebase`, and your problem is entirely phrased as > > HEAD-is-special due to your call out of "the current branch". Is your > > argument limited to such special cases? (If so, it might still be > > valid for `git rebase`, of course.) > > No, I don't think I need HEAD to be special. "The thing that I'm > rebasing" is special, and it is always HEAD for git rebase, but it can > be something else for replay. But what exactly should that something else be? I still don't understand what that is from your explanation so far. > > 4a) `git replay` does what Junio suggests naturally, since it doesn't > > update the refs but instead gives commands which can be fed to `git > > update-ref --stdin`. Thus, users can inspect the output of `git > > replay` and only perform the updates they want (by feeding a subset of > > the lines to update-ref --stdin). > > At this point I probably need to explain that I'm rarely using the > command line. I'm a user and co-maintainer of lazygit, and I want to > make lazygit work in such a way that "it does the right thing" in as > many cases as possible. ...and I'm pointing out that `git replay` has the necessary tools to enable you to do so. Unlike `git rebase --update-refs` it doesn't automatically update the branches, but just creates the new commits and tells you what it could update each branch to, in a format that you can pass along to another tool to actually do the updates of the branches. As such, you can write your tool to take that output, pick out the bits you like, and only pass those bits along so that only some of the branches are updated. > > 4b) For `git replay`, --contained is just syntactic sugar -- it isn't > > necessary. git replay will allow you to list multiple branches that > > you want replayed, so you can specify which branches are relevant to > > you. > > That's great, even if it means that I have to redo some of the work that > --contains would already do for me, just because I want a slightly > different behavior. Right, but I thought you were maintaining lazygit, meaning that programming it to select the branches you want is a one time cost? Something like `git log --format=%D --decorate-refs=refs/heads/ ${base}..HEAD^1 | grep -v ^$`, plus adding in the current branch, right? Or is the concern with this suggestion the performance hit you'd take (which admittedly might be a problem with this solution, since you walk the commits an extra time)? > > 4c) For `git rebase --update-refs`, you can add `--interactive` and > > then delete the `update-ref` line(s) corresponding to the refs you > > don't want updated. > > Yes, that's what I always do today to work around the problem. It's just > easy to forget, and I find it annoying that I have to take this extra > step every time. And if you forget, then after the rebase it's trivial to move the updated branch back to where you want it, right? git branch -f ${copy_branch_name} ${current_branch_name}@{1} In fact, that's probably easier than making the rebase interactive, and should be easier to remember since you only ever create these branches precisely when you want to do a rebase. > One last remark: whenever I describe my use case involving copies of > branches, people tell me not to do that, use detached heads instead, or > other ways to achieve what I want. But then I don't understand why my > proposal would make a difference for them. If you don't use copied > branches, then why do you care whether "rebase --update-refs" or "replay > --contained" moves those copies or not? I still haven't heard a good > argument for why the current behavior is desirable, except for the one > example of a degenerate stack that Phillip Wood described in [1]. The current behavior is easy to describe and explain to users, and generalizes nicely to cases of replaying multiple diverging and converging branches. To me, the behavior you're proposing doesn't seem to share either of those qualities, at least not as you've explained it so far. But, perhaps that's because I still don't really understand your usecase. I'm trying to, and it's possible I could be convinced there is a proposal here that is easy to explain to users and generalizes nicely. My way of attempting to get that out is to make counter-proposals and ask questions as a way of teasing out what your usecase is and what a refined proposal might be. Currently, it seems there are two trivial alternative solutions that would solve this problem more cleanly (namely, either creating the branch after the fact instead of beforehand, or simply updating the branch after the fact)...but maybe I'm still just missing something? ^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: Should --update-refs exclude refs pointing to the current HEAD? 2024-03-07 5:36 ` Elijah Newren @ 2024-03-07 20:16 ` Stefan Haller 2024-03-09 3:28 ` Elijah Newren 0 siblings, 1 reply; 18+ messages in thread From: Stefan Haller @ 2024-03-07 20:16 UTC (permalink / raw) To: Elijah Newren Cc: Junio C Hamano, git, Derrick Stolee, Phillip Wood, Christian Couder Elijah, thanks for your patience with this. I appreciate the time and energy you put into understanding what I want to achieve. The questions you are asking help me understand my proposal better myself. It seems that I didn't do a very good job at getting my point across so far, so I'll try again in a more structured way. Let's begin by describing two very different user scenarios: 1) Stacked branches. Git supports these reasonably well for simple cases through the "rebase --update-refs" command (and the "rebase.updateRefs" config), but since they are not a first-class concept, git needs to rely on heuristics to determine which branches are part of a stack. For simple cases this works very well, but more esoteric cases can have problems (e.g. a non-linear topology of multiple stacks that may share common base branches and then diverge, in which case rebasing one of them destroys the others; or degenerate stacks involving "empty" branches either in the middle or at the top, in which case there's no way to tell what the order of the branches is supposed to be). 2) Copying a branch, and rebasing it away from the original one (for non-stacked branches, see below). The use case is that you have a branch called topic-1 (branched off main), which is pushed and in review already, with CI running on it, and you want to test whether it works on devel, so you make a new branch called topic-1-on-devel off of topic-1, and rebase it onto devel. You want to make a draft PR of that new branch to have CI run on it, too, and of course you want to keep the original branch untouched. For me and most of my co-worker that I have observed in pairing sessions, the natural way to achieve this is as described above: checkout a new branch, and rebase it where you want it to go. Next I'll describe my goals, and my non-goals. I know I can easily achieve 2) by simply not using --update-refs, but I like to have "rebase.updateRefs" set to true by default because it is so useful, and having to remember to use --no-update-refs whenever I do 2) is annoying. So my goal is to make 2) work well (in the simple, non-stacked case) even when "rebase.updateRefs" is true, while not making 1) work any worse in the "normal", non-degenerate case. I'm _not_ trying to fix the problems that --update-refs has today (I briefly mentioned some of them above, but there are more), and I'm not trying to make 2) work well with stacked branches. It would certainly be nice if that would work too, but I don't think it can without introducing branch stacks as a first-class feature in git, so I'll have to live with not supporting that case well. It would still be a big improvement for me without that. I'll now go on to respond to some of your questions inline below, but I'll skip some of them in order to not make this too long. Do let me know if there are still open questions that I didn't address. On 07.03.24 06:36, Elijah Newren wrote: > On Wed, Mar 6, 2024 at 1:00 PM Stefan Haller <lists@haller-berlin.de> wrote: >> >> On 06.03.24 03:57, Elijah Newren wrote: >> >>> 1) What if there is a branch that is "just a copy" of one of the >>> branches earlier in the "stack"? Since it's "just a copy", shouldn't >>> it be excluded for similar reasons to what you are arguing? And, if >>> so, which branch is the copy? >> >> This is a good point, but in my experience it's a lot more rare. Maybe >> I'm looking at all this just from my own experience, and there might be >> other usecases that are very different from mine, but as far as I am >> concerned, copies of branches are not long-lived. > >> There is no point in having two branches point at the same commit. > > But isn't that what you're doing? Only briefly, not permanently. I only described this to illustrate why it never happens to encounter branch copies in the middle of a stack. >> When I create a copy of a >> branch, I do that only to rebase the copy somewhere else _immediately_, >> leaving the original branch where it was. > > If it is inherently tied like this, why not create the new branch > immediately after the rebase (with active_branch@{1} as the start > point), instead of creating it immediately before? That would be the wrong way round. I want to leave the original branch untouched, make a new branch and rebase that away from the original. >> Which means that I encounter >> copied branches only at the top of the stack, not in the middle. Which >> means that I'm fine with keeping the current behavior of "rebase >> --update-ref" to update both copies of that middle-of-the-stack branch, >> because it never happens in practice for me. > > You've really lost me here; are you saying you're fine changing the > design to add inherent edgecase bugs to the code because those edge > cases "never happen in practice for me"? Wait, now you are really turning things around. You make it sound like my proposal is responsible for what you call a "bug" here. It's not, git already behaves like this (and you may or may not consider that a problem), and my proposal doesn't change anything about it. It doesn't "fix" it, that's right (and this is what I referred to when I said "I'm fine with it"), but it doesn't make it any worse either. >> I don't see a contradiction here. I don't tend to do this in practice, >> but I can totally imagine a tree of stacked branches that share some >> common base branches in the beginning and then diverge into different >> branches from there. It's true that "rebase --update-refs", when told to >> rebase one of the leaf branches, will destroy this tree because it pulls >> the base branches away from under the other leaf branches, but this is >> unrelated to my proposal, it has this problem today already. And it's >> awesome that git replay has a way to avoid this by rebasing the whole >> tree at once, keeping everything intact. Still, I don't see what's bad >> about excluding branches that point at the same commits as the leaf >> branches it is told to rebase when using "replay --contains". > > By "leaf branches", do you mean (a) those commits explicitly mentioned > on the command line for being replayed, (b) only the subset of the > branches mentioned on the command line which aren't an ancestor of > another commit being replayed, or (c) something else? If I understand you right (and if I understand the user interface of git-replay right), then what I mean is the combination of all single commits that are mentioned on the command line, plus the right side of all A..B ranges that are mentioned on the command line. In my mental model those are "the things that are being rebased" (please let me know if that mental model is wrong), and I am proposing to exclude all branches from updating that point to any of those and are not mentioned on the command line, because they can be considered copies. > Let me re-ask my question another way. If someone runs > git replay --onto A --contained ^B ^C D E F > when branches G, H, & I are in the revision range of "^B ^C D E F", > with G in particular pointing where D does and H pointing where E > does, and E contains D in its history, and F contains commits that are > in neither D nor E, how do I figure out which of D-I should be > updated? D, E, F, and I are updated, G and H are not; this seems very obvious to me. D, E, and F because they are all mentioned explicitly; G and H are not updated because they point to one of the "things-to-be-rebased", so they are copies; I is updated because it is contained in E but does not point at one of the "things-to-be-rebased", so it's part of a "stack" (or whatever you want to call this topology). It's a heuristic; we need a way to distinguish things that are part of a stack from things that are copies. My heuristic for this relies on the assumption that the stack is not degenerate in the sense that it doesn't contain any "empty" branches in the middle or at the top of the stack, otherwise it wouldn't be possible to distinguish the two. >> No, I don't think I need HEAD to be special. "The thing that I'm >> rebasing" is special, and it is always HEAD for git rebase, but it can >> be something else for replay. > > But what exactly should that something else be? I still don't > understand what that is from your explanation so far. All the refs that are mentioned on the command line, either as a single commit or as the second half of an A..B expression. It may well be that I have some misconception of how exactly git replay works, this sounds like the most likely explanation for why we don't understand each other. > Something like `git log --format=%D --decorate-refs=refs/heads/ > ${base}..HEAD^1 | grep -v ^$`, plus adding in the current branch, > right? > > Or is the concern with this suggestion the performance hit you'd take > (which admittedly might be a problem with this solution, since you > walk the commits an extra time)? Yes, I can do that, and no, I'm not concerned about performance. We already have all that data cached in memory anyway, so that's not a problem. But this would only work for git replay, there's no way to do the same thing for --update-ref. My goal is to offer the same features both for the checked out branch (using rebase) and other branches (using replay), and have them behave the same. So my proposal is more about changing --update-ref, since I can solve it manually for replay, as you described. However, _if_ we decide to change "rebase --update-ref", then I think it would make sense to change "replay --contains" in the same way, so that they behave more consistently. >> One last remark: whenever I describe my use case involving copies of >> branches, people tell me not to do that, use detached heads instead, or >> other ways to achieve what I want. But then I don't understand why my >> proposal would make a difference for them. If you don't use copied >> branches, then why do you care whether "rebase --update-refs" or "replay >> --contained" moves those copies or not? I still haven't heard a good >> argument for why the current behavior is desirable, except for the one >> example of a degenerate stack that Phillip Wood described in [1]. > > The current behavior is easy to describe and explain to users, and > generalizes nicely to cases of replaying multiple diverging and > converging branches. It sounds like you value the property of being easy to describe higher than doing the expected thing in as many cases as possible. -Stefan ^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: Should --update-refs exclude refs pointing to the current HEAD? 2024-03-07 20:16 ` Stefan Haller @ 2024-03-09 3:28 ` Elijah Newren 2024-03-12 9:28 ` Stefan Haller 0 siblings, 1 reply; 18+ messages in thread From: Elijah Newren @ 2024-03-09 3:28 UTC (permalink / raw) To: Stefan Haller Cc: Junio C Hamano, git, Derrick Stolee, Phillip Wood, Christian Couder On Thu, Mar 7, 2024 at 12:16 PM Stefan Haller <lists@haller-berlin.de> wrote: > > Elijah, thanks for your patience with this. I appreciate the time and > energy you put into understanding what I want to achieve. The questions > you are asking help me understand my proposal better myself. > > It seems that I didn't do a very good job at getting my point across so > far, so I'll try again in a more structured way. I think I fell short on communication on a few points as well; sorry about that. > Let's begin by describing two very different user scenarios: > > 1) Stacked branches. Git supports these reasonably well for simple cases > through the "rebase --update-refs" command (and the "rebase.updateRefs" > config), but since they are not a first-class concept, git needs to rely > on heuristics to determine which branches are part of a stack. For > simple cases this works very well, but more esoteric cases can have > problems (e.g. a non-linear topology of multiple stacks that may share > common base branches and then diverge, in which case rebasing one of > them destroys the others; or degenerate stacks involving "empty" > branches either in the middle or at the top, in which case there's no > way to tell what the order of the branches is supposed to be). > > 2) Copying a branch, and rebasing it away from the original one (for > non-stacked branches, see below). The use case is that you have a branch > called topic-1 (branched off main), which is pushed and in review > already, with CI running on it, and you want to test whether it works on > devel, so you make a new branch called topic-1-on-devel off of topic-1, > and rebase it onto devel. You want to make a draft PR of that new branch > to have CI run on it, too, and of course you want to keep the original > branch untouched. For me and most of my co-worker that I have observed > in pairing sessions, the natural way to achieve this is as described > above: checkout a new branch, and rebase it where you want it to go. > > Next I'll describe my goals, and my non-goals. I know I can easily > achieve 2) by simply not using --update-refs, but I like to have > "rebase.updateRefs" set to true by default because it is so useful, and > having to remember to use --no-update-refs whenever I do 2) is annoying. > > So my goal is to make 2) work well (in the simple, non-stacked case) > even when "rebase.updateRefs" is true, while not making 1) work any > worse in the "normal", non-degenerate case. Thanks, this is helpful background. > I'm _not_ trying to fix the problems that --update-refs has today (I > briefly mentioned some of them above, but there are more), and I'm not > trying to make 2) work well with stacked branches. It would certainly be > nice if that would work too, but I don't think it can without > introducing branch stacks as a first-class feature in git, so I'll have > to live with not supporting that case well. It would still be a big > improvement for me without that. > >> When I create a copy of a > >> branch, I do that only to rebase the copy somewhere else _immediately_, > >> leaving the original branch where it was. > > > > If it is inherently tied like this, why not create the new branch > > immediately after the rebase (with active_branch@{1} as the start > > point), instead of creating it immediately before? > > That would be the wrong way round. I want to leave the original branch > untouched, make a new branch and rebase that away from the original. Ah, sorry for misunderstanding. Still, though, what's wrong with running git branch -f original_branch original_branch@{1} after the operation? That'll make the original branch point to where it was before the rebase operation. Since there's no separation in time between when you create the new copy branch and do this rebase operation, it's not a matter of forgetting that there was this original branch that you wanted to reflect its own pre-rebase state, right? Also, since you're not using the git cli directly but going through lazygit, isn't this something you can just include in lazygit as part of whatever overall operation is creating the new copy branch and rebasing it? > >> Which means that I encounter > >> copied branches only at the top of the stack, not in the middle. Which > >> means that I'm fine with keeping the current behavior of "rebase > >> --update-ref" to update both copies of that middle-of-the-stack branch, > >> because it never happens in practice for me. > > > > You've really lost me here; are you saying you're fine changing the > > design to add inherent edgecase bugs to the code because those edge > > cases "never happen in practice for me"? > > Wait, now you are really turning things around. You make it sound like > my proposal is responsible for what you call a "bug" here. It's not, git > already behaves like this (and you may or may not consider that a > problem), and my proposal doesn't change anything about it. It doesn't > "fix" it, that's right (and this is what I referred to when I said "I'm > fine with it"), but it doesn't make it any worse either. Ah, I see where I was unclear as well, and my lack of clarity stemmed from not understanding your proposal. To try to close the loop, allow me to re-translate your "This is a good point, but..it never happens in practice for me." paragraph, the way I _erroneously_ read it at the time: """ For my new proposal, the case you bring up is a good point. But it doesn't happen for me, so I propose to leave it as undefined behavior. [As undefined behavior, anyone that triggers it is likely to get behavior they deem buggy and not like it, but that won't affect me.] """ Now, obviously, that doesn't sound quite right. I knew it at the time, but reading and re-reading your paragraph, it kept coming out that way for me. Thus I tried to ask if that's what you really meant, and apologizing in advance if I was mis-reading. Anyway, with the extra explanation in your latest email, I now see that you weren't leaving it undefined, but your proposal wasn't clear to me either in that paragraph or in combination with the rest of your previous email. Sorry for my misunderstanding. > > By "leaf branches", do you mean (a) those commits explicitly mentioned > > on the command line for being replayed, (b) only the subset of the > > branches mentioned on the command line which aren't an ancestor of > > another commit being replayed, or (c) something else? > > If I understand you right (and if I understand the user interface of > git-replay right), then what I mean is the combination of all single > commits that are mentioned on the command line, plus the right side of > all A..B ranges that are mentioned on the command line. In my mental > model those are "the things that are being rebased" (please let me know > if that mental model is wrong), and I am proposing to exclude all > branches from updating that point to any of those and are not mentioned > on the command line, because they can be considered copies. > > > Let me re-ask my question another way. If someone runs > > git replay --onto A --contained ^B ^C D E F > > when branches G, H, & I are in the revision range of "^B ^C D E F", > > with G in particular pointing where D does and H pointing where E > > does, and E contains D in its history, and F contains commits that are > > in neither D nor E, how do I figure out which of D-I should be > > updated? > > D, E, F, and I are updated, G and H are not; this seems very obvious to > me. D, E, and F because they are all mentioned explicitly; G and H are > not updated because they point to one of the "things-to-be-rebased", so > they are copies; I is updated because it is contained in E but does not > point at one of the "things-to-be-rebased", so it's part of a "stack" > (or whatever you want to call this topology). > > It's a heuristic; we need a way to distinguish things that are part of a > stack from things that are copies. My heuristic for this relies on the > assumption that the stack is not degenerate in the sense that it doesn't > contain any "empty" branches in the middle or at the top of the stack, > otherwise it wouldn't be possible to distinguish the two. Ah, okay, now I understand the concrete proposal; thanks. > However, _if_ we decide to change > "rebase --update-ref", then I think it would make sense to change > "replay --contains" in the same way, so that they behave more consistently. Yep, makes sense. > >> One last remark: whenever I describe my use case involving copies of > >> branches, people tell me not to do that, use detached heads instead, or > >> other ways to achieve what I want. But then I don't understand why my > >> proposal would make a difference for them. If you don't use copied > >> branches, then why do you care whether "rebase --update-refs" or "replay > >> --contained" moves those copies or not? I still haven't heard a good > >> argument for why the current behavior is desirable, except for the one > >> example of a degenerate stack that Phillip Wood described in [1]. > > > > The current behavior is easy to describe and explain to users, and > > generalizes nicely to cases of replaying multiple diverging and > > converging branches. > > It sounds like you value the property of being easy to describe higher > than doing the expected thing in as many cases as possible. There's certainly some truth to that. For example, "three-way merges" are unsophisticated, and folks have suggested various ways of "making them smarter" over the years (though there was more of this years ago). To quote someone else on that: """ Me _personally_, I want to have something that is very repeatable and non-clever. Something I understand _or_ tells me that it can't do it. """ and """ I just don't like your notion that you should support the 5% problem with ugly hacks, and then you dismiss the 95% problem with "nothing else does it either". In other words, we're already merging manually for the 95%. Why do you think the 5% is so important? """ Three-way merging and rebase --update-refs are obviously quite different, so this might not be a good analogy. I'm just saying you are right that I do sometimes tend to have the same biases as the author of the above quotes. But, to be more direct about this particular issue, I've actually had the usecase Phillip described, and never run into yours. Yes, it's rare that I've run into Phillip's described case, but rare is still more often than never. That said, I totally accept that I might be an oddball. So, I think it's important to look at the alternatives: * If we make no modifications to --update-refs: * the --update-refs behavior is very simple to describe * folks with your usecase immediately understand why the copied branch was updated, even though you didn't want it to be * you have a trivial workaround you can run, as mentioned above (git branch -f original_branch original_branch@{1}) * If we modify --update-refs as you suggest: * the --update-refs behavior is more complicated to describe to users * folks with Phillip's usecase probably assume a bug and report it since it isn't going to make any sense to them (and, my guess is, many would report a bug even if the behavior is documented) The downsides for the latter option seem worse to me, so unless the first usecase is predominant, I'd rather not make a change. Granted, you did claim the first usecase would be far more common, and you may be right, but it's not so clear cut to me; I don't know how to validate that. I'd at least first like to hear why the workaround for your usecase that looks trivial to me is too onerous for you. ^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: Should --update-refs exclude refs pointing to the current HEAD? 2024-03-09 3:28 ` Elijah Newren @ 2024-03-12 9:28 ` Stefan Haller 0 siblings, 0 replies; 18+ messages in thread From: Stefan Haller @ 2024-03-12 9:28 UTC (permalink / raw) To: Elijah Newren Cc: Junio C Hamano, git, Derrick Stolee, Phillip Wood, Christian Couder On 09.03.24 04:28, Elijah Newren wrote: >> That would be the wrong way round. I want to leave the original branch >> untouched, make a new branch and rebase that away from the original. > > Ah, sorry for misunderstanding. Still, though, what's wrong with running > git branch -f original_branch original_branch@{1} > after the operation? It's unintuitive. Users don't think this way, at least as far as I have observed them (and I don't think this way myself). Also, for many users the branch{n} syntax to access previous reflog entries is an advanced concept that they are not familiar with. > Also, since you're not using the git cli directly but going through > lazygit, isn't this something you can just include in lazygit as part > of whatever overall operation is creating the new copy branch and > rebasing it? Yes, there are various workarounds that I could build into lazygit. Right now I'm planning to have lazygit check whether any branch heads point at any of the commits in the range of commits that is being rebased except for the head, and if not, add --no-update-refs. This will solve it well enough for most cases, and it doesn't bother me too much that I have to add this additional complexity to our code. I was just hoping that cli users typing git checkout -b original-branch copy git rebase --onto devel main would get the same improvement. It bothers me a bit that we have to build clients around the git cli that make it perform better than the git cli does. >> Wait, now you are really turning things around. You make it sound like >> my proposal is responsible for what you call a "bug" here. It's not, git >> already behaves like this (and you may or may not consider that a >> problem), and my proposal doesn't change anything about it. It doesn't >> "fix" it, that's right (and this is what I referred to when I said "I'm >> fine with it"), but it doesn't make it any worse either. > > Ah, I see where I was unclear as well, and my lack of clarity stemmed > from not understanding your proposal. To try to close the loop, allow > me to re-translate your "This is a good point, but..it never happens > in practice for me." paragraph, the way I _erroneously_ read it at the > time: > > """ > For my new proposal, the case you bring up is a good point. But it > doesn't happen for me, so I propose to leave it as undefined behavior. > [As undefined behavior, anyone that triggers it is likely to get > behavior they deem buggy and not like it, but that won't affect me.] > """ > > Now, obviously, that doesn't sound quite right. I knew it at the > time, but reading and re-reading your paragraph, it kept coming out > that way for me. Thus I tried to ask if that's what you really meant, > and apologizing in advance if I was mis-reading. > > Anyway, with the extra explanation in your latest email, I now see > that you weren't leaving it undefined, but your proposal wasn't clear > to me either in that paragraph or in combination with the rest of your > previous email. Sorry for my misunderstanding. I think it's worth clarifying this again, and see whether "undefined behavior" is the right term to use here. Again, this discussion has improved my own understanding of the matter, so let me try to spell it out again: The fundamental underlying problem is that when we encounter two branches pointing at the same commit in a rebase, git has no way to distinguish whether this is because there's an "empty" branch in a stack (either at the top or in the middle), or whether one branch is a copy of the other. In the first case, both branches should be updated by "rebase --update-ref", in the second case only one of them should, since the other is not part of the stack. Since there's no way for git to tell for sure, it can only guess which of the two was meant by the user, with a heuristic that hopefully guesses right in the majority of cases. I think it would be wrong to call it a "bug" (or an "edge case bug" like you did earlier) if it guesses wrong in a particular scenario. Right now, it _always_ guesses in favor of the stack, so it never considers a branch to be a copy. For my own use of git, and of my co-workers as I have observed them in pairing sessions, this is almost always wrong. I have never encountered an empty branch in a stack, as far as I remember, but I am encountering copies of branches fairly often, so I'd like to improve the heuristic to make git guess right in these cases. Note that this is definitely not a 5% thing as in your three-way merging example; I can't provide any hard numbers of course, but it feels much more like the classical 80/20 rule to me (where my proposal would improve it for 80% of the cases, to be clear). So, I concluded that copies are much more frequent than empty branches in a stack, so it would make sense for me to turn the heuristic around and always guess in favor of a copied branch. The problem is that we can only do this for the tip of the branch, because only in that case can we tell which branch is the copy (the one being rebased) and which one is the original that should be left alone. For branches in the middle of the stack we just can't tell, so we have to guess in favor of an empty branch in a stack and update both refs, since otherwise we'd have to randomly pick one of them to update and leave the other one alone, risking to break the stack this way. So that's really where my proposal comes from: guess in favor of a copied branch only at the tip but not in the middle; not because we only want it at the tip, but just because we only can at the tip. But fortunately, it is in fact true that I almost never create a copy of a branch in the middle of a stack, but then I almost never have empty branches in the middle of a stack either, so it doesn't really matter to me which way the heuristic guesses in this case. I hope this clarifies it a bit more. Having written all this, I do realize that it's probably too complex to explain to users (not the behavior itself, which is fairly simple, but the rationale behind it). -Stefan ^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: Should --update-refs exclude refs pointing to the current HEAD? 2024-03-05 7:40 ` Stefan Haller 2024-03-05 16:22 ` Junio C Hamano @ 2024-03-07 7:59 ` Kristoffer Haugsbakk 2024-03-07 8:22 ` Elijah Newren 1 sibling, 1 reply; 18+ messages in thread From: Kristoffer Haugsbakk @ 2024-03-07 7:59 UTC (permalink / raw) To: Stefan Haller Cc: Derrick Stolee, Elijah Newren, Phillip Wood, Christian Couder, git On Tue, Mar 5, 2024, at 08:40, Stefan Haller wrote: > Coming back to this after almost a year, I can say that I'm still > running into this problem relatively frequently, and it is annoying > every single time. Excluding refs pointing at the current head from > being updated, as proposed above, would be a big usability improvement > for me. Sounds like a ref-stash command is in order… # I want a new branch git checkout -b new # But I don’t want it to be affected by the next rebase git ref-stash push git rebase [...] # Now I’m done: put the ref back where it was git ref-stash pop -- Kristoffer Haugsbakk ^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: Should --update-refs exclude refs pointing to the current HEAD? 2024-03-07 7:59 ` Kristoffer Haugsbakk @ 2024-03-07 8:22 ` Elijah Newren 0 siblings, 0 replies; 18+ messages in thread From: Elijah Newren @ 2024-03-07 8:22 UTC (permalink / raw) To: Kristoffer Haugsbakk Cc: Stefan Haller, Derrick Stolee, Phillip Wood, Christian Couder, git Hi, On Wed, Mar 6, 2024 at 11:59 PM Kristoffer Haugsbakk <code@khaugsbakk.name> wrote: > > On Tue, Mar 5, 2024, at 08:40, Stefan Haller wrote: > > Coming back to this after almost a year, I can say that I'm still > > running into this problem relatively frequently, and it is annoying > > every single time. Excluding refs pointing at the current head from > > being updated, as proposed above, would be a big usability improvement > > for me. > > Sounds like a ref-stash command is in order… A what? > # I want a new branch > git checkout -b new > # But I don’t want it to be affected by the next rebase This doesn't make any sense; rebase always operates on the current branch, and Stefan wasn't asking for anything otherwise. He was just concerned that with --update-refs, one of the other branches it also operated on was one he didn't want it to operate on. Perhaps you meant git branch new for your first command? > git ref-stash push > git rebase [...] > # Now I’m done: put the ref back where it was > git ref-stash pop Leaving aside questions about how ref-stash is supposed to interact with each and every other command out there, and how it's supposed to know which branches it's operating on when you do pushes and pops... Why do we need to invent a new command, when we already have the reflog? You could drop both ref-stash commands, and instead just have a git branch -f new new@{1} at the end (assuming of course "git branch new" was used instead of your "git checkout -b new", as I suggested earlier) to put "new" back to where it was before the rebase. That's fewer commands. Or, even simpler, drop the initial branch creation and both ref-stash commands by just not creating the branch until after the rebase. That'd make the entire set of commands just be: git rebase --update-refs [...] git branch new current_branch@{1} Plus, either solution works today and needs no new changes. ^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: Should --update-refs exclude refs pointing to the current HEAD? 2023-04-17 8:21 Should --update-refs exclude refs pointing to the current HEAD? Stefan Haller ` (3 preceding siblings ...) 2024-03-05 7:40 ` Stefan Haller @ 2024-03-24 10:42 ` Stefan Haller 4 siblings, 0 replies; 18+ messages in thread From: Stefan Haller @ 2024-03-24 10:42 UTC (permalink / raw) To: git; +Cc: Elijah Newren, Phillip Wood On 17.04.23 10:21, Stefan Haller wrote: > The --update-refs option of git rebase is so useful that I have it on by > default in my config. For stacked branches I find it hard to think of > scenarios where I wouldn't want it. > > However, there are cases for non-stacked branches (i.e. other branches > pointing at the current HEAD) where updating them is undesirable. In > fact, pretty much always, for me. Two examples, both very similar: > > 1. I have a topic branch which is based off of master; I want to make a > copy of that branch and rebase it onto devel, just to try if that would > work. I don't want the original branch to be moved along in this case. > > 2. I have a topic branch, and I want to make a copy of it to make some > heavy history rewriting experiments. Again, my interactive rebases would > always rebase both branches in the same way, not what I want. In this > case I could work around it by doing the experiments on the original > branch, creating a tag beforehand that I could reset back to if the > experiments fail. But maybe I do want to keep both branches around for a > while for some reason. > > Both of these cases could be fixed by --update-refs not touching any > refs that point to the current HEAD. I'm having a hard time coming up > with cases where you would ever want those to be updated, in fact. Sorry for continuing to beat this dead horse, but I just can't help adding this other use case that I just ran into yesterday and that supports my point as well. Suppose I have a branch b, and I realize that it has commits for two separate features, so I want to split it up into two independent branches. The most natural way to do this is to create a branch b2 off of b, do an interactive rebase on b and drop half of its commits, then checkout b2, do an interactive rebase on it too and drop the other half of the commits. With rebase.updateRefs set to true, the first interactive rebase changes both b and b2, which is not what I want. Of course you can argue that since I'm doing an interactive rebase in this case, it's easy to see the update-ref todo and delete it if I don't want it. That's true, but it's an extra thing that I have to pay attention to. Also, there's a twist as I'm writing this from the perspective of lazygit again. Lazygit has a feature to drop commits from a branch without doing an interactive rebase; it runs an interactive rebase behind the scenes, sets the marked commits to "drop", and continues the rebase. I don't get a chance to delete the update-ref todo in this case. Now you can argue that this is really lazygit's problem then, and I can add code to it to delete the update-ref todo in this scenario if that's what I want. That's true again, and I will, but it bugs me that we have to add clients around stock git to get the desired behavior. I would prefer to change git so that it behaves in the desired way in the first place. And finally you can argue again that there's Phillip Wood's counter-example, where you create b2 off of b with the intention to create a stack, but before you make the first commit on b2 you realize there's this unwanted commit in b that you want to drop first. In this case you do want to update both b and b2. My take on this is that 1) it's a much more rare case in my experience, and 2) it's much easier to recover from. Once you realize that b2 wasn't updated by the rebase, you just reset --hard it to b again. This is a straight-forward operation that even novice git users know how to do. Compare this to having to reset b2 back to where it was when you realize that it was updated by the rebase and you didn't want it to; you need to get it back from the reflog in that case, which is a more advanced operation for many users. -Stefan ^ permalink raw reply [flat|nested] 18+ messages in thread
end of thread, other threads:[~2024-03-24 10:42 UTC | newest] Thread overview: 18+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2023-04-17 8:21 Should --update-refs exclude refs pointing to the current HEAD? Stefan Haller 2023-04-17 8:30 ` Stefan Haller 2023-04-17 8:34 ` Kristoffer Haugsbakk 2023-04-17 9:22 ` Stefan Haller 2023-04-18 2:00 ` Felipe Contreras 2023-04-17 12:14 ` Phillip Wood 2023-04-20 15:27 ` Stefan Haller 2024-03-05 7:40 ` Stefan Haller 2024-03-05 16:22 ` Junio C Hamano 2024-03-06 2:57 ` Elijah Newren 2024-03-06 21:00 ` Stefan Haller 2024-03-07 5:36 ` Elijah Newren 2024-03-07 20:16 ` Stefan Haller 2024-03-09 3:28 ` Elijah Newren 2024-03-12 9:28 ` Stefan Haller 2024-03-07 7:59 ` Kristoffer Haugsbakk 2024-03-07 8:22 ` Elijah Newren 2024-03-24 10:42 ` Stefan Haller
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox; as well as URLs for NNTP newsgroup(s).