LEP: Changesets for unified working tree change tracking with first-class conflicts#74
LEP: Changesets for unified working tree change tracking with first-class conflicts#74mjansson wants to merge 2 commits into
Conversation
Signed-off-by: Mattias Jansson <mattias.jansson@epicgames.com>
|
That's a very good proposal, and the way changesets are designed here solves so many Perforce issues, it is Unreal it can be done. Nice! One nitpick: I think the proposal should explicitly state that binary or otherwise unmergeable files are changeset-membership atomic: a path may belong to at most one attached changeset at a time, hunk-level reassignment is unavailable, and any competing change to the same path is treated as an inter-changeset conflict requiring detach, reassign, or revert. The other thing is I'm not entirely sure whether there already is a mechanism for classifying files as unmergeable, or how to tell it from the mergeable one. |
|
Great read and excellent proposal! This is very much in line with the designs we had internally discussed. It's a great step in moving Lore beyond both Git and Perforce in terms of UX. I wrote down some thoughts. Some were answered later in the proposal, but I left them in, in case it's useful for the proposal text itself. Hopefully this wall of text is a useful feedback format. Let me know if I should format this differently.
This paragraph confuses me. I think I understand the concept, but the words tripped me up (which is especially notable as the rest of the writing is excellent!). I think partially because it does not clearly explain what "base" is. I read "The base is [..] a changeset's latest", which seems incorrect :D I am also a bit confused about the verbs. I think I'm understanding this correctly: But it seems like
We often find ourselves asking a colleague to 'unshelve' our changes on their machine. For example for soak testing, checking if it works in combination with their in-progress work, etc. How would this work with the current design? I would imagine this would be implemented as a higher-level feature; an additional changeset local to that developer, with metadata referring back to the original changeset and the latest sync point. Edit: I see this was addressed later. Having the link back to the original changeset would be cool to easily update from the original author. But that is an additional feature request.
Often when working on larger changes, I find myself wanting to split things up into several reviews where the later reviews are dependent on the earlier ones. With the traditional PR workflow, this is quite painful, as any feedback on the first review will require manual rebasing of all the later reviews. My holy grail for this workflow is GitButler, which allows frictionless moving of hunks between commits, reviews, and branches. How would this work for Changesets?
In our own design of this same idea, we did allow for conflicts between changesets, but it imposed a strict ordering between the changesets. This would then enable stacked branches or the attaching of someone else's changeset even though it had overlap with your own. This may cause technical complexities that we didn't forsee, but it seemed useful to us.
I see that this is explicitly decided against, so offering this just as food-for-thought. As a related question, is it possible to assign files to a changeset when marking them as dirty? This is a common technique used in Perforce to track and group e.g. build artifacts during automated processes that run on the local machine.
It is unclear to me where hashing/content upload happens now. Does that become part of Additionally, I think users would want named snapshots. "This is where I tried the crazy refactor"; "Here it still builds"; etc.
It sounds like conflicts are made 'first class' by storing the diff3 markers in the file. How does that work for binary files? We consider binary files mergable in some cases, but diff3 markers won't work for them. It would be a shame if Lore would treat binary conflicts as second-class. Cheesy question: and what if I want to commit diff3 markers? For example for a unit test of a merge algorithm...
Am I misunderstanding this, or does this mean Or is the expectation that you commit a changeset onto a temporary branch and then merge that branch into main? We would prefer to work closer to Trunk-based development, and it sounds like changesets are not compatible with that. It's not obvious to me how committing a changeset is different from current commits. But then I'm not fully sure if the current commits can commit to non-latest.
We often find ourselves syncing only a small set of files to the latest version for specific workflows. I believe Lore technically already supports this using
Super unimportant, but I was curious: will that become a problem? Does it get reset at some point? |
|
@ahaczewski Agreed - unmergeable files should be forced to a single changeset, I will add that. The support for and details on how to mark and/or determine if files are unmergeable is orthogonal to this proposal - only that we will depend on such a feature to be added. |
|
@Stardidi thanks for the feedback! I will clarify the terminology a bit, agree that it's a bit confusing as I'm trying to shoehorn the existing terminology into this concept. In short, "commit" is the action of squashing a changeset and commit that as a single revision on the target branch, "dirty" is the action of notifying lore that a file has changed and allow lore to record that change into the changeset (basically a revision on the changeset history), "stage" is the action of selecting a file from a changeset to be included when committing that changeset. On sharing changesets, my idea was that this is what branches is for. If you want to collaborate with others, you create a branch for your shared work and commit your changeset(s) to that branch. The other collaborators are most likely not interested in all your incremental changes (basically auto-saves) on your changeset history lines, but the resulting state at some well known point when you're happy with the state.
Yeah, I see changesets mostly as your local organisation and incremental recording of your work as you iterate (automatic backups, persistent undo stack etc), then branches as the actual committed state for collaboration and review. So ideally stacked PRs/CRs is something we build on top of branches, and changesets is how you can easily partition, record and safely manage your work until you commit it to a branch for review. Making it easy to attach/detach changesets to move work between branches, and easily move hunks and files between changesets is what makes local work easy to manage.
Interesting, maybe we could have a separate discussion around that at some point. I think it could be added as an extension to this base idea though, and not as an added complexity at this point?
My initial proposal floated around internally actually had the concept of an active changeset, but it was deemed an added complexity for little benefit and thus removed. I think it can also be added as an additional feature at a later stage, to reduce complexity of the initial proposal and implementation.
As default it happens when a file is dirtied and recorded in the changeset history line - this is the reconciliation with the UEFN personal backups. When you, a file watcher or an editor integration marks a file as dirty (modified) Lore will commit it on the changeset history and thus chunk/hash, store and optionally upload it. That is why I think it is important to have good support for opt in/out of this for some file types. If you iterate on a large binary file you might not want to have automatic backups on every save. Agree on size being a blunt tool for this, but it's an easy first start. Interested in exploring the "debounce/back-off/gradual retention approach" as you mention.
Exactly, lore can commit files in conflict in the changeset history. Text files will carry the conflict markers, and binary files will carry the mine/theirs/base variants - since we're content addressed, those are not actually taking up storage or have any cost of recording, it's just hash addresses. So binary files will also be supported in this recorded conflict handling. |
Summary
This proposal lets a single Lore instance carry several ongoing streams of work in one working tree. It introduces the changeset: a lightweight set of changes recorded as its own strictly linear line of history — a line that reduces to a single net change — layered over the branch the developer is on.
A developer can attach a changeset to materialize its changes in the working tree, detach it to park them off-disk, keep several attached at once, let an editor or tool checkpoint work into one continuously, and move a changeset as a unit — committing it onto the current branch, or switching to another to land it there.
One mechanism thereby covers a wide range of needs that today are unmet or served only by separate, partial workarounds: automatic personal backup of work, several work streams in flight at once, carrying work across branch switches, deferring a conflict to resolve in an orderly fashion rather than head-on, toggling changes on and off to test combinations, keeping long-lived debug or utility changes switchable, and picking up in-progress work on a developer's other machines.
Concretely, it folds three mechanisms Lore has today — dirty-file tracking, staging as the recording of commit intent, and the personal backup branch already used in UEFN — into a single concept, and on that foundation adds what Lore has never had: multiple parallel changesets in one working tree and first-class conflict handling.