* Premerging topics (was: [RFD] annnotating a pair of commit objects?) @ 2013-04-10 20:35 Antoine Pelisse 2013-04-22 9:23 ` Antoine Pelisse 2013-04-23 6:34 ` Johan Herland 0 siblings, 2 replies; 16+ messages in thread From: Antoine Pelisse @ 2013-04-10 20:35 UTC (permalink / raw) To: Michael Haggerty, Junio C Hamano, Jeff King; +Cc: git The goal is to propose a structure for storing and pre-merging pairs of commits. Data-structure ============== We could use a note ref to store the pre-merge information. Each commit would be annotated with a blob containing the list of pre-merges (one sha1 per line with sha1 pointing to a merge commit). The commit on the other side of a merge would also be annotated. The choice of the refname could be done like we do with notes: - Have a default value - Have a default value configured in config - Use a specific value when merging/creating the pre-merges Here are my concerns: Pros ---- 1. Notes allow dynamic annotation a commit 2. If we manage to fix 4, we can easily download all pre-merges from a remote host by fetching the ref (or clean by deleting the ref). 3. Conflicts on pre-merge notes could probably be resolved by concatenation. Cons ---- 4. Checking connectivity means opening the blob and parsing it 5. Regular notes and pre-merge notes have to be handled separately because of 4. I'm hoping we can keep the pros and avoid the cons, but I'm kind of stuck here. Help would be really appreciated (or maybe this is a totally wrong direction, and I would also like to know ;) Merging (Using what we saved) ============================= The goal is to merge branches J and B using existing pre-merges. E0. Create an empty stack S E1. Create set of commits 'J..B' and 'B..J' (that is probably already done) E2. For each commit C in smallest(J..B, B..J), execute E3 E3. For each premerge P in notes-premerge(C), execute E4 E4. If one of both parents of P belongs to biggest(J..B, B..J), stack P in S E5. Merge J and B using all pre-merges from S Let's consider that |J..B| is smaller than |B..J|. E0 is executed only once E1 is O(|J..B| + |B..J|) E2 is O(|J..B|) E3 is O(|J..B| x the average number of pre-merge per commits P_avg) E4 is executed for each parent (let's say it's two/constant, after all the topic is "pair" of commits), so still O(|J..B| x P_avg) E5 I don't know (how it can be done, and what would be the resulting time complexity) So the time cost for steps E0 to E4 is O(|J..B| + |B..J| x P_avg) Tools (Save the pre-merges) =========================== Of course we need several tools to maintain the list of premerges, and to easily compute them. For example, it would be nice to be able to do something like: $ git pre-merge topicA topicB topicC to find, resolve and store all interactions between the topics. We could then easily derive to something that would allow to pre-merge a new topic with all topics already merged in master..pu (for example). Anyway, this task is left for latter. ^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: Premerging topics (was: [RFD] annnotating a pair of commit objects?) 2013-04-10 20:35 Premerging topics (was: [RFD] annnotating a pair of commit objects?) Antoine Pelisse @ 2013-04-22 9:23 ` Antoine Pelisse 2013-04-23 6:34 ` Johan Herland 1 sibling, 0 replies; 16+ messages in thread From: Antoine Pelisse @ 2013-04-22 9:23 UTC (permalink / raw) To: Michael Haggerty, Junio C Hamano, Jeff King; +Cc: git Any comment on that ? I think anyone using a "Topic Workflow" could use that feature and that it would be a nice addition to the project. Maybe I'm totally wrong in the proposal below (please tell me !), but there are some unanswered question that prevents me from starting (and I'd really like this to be discussed before actually starting). On Wed, Apr 10, 2013 at 10:35 PM, Antoine Pelisse <apelisse@gmail.com> wrote: > > The goal is to propose a structure for storing and pre-merging pairs of commits. > > Data-structure > ============== > > We could use a note ref to store the pre-merge information. Each commit > would be annotated with a blob containing the list of pre-merges (one > sha1 per line with sha1 pointing to a merge commit). The commit on the > other side of a merge would also be annotated. > > The choice of the refname could be done like we do with notes: > - Have a default value > - Have a default value configured in config > - Use a specific value when merging/creating the pre-merges > > Here are my concerns: > > Pros > ---- > 1. Notes allow dynamic annotation a commit > 2. If we manage to fix 4, we can easily download all pre-merges from a > remote host by fetching the ref (or clean by deleting the ref). > 3. Conflicts on pre-merge notes could probably be resolved by concatenation. > > Cons > ---- > 4. Checking connectivity means opening the blob and parsing it > 5. Regular notes and pre-merge notes have to be handled separately > because of 4. > > I'm hoping we can keep the pros and avoid the cons, but I'm kind of > stuck here. Help would be really appreciated (or maybe this is a totally > wrong direction, and I would also like to know ;) > > Merging (Using what we saved) > ============================= > The goal is to merge branches J and B using existing pre-merges. > > E0. Create an empty stack S > E1. Create set of commits 'J..B' and 'B..J' (that is probably already done) > E2. For each commit C in smallest(J..B, B..J), execute E3 > E3. For each premerge P in notes-premerge(C), execute E4 > E4. If one of both parents of P belongs to biggest(J..B, B..J), stack P in S > E5. Merge J and B using all pre-merges from S > > Let's consider that |J..B| is smaller than |B..J|. > E0 is executed only once > E1 is O(|J..B| + |B..J|) > E2 is O(|J..B|) > E3 is O(|J..B| x the average number of pre-merge per commits P_avg) > E4 is executed for each parent (let's say it's two/constant, after all > the topic is "pair" of commits), so still O(|J..B| x P_avg) > E5 I don't know (how it can be done, and what would be the resulting > time complexity) > > So the time cost for steps E0 to E4 is O(|J..B| + |B..J| x P_avg) > > Tools (Save the pre-merges) > =========================== > > Of course we need several tools to maintain the list of premerges, and > to easily compute them. For example, it would be nice to be able to do > something like: > > $ git pre-merge topicA topicB topicC > > to find, resolve and store all interactions between the topics. We could > then easily derive to something that would allow to pre-merge a new > topic with all topics already merged in master..pu (for example). > > Anyway, this task is left for latter. ^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: Premerging topics (was: [RFD] annnotating a pair of commit objects?) 2013-04-10 20:35 Premerging topics (was: [RFD] annnotating a pair of commit objects?) Antoine Pelisse 2013-04-22 9:23 ` Antoine Pelisse @ 2013-04-23 6:34 ` Johan Herland 2013-04-23 14:51 ` Antoine Pelisse 2013-04-23 14:53 ` Junio C Hamano 1 sibling, 2 replies; 16+ messages in thread From: Johan Herland @ 2013-04-23 6:34 UTC (permalink / raw) To: Antoine Pelisse; +Cc: Michael Haggerty, Junio C Hamano, Jeff King, git On Wed, Apr 10, 2013 at 10:35 PM, Antoine Pelisse <apelisse@gmail.com> wrote: > The goal is to propose a structure for storing and pre-merging pairs of commits. > > Data-structure > ============== > > We could use a note ref to store the pre-merge information. Each commit > would be annotated with a blob containing the list of pre-merges (one > sha1 per line with sha1 pointing to a merge commit). The commit on the > other side of a merge would also be annotated. > The choice of the refname could be done like we do with notes: > - Have a default value > - Have a default value configured in config > - Use a specific value when merging/creating the pre-merges > > Here are my concerns: > > Pros > ---- > 1. Notes allow dynamic annotation a commit > 2. If we manage to fix 4, we can easily download all pre-merges from a > remote host by fetching the ref (or clean by deleting the ref). > 3. Conflicts on pre-merge notes could probably be resolved by concatenation. > > Cons > ---- > 4. Checking connectivity means opening the blob and parsing it Can you solve this problem with a tree object, instead of inventing a specially-formatted blob? I.e. given pre-merge info P for a merge between commits A and B: A is annotated by a tree object that contains all pre-merges where A is involved. Each entry in the tree object has a filename and a blob SHA1; we store other commit involved in this pre-merge (B) in the filename, and the pre-merge data (P) in the blob SHA1. Conversely, B is annotated with a tree object containing all pre-merge entries concerning B, and within there, is an entry called A, pointing to the same P. You still need "special handling" in that you have to know that "refs/notes/pre-merge" (or whatever it's called) stores tree objects instead of blobs, and how to interpret the contents of those trees, but you'll need such knowledge in any case. A nice side-effect of using tree objects to store pre-merge entries, is that you can do a tree-level merge of them, and it'll do the Right Thing (assuming two-way merge with no common history), i.e. perform a union merge, and leave you to handle conflicts of individual pre-merges (i.e. you'll only get conflicts when both sides offer different pre-merge data for A + B). > 5. Regular notes and pre-merge notes have to be handled separately > because of 4. Sort of, but you get that for any automated usage of notes for a specific purpose. Just look at the notes-cache mechanism in notes-cache.{h,c}. That's another example of functionality layered on top of notes that makes assumptions on how its notes trees are structured. > I'm hoping we can keep the pros and avoid the cons, but I'm kind of > stuck here. Help would be really appreciated (or maybe this is a totally > wrong direction, and I would also like to know ;) > > Merging (Using what we saved) > ============================= > The goal is to merge branches J and B using existing pre-merges. > > E0. Create an empty stack S > E1. Create set of commits 'J..B' and 'B..J' (that is probably already done) > E2. For each commit C in smallest(J..B, B..J), execute E3 > E3. For each premerge P in notes-premerge(C), execute E4 > E4. If one of both parents of P belongs to biggest(J..B, B..J), stack P in S I don't think _both_ parents of P can belong to biggest(J..B, B..J). AFAICS J..B and B..J must always be completely disjoint sets of commits (this is easier to see when you consider that "A..B" is equivalent to "B ^A" for any commits A and B), and in E2/E3, you have already made sure that P has a parent in one of them. There is then no way that the same parent can occur in the other set, so you have at most _one_ parent in the other set. > E5. Merge J and B using all pre-merges from S This is where things get complicated... :) First there is one important thing that I have not seen a decision on yet (maybe this was discussed in an earlier thread?): Given pre-merge data P for commit A and B, does P encode the merge of the entire history up to A with the entire history up to B, or does it only encode the merging of the changes introduced in A with the changes introduced in B? In other words, are we merging snapshots or diffs? In the former case, we only need to find the most recent commits A and B on their respective branches - for which P exists - and then execute that one P (or at most two Ps, if there is a criss-cross pre-merge situation). In the other case, however, we need to enumerate all Ps that apply to the two branches, and find a way to execute them chronologically, dealing with missing Ps and conflicting Ps along the way. IMHO, only the former approach seems practically solvable. So you do not need to enumerate all Ps in J..B vs. B..J, you only need to find the _most_ _recent_ P, and execute that one. > Let's consider that |J..B| is smaller than |B..J|. > E0 is executed only once > E1 is O(|J..B| + |B..J|) > E2 is O(|J..B|) > E3 is O(|J..B| x the average number of pre-merge per commits P_avg) > E4 is executed for each parent (let's say it's two/constant, after all > the topic is "pair" of commits), so still O(|J..B| x P_avg) > E5 I don't know (how it can be done, and what would be the resulting > time complexity) > > So the time cost for steps E0 to E4 is O(|J..B| + |B..J| x P_avg) > > Tools (Save the pre-merges) > =========================== > > Of course we need several tools to maintain the list of premerges, and > to easily compute them. For example, it would be nice to be able to do > something like: > > $ git pre-merge topicA topicB topicC > > to find, resolve and store all interactions between the topics. Let's leave out octopus merges for now, and only concentrate on two branches at a time. Hope this helps, ...Johan PS: Could you also use this mechanism to store rerere information? > We could > then easily derive to something that would allow to pre-merge a new > topic with all topics already merged in master..pu (for example). > Anyway, this task is left for latter. -- Johan Herland, <johan@herland.net> www.herland.net ^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: Premerging topics (was: [RFD] annnotating a pair of commit objects?) 2013-04-23 6:34 ` Johan Herland @ 2013-04-23 14:51 ` Antoine Pelisse 2013-04-23 23:06 ` Johan Herland 2013-04-23 14:53 ` Junio C Hamano 1 sibling, 1 reply; 16+ messages in thread From: Antoine Pelisse @ 2013-04-23 14:51 UTC (permalink / raw) To: Johan Herland; +Cc: Michael Haggerty, Junio C Hamano, Jeff King, git On Tue, Apr 23, 2013 at 8:34 AM, Johan Herland <johan@herland.net> wrote: > On Wed, Apr 10, 2013 at 10:35 PM, Antoine Pelisse <apelisse@gmail.com> wrote: >> Data-structure >> ============== >> We could use a note ref to store the pre-merge information. Each commit >> would be annotated with a blob containing the list of pre-merges (one >> sha1 per line with sha1 pointing to a merge commit). The commit on the >> other side of a merge would also be annotated. >> The choice of the refname could be done like we do with notes: >> - Have a default value >> - Have a default value configured in config >> - Use a specific value when merging/creating the pre-merges >>[snipped] >> Cons >> ---- >> 4. Checking connectivity means opening the blob and parsing it > > Can you solve this problem with a tree object, instead of inventing a > specially-formatted blob? That looks like a good idea. > I.e. given pre-merge info P for a merge between commits A and B: A is > annotated by a tree object that contains all pre-merges where A is > involved. Each entry in the tree object has a filename and a blob > SHA1; we store other commit involved in this pre-merge (B) in the > filename, and the pre-merge data (P) in the blob SHA1. But P is a commit(/merge with two parents), not a blob. Can we have trees pointing to commits instead of blobs ? > A nice side-effect of using tree objects to store pre-merge entries, > is that you can do a tree-level merge of them, and it'll do the Right > Thing (assuming two-way merge with no common history), i.e. perform a > union merge, and leave you to handle conflicts of individual > pre-merges (i.e. you'll only get conflicts when both sides offer > different pre-merge data for A + B). I'm definitely not sure what you mean here, especially with "tree-level merge". Also I think we could be doing three-way merges. But I'm no merge-strategies expert, so I'm kind of confused. >> 5. Regular notes and pre-merge notes have to be handled separately >> because of 4. > > Sort of, but you get that for any automated usage of notes for a > specific purpose. Just look at the notes-cache mechanism in > notes-cache.{h,c}. That's another example of functionality layered on > top of notes that makes assumptions on how its notes trees are > structured. Thanks, I will have a look at it. >> The goal is to merge branches J and B using existing pre-merges. >> >> E0. Create an empty stack S >> E1. Create set of commits 'J..B' and 'B..J' (that is probably already done) >> E2. For each commit C in smallest(J..B, B..J), execute E3 >> E3. For each premerge P in notes-premerge(C), execute E4 >> E4. If one of both parents of P belongs to biggest(J..B, B..J), stack P in S > > I don't think _both_ parents of P can belong to biggest(J..B, B..J). > AFAICS J..B and B..J must always be completely disjoint sets of > commits (this is easier to see when you consider that "A..B" is > equivalent to "B ^A" for any commits A and B), and in E2/E3, you have > already made sure that P has a parent in one of them. There is then no > way that the same parent can occur in the other set, so you have at > most _one_ parent in the other set. I agree with that. After step E3, one of the parent belongs to one of the two branches. Step E4 makes sure the other parent belongs to the other branch (and not another unrelated branch). >> E5. Merge J and B using all pre-merges from S > > This is where things get complicated... :) > > First there is one important thing that I have not seen a decision on > yet (maybe this was discussed in an earlier thread?): > > Given pre-merge data P for commit A and B, does P encode the merge of > the entire history up to A with the entire history up to B, or does it > only encode the merging of the changes introduced in A with the > changes introduced in B? In other words, are we merging snapshots or > diffs? > > In the former case, we only need to find the most recent commits A and > B on their respective branches - for which P exists - and then execute > that one P (or at most two Ps, if there is a criss-cross pre-merge > situation). In the other case, however, we need to enumerate all Ps > that apply to the two branches, and find a way to execute them > chronologically, dealing with missing Ps and conflicting Ps along the > way. IMHO, only the former approach seems practically solvable. > > So you do not need to enumerate all Ps in J..B vs. B..J, you only need > to find the _most_ _recent_ P, and execute that one. Indeed, we only need to know the most recent. That's a good point. B1 B2 B3 O---o---o-----o | / \ \ | P1 P2 M | / | / \__o_____o___o J1 J2 J3 In this use-case, we can use P1 to compute P2, and then we only need P2 to compute the real merge M. The idea would be to use P1 as an implicit third parent to the pre-merge P2, and then P2 as an implicit third parent to the real merge M. >> $ git pre-merge topicA topicB topicC >> >> to find, resolve and store all interactions between the topics. > > Let's leave out octopus merges for now, and only concentrate on two > branches at a time. I was not thinking about octopus merge here, but an easy way to pre-merge many topic in a "single step", so it could be used like this: $ git pre-merge $(git for-each-ref refs/heads/??/*) And that would pre-merge each pair of branches between each other. That is: "n choose 2" (with n the number of branches given) > Hope this helps, It does, thanks ! > PS: Could you also use this mechanism to store rerere information? That's one of the primary goal. The problem it would solves are: - You could apply pre-merges even on clean merges, while rerere focuses on conflict resolution. (typical use case is: somebody renames a function in one topic, somebody decides to use that function in another topic. You won't have a conflict in this situation but the code no longer compiles.) - You could easily pull/push pre-merges while today there is not built-in mechanism to exchange/remote-save rerere's Cheers, Antoine ^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: Premerging topics (was: [RFD] annnotating a pair of commit objects?) 2013-04-23 14:51 ` Antoine Pelisse @ 2013-04-23 23:06 ` Johan Herland 2013-04-24 5:48 ` Premerging topics Junio C Hamano 0 siblings, 1 reply; 16+ messages in thread From: Johan Herland @ 2013-04-23 23:06 UTC (permalink / raw) To: Antoine Pelisse; +Cc: Michael Haggerty, Junio C Hamano, Jeff King, git On Tue, Apr 23, 2013 at 4:51 PM, Antoine Pelisse <apelisse@gmail.com> wrote: > On Tue, Apr 23, 2013 at 8:34 AM, Johan Herland <johan@herland.net> wrote: >> On Wed, Apr 10, 2013 at 10:35 PM, Antoine Pelisse <apelisse@gmail.com> wrote: >>> Data-structure >>> ============== >>> We could use a note ref to store the pre-merge information. Each commit >>> would be annotated with a blob containing the list of pre-merges (one >>> sha1 per line with sha1 pointing to a merge commit). The commit on the >>> other side of a merge would also be annotated. >>> The choice of the refname could be done like we do with notes: >>> - Have a default value >>> - Have a default value configured in config >>> - Use a specific value when merging/creating the pre-merges >>>[snipped] >>> Cons >>> ---- >>> 4. Checking connectivity means opening the blob and parsing it >> >> Can you solve this problem with a tree object, instead of inventing a >> specially-formatted blob? > > That looks like a good idea. > >> I.e. given pre-merge info P for a merge between commits A and B: A is >> annotated by a tree object that contains all pre-merges where A is >> involved. Each entry in the tree object has a filename and a blob >> SHA1; we store other commit involved in this pre-merge (B) in the >> filename, and the pre-merge data (P) in the blob SHA1. > > But P is a commit(/merge with two parents), not a blob. Can we have trees > pointing to commits instead of blobs ? Sort of. We do so when recording submodules in regular git trees. The submodule is recorded as a tree entry whose type is commit, name is the subdir containing the submodule, and SHA1 is the commit ID recorded for that submodule at this point in history. So it definitely _can_ be done. That said, I'm not sure whether it's actually a good idea in this case... I apologize for not having followed the initial discussion. I did not know that the pre-merge information would be stored as commits. I figured since it would only contain a partial merge resolution, it could be represented as something like a tree object containing (only) the pre-resolved paths... >> A nice side-effect of using tree objects to store pre-merge entries, >> is that you can do a tree-level merge of them, and it'll do the Right >> Thing (assuming two-way merge with no common history), i.e. perform a >> union merge, and leave you to handle conflicts of individual >> pre-merges (i.e. you'll only get conflicts when both sides offer >> different pre-merge data for A + B). > > I'm definitely not sure what you mean here, especially with "tree-level > merge". Also I think we could be doing three-way merges. But I'm > no merge-strategies expert, so I'm kind of confused. I was simply thinking of the simple two-way merge that can be done between two independent trees: - All paths that exist in only one tree, exist unchanged in the result - All paths that are identical in both trees exist unchanged in the result - All paths that exist in both trees, but are different, yield a conflict In your pre-merge domain, this would become (for given commit A, B, C): - If one tree records a pre-merge for (A,B) and the other for (A,C), then both (A,B) and (A,C) will exist in the result - If both trees records the _same_ pre-merge resolution for (A,B), then that will also exist in the result - If one tree records one way of pre-merging (A,B), and the other tree records a _different_ way of pre-merging (A,B), then there will be a conflict that needs resolving. >>> 5. Regular notes and pre-merge notes have to be handled separately >>> because of 4. >> >> Sort of, but you get that for any automated usage of notes for a >> specific purpose. Just look at the notes-cache mechanism in >> notes-cache.{h,c}. That's another example of functionality layered on >> top of notes that makes assumptions on how its notes trees are >> structured. > > Thanks, I will have a look at it. > >>> The goal is to merge branches J and B using existing pre-merges. >>> >>> E0. Create an empty stack S >>> E1. Create set of commits 'J..B' and 'B..J' (that is probably already done) >>> E2. For each commit C in smallest(J..B, B..J), execute E3 >>> E3. For each premerge P in notes-premerge(C), execute E4 >>> E4. If one of both parents of P belongs to biggest(J..B, B..J), stack P in S >> >> I don't think _both_ parents of P can belong to biggest(J..B, B..J). >> AFAICS J..B and B..J must always be completely disjoint sets of >> commits (this is easier to see when you consider that "A..B" is >> equivalent to "B ^A" for any commits A and B), and in E2/E3, you have >> already made sure that P has a parent in one of them. There is then no >> way that the same parent can occur in the other set, so you have at >> most _one_ parent in the other set. > > I agree with that. After step E3, one of the parent belongs to one of > the two branches. Step E4 makes sure the other parent belongs to the other > branch (and not another unrelated branch). > >>> E5. Merge J and B using all pre-merges from S >> >> This is where things get complicated... :) >> >> First there is one important thing that I have not seen a decision on >> yet (maybe this was discussed in an earlier thread?): >> >> Given pre-merge data P for commit A and B, does P encode the merge of >> the entire history up to A with the entire history up to B, or does it >> only encode the merging of the changes introduced in A with the >> changes introduced in B? In other words, are we merging snapshots or >> diffs? >> >> In the former case, we only need to find the most recent commits A and >> B on their respective branches - for which P exists - and then execute >> that one P (or at most two Ps, if there is a criss-cross pre-merge >> situation). In the other case, however, we need to enumerate all Ps >> that apply to the two branches, and find a way to execute them >> chronologically, dealing with missing Ps and conflicting Ps along the >> way. IMHO, only the former approach seems practically solvable. >> >> So you do not need to enumerate all Ps in J..B vs. B..J, you only need >> to find the _most_ _recent_ P, and execute that one. > > Indeed, we only need to know the most recent. That's a good point. > > B1 B2 B3 > O---o---o-----o > | / \ \ > | P1 P2 M > | / | / > \__o_____o___o > J1 J2 J3 > > In this use-case, we can use P1 to compute P2, and then we only need P2 > to compute the real merge M. The idea would be to use P1 as an implicit > third parent to the pre-merge P2, and then P2 as an implicit third > parent to the real merge M. I'm not sure what is really meant by "implicit third parent" here. Will Git automatically help you (i.e. auto-resolve conflicts in the correct manner) if you do an octopus merge between (B3, J3, P2)? I thought you would have to start by merging J3 and B3, and then - as part of the conflict resolution process - you invoke the pre-merge machinery, which goes searching for pre-merges and finds P2, which is then used to help auto-resolve (some of) the conflicts between B3 and J3. This means that P2 is not a merge parent in the traditional sense, and I also don't think that you want to record P2 as a "proper" parent of the resulting merge commit M. At this point I also wonder why the pre-merges (P1, P2) has to be recorded as commit objects. Do they have meaningful commit messages? ...Johan >>> $ git pre-merge topicA topicB topicC >>> >>> to find, resolve and store all interactions between the topics. >> >> Let's leave out octopus merges for now, and only concentrate on two >> branches at a time. > > I was not thinking about octopus merge here, but an easy way to > pre-merge many topic in a "single step", so it could be used like this: > > $ git pre-merge $(git for-each-ref refs/heads/??/*) > > And that would pre-merge each pair of branches between each other. That > is: "n choose 2" (with n the number of branches given) > >> Hope this helps, > > It does, thanks ! > >> PS: Could you also use this mechanism to store rerere information? > > That's one of the primary goal. The problem it would solves are: > - You could apply pre-merges even on clean merges, while rerere focuses > on conflict resolution. (typical use case is: somebody renames a > function in one topic, somebody decides to use that function in another > topic. You won't have a conflict in this situation but the code no > longer compiles.) > - You could easily pull/push pre-merges while today there is not built-in > mechanism to exchange/remote-save rerere's -- Johan Herland, <johan@herland.net> www.herland.net ^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: Premerging topics 2013-04-23 23:06 ` Johan Herland @ 2013-04-24 5:48 ` Junio C Hamano 2013-04-24 6:22 ` Johan Herland 2013-04-29 13:04 ` Antoine Pelisse 0 siblings, 2 replies; 16+ messages in thread From: Junio C Hamano @ 2013-04-24 5:48 UTC (permalink / raw) To: Johan Herland; +Cc: Antoine Pelisse, Michael Haggerty, Jeff King, git Johan Herland <johan@herland.net> writes: >> But P is a commit(/merge with two parents), not a blob. Can we have trees >> pointing to commits instead of blobs ? > > Sort of. We do so when recording submodules in regular git trees. You are using notes to maintain reachability, aren't you? Because commit objects that appears in trees are not treated as reachable from the trees, that won't fly. I think you guys are making it unnecessarily complex by using notes. To record a prepared evil merge for merging branch that contains A with another branch that contains B (assuming that the interation between A and B is what makes the evil merge necessary, e.g. A renames a function foo() to bar(), while B adds new callsite that calls foo()), we can store a single commit that records the prepared evil merge under "refs/merge-fix/$A-$B" where A and B are their object names. Then when merging a branch Y that contains B into our history X that already contains A (or vice versa), ---o---o---A---o---X... ??? \ . \ . \ . o---B----o---Y we can enumerate the commits that appear in "log --left-right X...Y" on the left/right side and notice there is refs/merge-fix/$A-$B. So the simplest implementation of "an efficient data store to record a commit for <A,B> pair" turns out to be just a ref namespace ;-) There may be other <C,D> pairs in X...Y history, and it probably is the sane thing to do to replay prepackaged evil merges from older to newer in the topological sense, but that loop would be trivial, once we understand how to replay a single such evil merge. The actual merge-fix data should be just a commit with a single parent. The easiest way to prepare it would be like this: ---o---o---A \ \ \ M---F \ / o---B where M is the result of mechanical merge between A and B (there could be textual conflicts and you could choose to leave them in, or you could choose to have rerere resolve it. As long as you do the same when replaying this prepackaged evil merge, this choice does not matter, but using rerere will make your life easier), and F is the final result you would want, with semantics conflicts resolved. In other words, in the ideal world, you would have resolved a merge between A and B to record the tree of F. Point "F" with refs/merge-fix/$A-$B and you are done. When you replay this prepackaged evil merge, first you mechanically merge X and Y without worrying about M or F to produce N. If you allowed rerere to resolve textual conflicts between A and B when you recorded M, allow rerere to resolve this merge. Otherwise leave the textual conflict in. ---o---o---A---o---X \ \ \ N \ / o---B---o---Y Then on top of N, you cherry-pick F, which will bring the semantic conflict resolution between M and F on top of N. ---o---o---A---o---X \ \ \ N---F' \ / o---B---o---Y Once you know the tree shape of F', then you no longer need N. Just amend it away and make the tree recorded in F' the result of the merge between X and Y. ---o---o---A---o---X---. \ \ \ F'' \ / o---B---o---Y--. ^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: Premerging topics 2013-04-24 5:48 ` Premerging topics Junio C Hamano @ 2013-04-24 6:22 ` Johan Herland 2013-04-24 7:14 ` Junio C Hamano 2013-04-29 13:04 ` Antoine Pelisse 1 sibling, 1 reply; 16+ messages in thread From: Johan Herland @ 2013-04-24 6:22 UTC (permalink / raw) To: Junio C Hamano; +Cc: Antoine Pelisse, Michael Haggerty, Jeff King, git On Wed, Apr 24, 2013 at 7:48 AM, Junio C Hamano <gitster@pobox.com> wrote: > Johan Herland <johan@herland.net> writes: > >>> But P is a commit(/merge with two parents), not a blob. Can we have trees >>> pointing to commits instead of blobs ? >> >> Sort of. We do so when recording submodules in regular git trees. > > You are using notes to maintain reachability, aren't you? Because > commit objects that appears in trees are not treated as reachable > from the trees, that won't fly. > > I think you guys are making it unnecessarily complex by using notes. > To record a prepared evil merge for merging branch that contains A > with another branch that contains B (assuming that the interation > between A and B is what makes the evil merge necessary, e.g. A > renames a function foo() to bar(), while B adds new callsite that > calls foo()), we can store a single commit that records the prepared > evil merge under "refs/merge-fix/$A-$B" where A and B are their > object names. > > Then when merging a branch Y that contains B into our history X that > already contains A (or vice versa), > > ---o---o---A---o---X... ??? > \ . > \ . > \ . > o---B----o---Y > > we can enumerate the commits that appear in "log --left-right X...Y" > on the left/right side and notice there is refs/merge-fix/$A-$B. > > So the simplest implementation of "an efficient data store to record > a commit for <A,B> pair" turns out to be just a ref namespace ;-) > > There may be other <C,D> pairs in X...Y history, and it probably is > the sane thing to do to replay prepackaged evil merges from older to > newer in the topological sense, but that loop would be trivial, once > we understand how to replay a single such evil merge. This raises the same question I recently asked Antoine: For a given prepackaged merge <X,Y>, do we assume that it only resolves conflicts between the changes introduced in commit X vs. changes introduced in commit Y, or do we assume that it resolves conflicts between the histories leading up to X and Y, respectively? In other words, does <X,Y> _supercede_ earlier pre-merges between the histories leading up to X and Y? I think the latter makes more sense, since we can then reduce the number of pre-merges to consider in the final merge. There might still be more than one pre-merge to consider, though, e.g. in criss-cross cases like this: ---o---o---o---o---o---o---o---o \ / \ \ \ / \ \ \ / \ \ o---o---o P2 \ \ \ / \ \ \ / M \ \ / / o---o---+---o / \ \ \ / \ P1 \ / \ / \ / o---o---o---o---o (there is no commit at the "+") > The actual merge-fix data should be just a commit with a single > parent. The easiest way to prepare it would be like this: > > ---o---o---A > \ \ > \ M---F > \ / > o---B > > where M is the result of mechanical merge between A and B (there > could be textual conflicts and you could choose to leave them in, or > you could choose to have rerere resolve it. As long as you do the > same when replaying this prepackaged evil merge, this choice does > not matter, but using rerere will make your life easier), and F is > the final result you would want, with semantics conflicts resolved. > In other words, in the ideal world, you would have resolved a merge > between A and B to record the tree of F. > > Point "F" with refs/merge-fix/$A-$B and you are done. > > When you replay this prepackaged evil merge, first you mechanically > merge X and Y without worrying about M or F to produce N. If you > allowed rerere to resolve textual conflicts between A and B when you > recorded M, allow rerere to resolve this merge. Otherwise leave the > textual conflict in. > > ---o---o---A---o---X > \ \ > \ N > \ / > o---B---o---Y > > Then on top of N, you cherry-pick F, which will bring the semantic > conflict resolution between M and F on top of N. > > ---o---o---A---o---X > \ \ > \ N---F' > \ / > o---B---o---Y > > Once you know the tree shape of F', then you no longer need N. Just > amend it away and make the tree recorded in F' the result of the > merge between X and Y. > > ---o---o---A---o---X---. > \ \ > \ F'' > \ / > o---B---o---Y--. This is obviously a much better way to solve it. It might already be obvious, but I would suggest when making "refs/merge-fix/$A-$B" that you canonicalize the name by always choosing A and B such that A precedes B alphabetically. That way you won't have problems with both recording "refs/merge-fix/$A-$B" and "refs/merge-fix/$B-$A". ...Johan -- Johan Herland, <johan@herland.net> www.herland.net ^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: Premerging topics 2013-04-24 6:22 ` Johan Herland @ 2013-04-24 7:14 ` Junio C Hamano 2013-04-29 19:06 ` Antoine Pelisse 0 siblings, 1 reply; 16+ messages in thread From: Junio C Hamano @ 2013-04-24 7:14 UTC (permalink / raw) To: Johan Herland; +Cc: Antoine Pelisse, Michael Haggerty, Jeff King, git Johan Herland <johan@herland.net> writes: > This raises the same question I recently asked Antoine: For a given > prepackaged merge <X,Y>, do we assume that it only resolves conflicts > between the changes introduced in commit X vs. changes introduced in > commit Y, or do we assume that it resolves conflicts between the > histories leading up to X and Y, respectively? In other words, does > <X,Y> _supercede_ earlier pre-merges between the histories leading up > to X and Y? That is an interesting question. There are largely two cases. When you replayed M---F to produce N---F', there may have been no textual or semantic conflict. Which means that there were no new reason between A--X and B--Y that necessitates an evil merge. A later merge between a descendant of X (but not Y) and a descendant of Y (but not X) can cherry pick the same <A,B> (M---F) on top of a mechanical merge, On the other hand, you may have had either a textual or a semantic conflict when replaying <A,B> on N, and you had to fix up F' for it to be the correct resolution of merge between X and Y. ---o---o---A---o---X \ \ \ N---F' \ / o---B---o---Y In such a case, you do want to record the fixed N---F' as the prepackaged resolution for <X,Y>. Any time later when somebody is on a branch that has X (but not Y) and merges a branch that has Y (but not X), that N---F' should be the one to cherry-pick on top of a mechanical merge O between S and T. What <A,B> (M---F) did is superseded if you are going to replay <X,Y>. ---o---o---A---o---X----------S \ \ \ \ \ M--F N---F' O---F'' \ / / / o---B---o---Y----------T You can tell that by noticing that A is an ancestor of X and B is an ancestor of Y. As you said, this is a good way to reduce the number of prepackaged evil merges that need to be considered. ^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: Premerging topics 2013-04-24 7:14 ` Junio C Hamano @ 2013-04-29 19:06 ` Antoine Pelisse 2013-04-29 22:19 ` Junio C Hamano 0 siblings, 1 reply; 16+ messages in thread From: Antoine Pelisse @ 2013-04-29 19:06 UTC (permalink / raw) To: Junio C Hamano; +Cc: Johan Herland, Michael Haggerty, Jeff King, git Should we think about adding some commands for that ? On the very top of my head (there is certainly more than that): - Save such a change: By basically creating a ref to HEAD (HEAD being the commit, HEAD^ the fixed merge) with merge-fix/HEAD^^1-HEAD^^2 - Apply the merge-fix: On top of a merge, find the most recent merge-fix for HEAD^1/HEAD^2 (according to what was discussed), and squash it. On Wed, Apr 24, 2013 at 9:14 AM, Junio C Hamano <gitster@pobox.com> wrote: > Johan Herland <johan@herland.net> writes: > >> This raises the same question I recently asked Antoine: For a given >> prepackaged merge <X,Y>, do we assume that it only resolves conflicts >> between the changes introduced in commit X vs. changes introduced in >> commit Y, or do we assume that it resolves conflicts between the >> histories leading up to X and Y, respectively? In other words, does >> <X,Y> _supercede_ earlier pre-merges between the histories leading up >> to X and Y? > > That is an interesting question. There are largely two cases. > > When you replayed M---F to produce N---F', there may have been no > textual or semantic conflict. Which means that there were no new > reason between A--X and B--Y that necessitates an evil merge. A > later merge between a descendant of X (but not Y) and a descendant > of Y (but not X) can cherry pick the same <A,B> (M---F) on top of a > mechanical merge, > > On the other hand, you may have had either a textual or a semantic > conflict when replaying <A,B> on N, and you had to fix up F' for it > to be the correct resolution of merge between X and Y. > > ---o---o---A---o---X > \ \ > \ N---F' > \ / > o---B---o---Y > > In such a case, you do want to record the fixed N---F' as the > prepackaged resolution for <X,Y>. Any time later when somebody is > on a branch that has X (but not Y) and merges a branch that has Y > (but not X), that N---F' should be the one to cherry-pick on top of > a mechanical merge O between S and T. What <A,B> (M---F) did is > superseded if you are going to replay <X,Y>. > > ---o---o---A---o---X----------S > \ \ \ \ > \ M--F N---F' O---F'' > \ / / / > o---B---o---Y----------T > > You can tell that by noticing that A is an ancestor of X and B is an > ancestor of Y. As you said, this is a good way to reduce the number > of prepackaged evil merges that need to be considered. > ^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: Premerging topics 2013-04-29 19:06 ` Antoine Pelisse @ 2013-04-29 22:19 ` Junio C Hamano 0 siblings, 0 replies; 16+ messages in thread From: Junio C Hamano @ 2013-04-29 22:19 UTC (permalink / raw) To: Antoine Pelisse; +Cc: Johan Herland, Michael Haggerty, Jeff King, git Antoine Pelisse <apelisse@gmail.com> writes: > Should we think about adding some commands for that ? > > On the very top of my head (there is certainly more than that): > - Save such a change: By basically creating a ref to HEAD (HEAD being > the commit, HEAD^ the fixed merge) with merge-fix/HEAD^^1-HEAD^^2 > - Apply the merge-fix: On top of a merge, find the most recent > merge-fix for HEAD^1/HEAD^2 (according to what was discussed), and > squash it. Yeah, some nasties may live in the details, but these two operations are needed and probably sufficient as the end-user facing UI. The "save" step, when done manually, needs to be a two-step process that saves M and then F separately, but somebody _might_ be able to come up with a clever idea to let the user jump directly to F without recording M. If such a triangle (A and B merges to F) can be recorded as merge-fix/A-B, that would certainly be less error prone and easier for the users to use. Having said that, in the presense of possible textual conflicts when creating M, I do not think of a way that is easily implementable mechanically to internally sift changes for M and F when replaying it while resolving a merge between X and Y to produce N and eventually F'. The "apply" step should be a single step, and it should be easy to implement mechanically if M and F are recorded separately (but again, you may be able to re-synthesise M from A and B when you need to replay the evil merge). ^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: Premerging topics 2013-04-24 5:48 ` Premerging topics Junio C Hamano 2013-04-24 6:22 ` Johan Herland @ 2013-04-29 13:04 ` Antoine Pelisse 2013-04-29 15:08 ` Junio C Hamano 1 sibling, 1 reply; 16+ messages in thread From: Antoine Pelisse @ 2013-04-29 13:04 UTC (permalink / raw) To: Junio C Hamano; +Cc: Johan Herland, Michael Haggerty, Jeff King, git On Wed, Apr 24, 2013 at 7:48 AM, Junio C Hamano <gitster@pobox.com> wrote: > there > could be textual conflicts and you could choose to leave them in, or > you could choose to have rerere resolve it. As long as you do the > same when replaying this prepackaged evil merge, this choice does > not matter, but using rerere will make your life easier The problem is that rerere can not be easily shared, and I'm afraid it would be almost impossible to go without it (do you commit N with conflict markers hoping that F' will remove them?). But, as it looks like you would save F on top of M, it means that M would be reachable, and thus rerere would be "recomputable" from somewhere else. ^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: Premerging topics 2013-04-29 13:04 ` Antoine Pelisse @ 2013-04-29 15:08 ` Junio C Hamano 0 siblings, 0 replies; 16+ messages in thread From: Junio C Hamano @ 2013-04-29 15:08 UTC (permalink / raw) To: Antoine Pelisse; +Cc: Johan Herland, Michael Haggerty, Jeff King, git Antoine Pelisse <apelisse@gmail.com> writes: > But, as it looks like you would save F on top of M, it means that M > would be reachable, and thus rerere would be "recomputable" from > somewhere else. Exactly. ^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: Premerging topics 2013-04-23 6:34 ` Johan Herland 2013-04-23 14:51 ` Antoine Pelisse @ 2013-04-23 14:53 ` Junio C Hamano 2013-04-23 15:17 ` Antoine Pelisse 1 sibling, 1 reply; 16+ messages in thread From: Junio C Hamano @ 2013-04-23 14:53 UTC (permalink / raw) To: Johan Herland; +Cc: Antoine Pelisse, Michael Haggerty, Jeff King, git Johan Herland <johan@herland.net> writes: > Can you solve this problem with a tree object, instead of inventing a > specially-formatted blob? Hmm. What problem are you guys trying to solve? I think Michael's use of a merge commit to record a merge result is sufficient as a means to record how to recreate an evil merge. http://thread.gmane.org/gmane.comp.version-control.git/212570/focus=212578 FWIW, in the [RFD], I wasn't asking for ideas on that part. When rebuiling 'pu', I use an even simpler solution to have rerere autoresolve the mechanical part of the merge, and then cherry-pick a separate commit from refs/merge-fix/ hierarchy on the result, and it works perfectly fine (this is done by the 'Reintegrate' script on the 'todo' branch; see Documentation/howto/maintain-git.txt). When topic A is closer to be done than topic B (in other words, when I merge topic B to an integration branch, topic A is already merged there), and these topics have semantic conflicts (e.g. A renames a function foo() to bar(), while B adds a new callsite of foo()), a mechanical merge of B may succeed without any textual conflict (or if there is, rerere can resolve it), but a semantic fix-up needs to do "s/foo/bar/g" on the result. I would do something like this for the first time: ... while on 'pu', A has already been merged ... git merge B ;# may conflict edit textual conflicts away git rerere ;# remember the textual resolution git commit ;# commit _without_ semantics adjustment edit semantic conflict away, i.e. s/foo/bar/g git commit git update-ref refs/merge-fix/B After that, next time I rebuild 'pu', when the automated procedure processes B, it would "git merge B", "git rerere", make sure textual conflicts are gone, and "git cherry-pick refs/merge-fix/B". To make sure this would work, what I typically do immediately after doing all of the above is: git reset --hard HEAD^^ to drop the fix-up commit and merge of B, and actually tell the automated procedure to process B. It should recreate the evil merge using the information I just recorded. So "how a recipe to recreate an evil merge is recorded", as far as I am concerned, is an already solved problem. The part of the existing solution I was not happy was deciding when to use which "merge-fix" commit to cherry-pick. If I start merging topic B before topic A, the "merge-fix/B" needs to be renamed to "merge-fix/A" in the above. Otherwise, when B is merged to 'pu', there is no 'A' merged to it yet, so merge-fix that munges its new call to foo() to call bar() instead will _break_ things [*1*]. And that was why I wanted to have a data structure that is quick to query to answer "I am about to merge B. Does the history already have an A for which I have recorded a merge-fix for <A,B> pair?" [Footnote] *1* If A has other kinds of conflicts with other topics, it is not sufficient to just rename "merge-fix/B" to "merge-fix/A"---the effect of cherry-picking "merge-fix/B" needs to be merged to existing "merge-fix/A". If a merge-fix is recorded for a pair of commits that necessitates an evil merge, this naturally goes away. I can keep a merge-fix for the <A,B> pair whether I merge A before or after B, and semantic conflicts A may have with another topic C would be stored in a separate merge-fix for <A,C> pair. ^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: Premerging topics 2013-04-23 14:53 ` Junio C Hamano @ 2013-04-23 15:17 ` Antoine Pelisse 2013-04-23 15:29 ` Junio C Hamano 0 siblings, 1 reply; 16+ messages in thread From: Antoine Pelisse @ 2013-04-23 15:17 UTC (permalink / raw) To: Junio C Hamano; +Cc: Johan Herland, Michael Haggerty, Jeff King, git On Tue, Apr 23, 2013 at 4:53 PM, Junio C Hamano <gitster@pobox.com> wrote: > Johan Herland <johan@herland.net> writes: > >> Can you solve this problem with a tree object, instead of inventing a >> specially-formatted blob? > > Hmm. What problem are you guys trying to solve? > > [snipped..] > And that was why I wanted to have a data structure that is quick to > query to answer "I am about to merge B. Does the history already > have an A for which I have recorded a merge-fix for <A,B> pair?" That's exactly the problem I'm trying to solve. I'm willing to have an efficient way to merge topicC that has semantic conflicts with topicA and topicB. As topics will be merged together first in pu, then in next and finally in master, chances are that they won't be merged in the same order (or then, why would we even care about a topic workflow?). And I have the feeling that "merge-fix/B" or "merge-fix/A" doesn't hold enough information to do that accurately. The idea is then to store the <A, B> pair as a note, and to associate a "merge" to that (solving the semantic conflict). It would then be used as an implicit third parent for the merge of "branch containing A" and "branch containing B". This is pretty much what Michael said in the $gmane you talked about. Cheers, Antoine ^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: Premerging topics 2013-04-23 15:17 ` Antoine Pelisse @ 2013-04-23 15:29 ` Junio C Hamano 2013-04-23 15:36 ` Antoine Pelisse 0 siblings, 1 reply; 16+ messages in thread From: Junio C Hamano @ 2013-04-23 15:29 UTC (permalink / raw) To: Antoine Pelisse; +Cc: Johan Herland, Michael Haggerty, Jeff King, git Antoine Pelisse <apelisse@gmail.com> writes: > And I > have the feeling that "merge-fix/B" or "merge-fix/A" doesn't hold > enough information to do that accurately. Oh, you do not have to resort to feeling; these names do _not_ hold enough information, period. We already know that, that was why I was unhappy, and that was why I sent the "annotating a pair of commit objects" RFD in the first place ;-). > The idea is then to store the <A, B> pair as a note, and to associate > a "merge" to that (solving the semantic conflict). OK, and as the datastore for <A, B> pair you were thinking about using a specially-formatted blob and Johan suggested to use a regular tree object? ^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: Premerging topics 2013-04-23 15:29 ` Junio C Hamano @ 2013-04-23 15:36 ` Antoine Pelisse 0 siblings, 0 replies; 16+ messages in thread From: Antoine Pelisse @ 2013-04-23 15:36 UTC (permalink / raw) To: Junio C Hamano; +Cc: Johan Herland, Michael Haggerty, Jeff King, git On Tue, Apr 23, 2013 at 5:29 PM, Junio C Hamano <gitster@pobox.com> wrote: > Antoine Pelisse <apelisse@gmail.com> writes: > >> And I >> have the feeling that "merge-fix/B" or "merge-fix/A" doesn't hold >> enough information to do that accurately. > > Oh, you do not have to resort to feeling; these names do _not_ hold > enough information, period. We already know that, that was why I was > unhappy, and that was why I sent the "annotating a pair of commit > objects" RFD in the first place ;-). :) >> The idea is then to store the <A, B> pair as a note, and to associate >> a "merge" to that (solving the semantic conflict). > > OK, and as the datastore for <A, B> pair you were thinking about > using a specially-formatted blob and Johan suggested to use a > regular tree object? Exactly. But as I said, it should associate the pair to a merge. And trees contain other trees or blobs, not commits. I'm wondering if this is a problem. ^ permalink raw reply [flat|nested] 16+ messages in thread
end of thread, other threads:[~2013-04-29 22:19 UTC | newest] Thread overview: 16+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2013-04-10 20:35 Premerging topics (was: [RFD] annnotating a pair of commit objects?) Antoine Pelisse 2013-04-22 9:23 ` Antoine Pelisse 2013-04-23 6:34 ` Johan Herland 2013-04-23 14:51 ` Antoine Pelisse 2013-04-23 23:06 ` Johan Herland 2013-04-24 5:48 ` Premerging topics Junio C Hamano 2013-04-24 6:22 ` Johan Herland 2013-04-24 7:14 ` Junio C Hamano 2013-04-29 19:06 ` Antoine Pelisse 2013-04-29 22:19 ` Junio C Hamano 2013-04-29 13:04 ` Antoine Pelisse 2013-04-29 15:08 ` Junio C Hamano 2013-04-23 14:53 ` Junio C Hamano 2013-04-23 15:17 ` Antoine Pelisse 2013-04-23 15:29 ` Junio C Hamano 2013-04-23 15:36 ` Antoine Pelisse
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).