Note: This document is an incomplete draft of a proposal for a design for adding "merge history" to CVS. This would allow you to do multiple merges between the same two branches without getting artificial conflicts.
I have not officially announced this idea anywhere. Maybe someday I will decide to commit the time to actually implement this, and as the first steps I will finish this document and announce it on the mailing list.
Update (Jan 2004): My current thoughts are:
START INTRODUCTION: One weakness with CVS is that it doesn't automatically keep track of merges, so if you want to continue development on a branch after it has already been merged into the main branch, it requires a lot of careful manual tracking of what was merged when (using tags) so that you can cleanly merge the new branch development later without getting conflicts from the already merged changes. So I am seriously thinking about adding merge tracking capabilities to CVS. - This occasionally comes up in the mailing list. It is also mentioned in the TODO list (item 39). - Is there any chance this could make it into CVS proper? I probably won't bother unless there is some assurance that it will make it in as long as it is done well. CVS definately seems to be in a "no new features" mode, with only a few people working on bug fixes part time. It doesn't look particularly likely for any large change to make it in, no matter how useful it might be... The brief version of this proposal is to add a newphrase to the RCS file format to record whenever a merge is done, and modifiy the default single -j "update -j" common-ancestor search algorithm to take into account previous merge history. This idea is expanded on in a fair amount of detail below. If anyone has any additional thoughts or concerns, let me know. END INTRODUCTION. ========================== HERE: Update sourceforge, add links, . ================================================ Expanded Version: Index: - Introduction. - Use cases. - Future extension ideas (not intended to be in original patch) - UI changes brainstorm. - A theoretical, extendable way to represent a merge. - Specific ways to encode merge info in real files and protocols. - Internal design of changes. - Development plan. ================================================ Use case A. Basic: 1. Developers develop some software on the main branch. 2. It is time for release, so they create a release branch. They start fixing bugs and cutting releases off the branch. 3. New features continue to be developed on the main branch. 4. You think you are done with the release branch, so you merge the fixes into the main branch, and you intend to drop the old branch. [When you do the 'cvs update -j', it automatically remembers (in your sandbox) some details about what was merged. When you commit it, that info is automatically stored in a newphrase of the RCS file.] 5. Oops, you are still supporting the old release, and someone just found some more problems. So you fix the problems on the release branch. 6. Now you want to merge the fixes into the main branch as well. All you do is a simple "cvs update -j RELEASE_BRANCH", and it automatically takes the earlier merge into account when calculating which version to use as the common ancestor. [Specifically, it uses what was the old head of the release branch as the new common ancestor.] ================================================ Use case B. Backport: 1. Developers develop some software on the main branch. 2. It is time for release, so they create a release branch. They start fixing bugs and cutting releases off the branch. 3. New features continue to be developed on the main branch. 4. The main branch is currently unstable, but there is one relatively simple feature in it that customers really want now. So you go in and carfully merge the few files needed for that feature from the main branch to the release branch. [Again, merge details are transiently remembered in your sandbox, and then later added to the RCS file.] 5. You continue development on the main branch. 6. Now you want to merge the release branch into the main branch. All you do is a simple "cvs update -j RELEASE_BRANCH", and it automatically takes the earlier merge into account when calculating which version to use as the common ancestor. ================================================ Use case C. External Tools 0. [This is kind of a generalization of the "vendor branch import" support in cvs already, but it should avoid some of the problems, and support merges in both directions without conflicts. No changes will be made to the existing vendor branch support. Instead, it just provides hooks that external tools can use. The external tools (not part of the proposed patch) would treat multiple different repositories as different branches in one big meta-repository. The patch would support ways for those tools to inject extra merge info about the merges done in the meta-repository. So the external tool would use CVS as follows:] 1. The tool starts with 2 sandboxes, tied to the source and destination of the merge. [Perhaps an advanced tool could talk client/server protocol directly, but we'll ignore that possibility.] 2. It searches them to find all controlled files in either one. Then for each file it does the following: 3. The script pulls out the full version tree and merge history for the file from both reposititories (using some cvs command(s) and parsing the output). Then it combines the version trees and merge histories, and seatches them to find what to use as the common ancestor. 4. It use 'cvs update -P' to get a copy of the common ancestor, and perform the merge. 5. It uses some cvs commands to register the details of the merge in the target sandbox. It uses an extended syntax to distinquish other-repository versions from local versions. (In might also add useful merge info fron the target repository to the source repository.) 6. The user can test it, fix it, and commit as usual. [Some scripts may want the user to use a special tool to commit, particularly if the target repository is for a system that can't acknowledge external merges.] ================================================ Mini use cases: D1. If a patched client asks an old server to do a merge, the new client should warn the user that it won't properly record the merge: the server should be upgraded and the merge redone. But continue the merge anyways. D2. If an old client asks a new server to do a merge, the new server should warn the user that it won't properly record the merge: the client should be upgraded and the merge redone. But continue the merge anyways. D3. If new client commits to an old server, and there are merge records scheduled to be added, recover as gracefully as possilble: Maybe: abort the merge (unless -f is selected?) Or: Do the merge, output a warning, and leave the merge records in the sandbox to try again on the next commit (marked to the actual version instead of floating with the sandbox version...)? D4. When the merge record is added to the sandboxes, refer to the target as "checkedout", not a specific version number. Only tie to a specific version when you actually comit a specific version. This way the user can continue doing updates prior to committing, and get the right result. D5. Fix Up Merge History: The user will sometimes want to manually add, remove, or change merge history information. ================================================ ================================================ ================================================ Future use case E: Rename a File 0. This will not be part of the patch I write. Maybe it will be added in the future. 1. Developers develop some software on the main branch. 2. It is time for release, so they create a release branch. They start fixing bugs and cutting releases off the branch. 3. On the main branch, you are doing massive cleanup, including renaming many of the files. You use a special "cvs move" command to rename the files, and it remembers some specialized merge history information into both old and new files so that tools can automatically follow the rename. 4. You are done with the release branch, so you merge the fixes into the main branch. You use some special option to 'cvs update' that causes it to follow the merge history back to the old file in order to the find the changes made on the branch. [Of course, it also records this new merge...] 5. Then you commit, and it remembers the merge so you could do it again. ================================================ Future use case F: Merge Holes 0. This will not be part of the patch. I intend to store merge history in enough detail that in the future CVS could be extended to handle this case correctly without changing the merge history schema, but for now I'm not going to try to deal with the multiple sub-merges for one user-requested merge, or the possibility of N-way merge conflicts. Instead (see below), it will simply treat "GoodChanges" as the common ancestor, having the effect of erroneously removing the UnrelatedChanges from the main branch. (At least the changes are still in version history, so they could be manually re-merged...) 1. Developers develop some software on the main branch. 2. It is time for release, so they create a release branch. They start fixing bugs and cutting releases off the branch. 3. New features continue to be developed on the main branch. 4. The main branch is currently unstable, but there is one relatively simple feature in it that customers really want now. But the version tree currently looks like: Base ------ UnrelatedChanges -------- GoodChanges \ \------ BugFixes So while working on the release branch, the user does something like 'cvs update -jUnrelatedChanges -jGoodChanges FILE'. Now the tree looks like: Base ------ UnrelatedChanges -------- GoodChanges ------ MoreChanges \ | \ | \ \|/ \------ BugFixes -------- MergedGoodChanges ------ MoreFixes But not depicted in this picture is the fact that UnrelatedChanges were *not* merged into MergedGoodChanges. [These diagrams are kind of sloppy in distinguishing actual versions from deltas between versions, but you should be able to get the overall idea anyways...] 5. Now you want to merge the release branch into the main branch. All you do is a simple "cvs update -j RELEASE_BRANCH", and it automatically takes the earlier merge into account when calculating which version to use as the common ancestor. The merge history and merge algorithms are smart enough that cvs will merge "BugFixes" and "MoreFixes" into the main branch, but *not* merge MergeGoodChanges (since those changes are already applied...). ================================================ Future miscellaneous semi-related changes: 0. None of these will be part of the patch... See also .../WishList.txt (HERE), which expands on some of these ideas and also includes some unrelated ideas. G1. Add a per-version "mergetype" newphrase to the RCS file. It would be used to look up (in some config file) plugin merge algorithms to use instead of the default one. Give the config format some way to resolve different "mergetypes" in one merge. Have some mechanism to configure default mergetypes for new files (or old files missing "mergetype"). Always do auto merge on server, but also support sending the "from" and "ancestor" files to client for use by an optional interactive client-side merge algorithm. It should be possible to configure the client to automatically invoke such a thing when possible. I also have vague ideas of implementing a generic, bidirection communications "tunnel" for the client/server protocol, that could be used by things server-side interactive merge tools, or an rsync-based file transfer subsystem. G2. Add a per-version "keywordexpansion" newphrase to the RCS file that takes precedence over the main file setting. If you delete and readd a particular file name with a totally different type, the old and new files can have different keywordexpansion modes... G3. Add a "conditionally override -k" to checkout and/or update, that can be generically instructed to only override -k in certain ways (for example, never override -kb). G4. Add a "meta-operation log" newphrase to the RCS format that records details about various meta-operations like adding and removing tags. Theoretically, if a tag is erroneously removed, this could be used to figure out specifically how to fix it. G5. Patch management. I've got various ideas for making it so CVS could help automate tracking multiple independent "changesets", provide me with a combined view of all the "changesets", yet being able to "commit" additional changes to a specific changeset only. I get the impression that BitKeeper may have some support for this, but I don't know a whole lot about how it does it. G6. Alternate/better rename idea: The files in a directory would be controlled by a special "CVS/dir,v" file in the repository, where each version of CVS/dir is a list of which RCS files contribute to that version of the directory (ideally it would only reference local RCS files, but might sometimes reference ones in other directories). Carefully maintain backwards compatibility in the absense of a CVS/dir,v file. (I've got more ideas, like how to handle hard links cleanly; this is just a summary. See the wishlist link above.) This is conceptually cleaner then recording "merge history" between different files, but the need to maintain backwards compatibility and the significant internal redesign of CVS that would likely be needed makes it so this probably isn't practical. It might be best to drop client/server compatibility for this, and overhaul a large chunk of the CVS code. G7. One specific instance of a specialize multi-system merge tool: Be able to merge among multiple sandboxes tied to the same repository (usefull for things like trying changes on both UNIX and NT before committing.). See HERE for a tool that kind of supports this, but doesn't really manage merging... ================================================ ================================================ ================================================ Possible User Interface Changes: Some of this is very tentative. 1. "cvs update -j [-j]" will record in the sandbox that a merge occured 2. "cvs update -j" will by default use the smarter algorithm for finding a the common ancestor. But you can still get the old algorithm with some new option (--ancestor-algorthm=treeOnly?) 3. "cvs update -C", or any time update replaces a missing file, it will clear out any merge history info stored in the sandbox that mentions the CHECKEDOUT version of that file. 4. What if 'cvs update" notices that a file is unmodified, but is marked as having been merged? Should it warn the user? Clear out the merge info automatically (probably not)? Report it as modified (perhaps with a little m?)? 5. "cvs commit" will copy the sandbox merge history at the same time it is committing the file. 6. What if 'cvs commit' notices an unmodified file that is marked as having been merged? Should it abort the merge (unless some kind of override option is set)? Should it drop the merge info (probably not)? Should it write a new (unchanged) version and point the merge history at that? Should it just add the merge info to the file, without adding new versions (probably best)? 7. "cvs status" should probably mention any sandbox merge history state somehow. 8. "cvs log" should probably add a section to report merge history. Should it be there by defualt? 9. Add a "cvs arrow" command with sub-commands for adding, removing, and listing. Include an option to control weather to save it in the sandbox (for a later commit), or send it directly to the RCS file. Possibly just go with "cvs log" changes for listing, but probably not. Possibly enhance "cvs admin" with new options for add/remove, but probably no. 10. Maybe or maybe not: A "cvs decode" command that reads the output of other commands (like "cvs log"), decodes the output, and then outputs a specificaly asked for piece of information out of it. This could allow scripts to easily and robustly parse the output of a command. It would often be faster then adding detailed queries to ("cvs log") [avoid repeated overhead of client/server and reading a large RCS file], but not as fast as a program internally parsing the output. It could also be used to convert "escaped" strings back into pure binary data (see 8-bit clean discussion lower down). 11. Maybe give "cvs tag" some options for tagging versions based on searching the version tree? Perhaps "tag the common ancestor that would be used for a specified merge", or "find the a merge entry that matches a query, and tag the from, to, or ancestor revision mentioned in that merge". (FUTURE, not part of initial patch.) #HERE: Expand on the above? ================================================ ================================================ ================================================ Theoretical representation for merges. The representation I intend to use will store the version numbers of the "from", "to" and "common ancestor" versions of the file for every merge ever done involving that file. Also arbitrary name value pairs (date, comment, (see below).). Names will be match the RE "[A-Za-z_][A-Za-z_0-9]*" (like C identifiers; or a subset of valid RCS "id"s. ##### HERE: dropping default contributer to a version. Alternate locators. MergeName (if the user wants to give a big, important merge a name for some reason?) Required: from - The version number of the source of the merge. to - The version number of the result of the merge. ancestor - The common ancestor that was used for the merge. Optional ignoreParent - Normally defaults to 0 (though mayb the default should be 1 when it is dead?) When 1, don't consider the parent of the "to" version to be to be a direct ancestor of the "to" version. comment - If the user wants a comment to go with the merge... id - Some sort of name for the merge (might be queried for...) HERE: Something to say "treat this as the direct ancestor". A user supplied comment for the ", "id", "date", "user", ... ##### Version numbers deserve some clarification: 1. If it looks like an RCS version number, then it refers to a version of the same file. 2. If it is "CHECKEDOUT", it refers to the version currently checked out and will be bound to a specific version when the file is committed. 2. If it is anything else, then it refers to something external, in another file, another repository, or even a completely different version control system. Naming convention for external versions is something like "scheme:SCHEME_SPECIFIC_INFO". Model the after URLs: access method/what kind of repository, where the repository is, what file within that repository, what version of that file. Issues: - Relative references. If refering to another file in the same repository, robustness would suggest that a relative path is best (then you can relocate the repository without breaking internal references). But that complicates checking if multiple things represent the same file. Internally, it would probably need to transiently normalize all references to absolute references... - UUIDs vs "current location" vs both to find files... Clearcase at least uses UUIDs to refer to files. But the ability to look things up by UUID may be missing from the UI (I'm not sure), and certainly trying to manually read a history using UUID references would be difficult. But if you try to use "current location", how would tools handle it if it moved? One desirable property of external version references is that they be normalized (or easily/generically normalizable) so that the string can uniquely ID a node in the version graph without needing to know how to decod the string. That doesn't work so well if you use CCase rename capabilities. A possibility is to use UUIDs in the from and to properties, and have seperate path_from/path_to settings to record the expected pathname at that location... HERE: Expand on this some more. ##### String representation also deserves some attention. I think it is worth the time to carefully consider how to encode strings so they can store any arbitrary 8 bit data. (Although I don't think it is a good idea to actually use that capability...) 1. Characters that need to be escaped probably include [\n\0\r"@], anything that is "non-printable", and whatever character is used for escaping. 2. Unicode: Essentially ignore it. Just store a stream of bytes that is "typically" interpreted as IEEE (HERE) like the rest of CVS. If someone wants to store unicode, the 8-bit-cleanliness means they can use any encoding they want, though they may need to intercept and massage the output... 3. Three input encoding schemes are: - Fully encode a supplied, unencoded byte stream. When getting data directly from the user on a clean channel, have this as an option selectable by command line flags... - Partial encoding: Except for the escape character, escape all special characters. The escape character is left as is, as long as it is part of a valid escape sequence. The idea here is to allow a defensive programming strategy where we always partially encode any string coming from an external source, even if it is *supposed* to already be encoded. If it really is encoded, the partial encoding does nothing, but if it somehow is *not* encoded, partial encoding it can protect you... - Pre-encoded (no encoding): There should never be a reason to assume the string is properly pre-encoded. Use partial encoding instead. 4. Output encodings: - Internally (including in RCS files), the string is stored and manipulated in encoded format. - Normally (and especially if output as part of a larger construct), output strings in encoded format. - Perhaps a "cvs decode" command that takes an encoded string (either on standard input or on the command line) and outputs the original binary string on standard output would be useful? 4. Newlines are an issue: - When decoding, be able to ignore unescaped newlines. - For some forms of output, you don't want newlines at all. (easier parsing by scripts...) - For other forms of output, adding ignored real newlines next to the encoded newlines can make the output more readable but harder to parse. - Can't be relied upon anyways ["\n" vs "\r\n" are often hosed up by whatever tools you happen to be using]. 5. Options: - C style escapes. "\n\0" - quoted-printable style escapes. "=0a=00" - XML/HTML style escapes. "
�" HERE: correct? -------------------- Alternate representation: (I'm not planning on using this representation.) - Subversion gives each version a list of all version deltas that have been accounted for in that version. I suspect my representation can theoretically represent about the same information as the representation above (with reasonable conversions between the representations), but I kind of like a representation that closely resembles the input (the actual merges performed). - I believe clearcase is more limited: I think it only remembers the from and to versions of a merge, not the common ancestor. On the other hand, the initial common ancestor search algorithm I have in mind is probably going to only use the "from" and "to" versions of past merges. But storing more details would allow future enhancements to be smarter. ================================================ ================================================ ================================================ Specifics of storing merges in sandbox, repository, and client/server protocol. It might be useful to use the same format for more then one thing (for example, output a fragment of an RCS file in a CVS/mergeHistory file in the sandbox). ---------------- RCS: (For context, see "man rcsfile") Add a newphrase "merges" to the "delta" section of the RCS file, as shown in the following addition to the RCS file grammer: <admin> ::= # [snip] ... merges { <mergeInfo> { : <mergeInfo> }* } ; { <newphrase> }* <mergeInfo> ::= { <infoItemName> <infoValueValue> }+ <infoItemName> ::= <id> <infoItemValue> ::= <string> | <id> | <num> Alternatives: - I considerred storing the merges to (or from?) version X in the "delta" section for version X, but it doesn't seem like a properly normalized representation (possibilities of inconsistencies, etc). It also doesn't eliminate the issue of storing multiple merges in one section of the file, with each merge needing multiple name/value pairs. - I also considerred using one newphrase per merge, but that would require multiple instances of the same newphrase in one section of the RCS file, and: a. There are no provisions for that in the existing format. (See how locks and symbols (tags) are handled...) b. I wouldn't be suprised if some RCS file parsers can't manage multiple instances of a single newphrase. Questions: - Is there some semi-official registrar of newphrases that people have added to RCS, to make sure I don't conflict with any body else's extensions? - Does anybody know of any other merge history extensions anyone has added to RCS that I should try to be able to interoperate with? ---------------- Sandbox: I'm leaning towards using a new admin file (CVS/mergeHistory) that just stores the relevent data fragment from one of the other representations. I'll use whichever one seems easiest to factor out. Ooops: Needs to store a fragment per file, with additional info to identify the file that each fragment is for. ---------------- Client/server protocol: - I'm thinking of making sure there is no line length limit and that the encoded form of the strings don't have any newlines, and then just passing each merge in a form like: "mergeHist ${var}=\"${value}\" ${var}="${value}" ...\n" This will be recognized both client->server and server->client. The file it applies to will be derived from context. HERE: more details ---------------- Command line: When a name/value pair needs to be specified on the command line, each name/value pair will specified as something like: --set NAME=VALUE # Fully encode value --setEnc NAME=ENCODED_VALUE # Partially encode value. --setD NAME=DATE # Use version number that goes with DATE. --setR NAME=TAG # Use version number that goes with TAG. --setRD NAME=BRANCH:DATE # Use version number for date on branch. Of course, cvs update -j will automatically set all required names, but they can be overridden or more added if desired. ---------------- CVS log output: HERE merge: name="value" name="value" name="value" merge: name="value" name="value" name="value" Values will appear in encoded format, no newlines. It is intended to be easy to pull out the values with simple RE based parsers. (Nothing tricky for embedded newlines, quotes, etc.) ================================================ ================================================ ================================================ Internal design: - A new file will be created to house the common ancestor search algorithm, and other merge history related functions. In new code I'll try to aim for the reentrancy desire expressed in the HACKING file. - Things like I/O routines for various formats may go in existing files (for those I/O formats) or in the new file, depending on where it seems to fit best. - Obviously changes to specific cvs subcommands will go in the file for that subcommand. - New cvs subcommands (cvs mergeHistory or something) will get their own files (for high level code); lower level code may go into the generic new file with the search algorithm... - I think the RCS code can read and write newphrases already, so I don't need to do much to it (except verify that it can handle newphrases.) Search Algorithm: - First, create a combined directed graph from the version tree and the "from" and "to" versions of all historical merges (both from the RCS file and from the sandbox admin files). - Now search the graph for the "closest" common predecessor. This will likely be a relatively small part of the patch, that will be easily changed or replaced as desired, but basic dificulties are: - Define "closest": - Number of transitions? - Sum of some kind of transition weights? - Consider version tree common ancestor A and a nearer ancestor B that happens to be common via a long series of merges and changes through several different branches. Even though B likely has more transitions, it is still probably a better choice then A for the common ancestor. - Only consider direct ancestors as candidates; choose the closest one that is any kind of ancestor of the other version? - External versions: - It probably can't use an external version as any contributer to the merge (although future changes (not part of this initial proposal) might implement the ability to ask an external plugin for a copy of the indicated external version...). - The simplest thing is to ignore transitions involving external versions completely. They are only for use by external tools. - On the other hand, perhaps it would be useful to be able to transition through external versions during the search for the best internal common ancestor. ================================================ ================================================ ================================================ Splitting Up the Patch Into Subpatches: - Smaller patches are often desired. - Prerequisite bugfixes found during development will be split off. //HERE: Reorganize: Application Options: 1. Build it as one big monolithic patch. It will still be developed in the lean order, but trying to organize the patch in that way is not practical, unless pieces of the patch are regularly applied to the official source. 2. Build as one big patch, but prior to application, attempt to seperate it as much as possible into distinct subpatches for easier chewing. //HERE: What subpatches might be possible. 3. Submit incremental patches regularly, which someone will apply nearly as often as I generate them. //HERE: incremental patches that might be submitted. How to split up a megapatch: - The main patch would need to include the low level search algorithm; read/write routines for various protocols/file formats; and enhancements to the update.c and commit.c [This would be the bulk of the changes regardless...] - "cvs mergeHistory" and other new subcommands could probably be seperate, if desired. [Though it doesn't seem likely you would want to apply the main patch but not this one...] - Other loosely related changes would also be seperate. [depending on how loosely and/or how embedded they are in the same parts of the code as the main patch...] A tentative development strategy is listed below. If regular small merges into the source tree can be arranged, incremental patches might be applied regularly during this plan: - Make any fixes to make sure that it can read/write RCS files with arbitrary newphrases without messing them up. Also do some tests of RCS itself... If problems are found, come up with transition strategies when access may be made when working with old versions of CVS and/or RCS... - Add an initial UI facility to add/remove merge history entries directly to the RCS file (local only, for now). Put some effort into a good interface up front, but it is subject to change during development. - Add in sandbox admin file capabilities to temporarily remember merge history entries, and make "commit" copy those entries into the RCS file. (Initial testing will depend on manually adding those entries to the sandbox admin files.) [Initially only support direct repository access.] - Extend commit to handle merge histroy over the client/server protocol. - When a merge is done, have "cvs update" record a merge history entry in the sandbox admin files. - Have "cvs update -j" (single -j) use a merge history aware algorithm for deciding the common ancestor to use. - Possibly experiment with different algorithms, maybe even allowing plugin algorithms. - Go through the new UIs, clean them up, and finalize them. If not already done, support: - Directly changing merge history immediately. - Scheduling merge history changes for the next commit. - Arguments to update to support optional merge history attributes. - Arguments to update to control algorithm to use for searching for common ancestor: (at least 2 algorithms: the new default merge-history-aware algorithm, and the old (current) algorithm) Finish up the test cases and documentation. And do any final cleanup of the code changes. - Look into optional, loosely related enhancements (other merge history mechanisms, UI for encoding/decoding strings, etc). This is tentative. Test cases should be developed fairly soon (facilitate development). Documentation may take a bit longer then test cases (initially somewhat fluid interface, and docs aren't much use during initial development), but should not be delayed too long. My impression is that some open source owners prefer to merge things using this strategy. But it definately takes regular and consistent attention by someone with commit access to the tree, plus at least some degree of faith in the long term strategy: merging things that aren't immediately useful (having commit copy history entries from the sandbox to the RCS file is not useful when there is not yet an automated way to get those entries into the sandbox in the first place...) HERE: somewhere: HERE: If tag not found internally, search externally referenced (by merge history) items. HERE: Extended tags reference external versions? ================================================ ================================================ ================================================ GARBAGE OBSOLETE ### I don't like this because it means either redundant info ### (a "to" value vs the delta number) and the corresponding ### potential for inconsistencies, or it means that one attribute ### of the a merge record comes from outside the record. #<delta> ::= <num> # # [snip] ... # next { <num> }; # merges <mergeInfo> { : <mergeInfo> }* ; # { <newphrase> }* ######################### OR: # (One newphrase per merge) But the existing grammer doesn't # use multiple same-named phrases at all... <admin> ::= # [snip] ... { merge { <nameValue> }+ ; }* <nameValue> ::= <id> : <value> <value> ::= <id> | <num> | <string> The reluctance to incorporate patches may be overreaction to past problems: Burned by the PreservePermissions stuff, and as far as I can tell, nothing major has been merged in since then (just small bugfixes). As direct experience, I've helped with the "advisory lock patch" (HERE: Link). As far as I know it meets all the requirements mentioned in HACKING, but no one is jumping to incorporate it.