{"thread":{"id":"50058","subject":"[PATCH v3] technical doc: add a design doc for the evolve command","startedAt":"2018-12-18T16:47:03Z","lastAt":"2018-12-18T16:47:03Z","messageCount":1,"participants":["sxenos@google.com"],"isPatch":true,"patchVersion":3,"patchTotal":null},"messages":[{"id":"365538","messageId":"20181218164612.233602-1-sxenos@google.com","threadId":"50058","inReplyTo":null,"subject":"[PATCH v3] technical doc: add a design doc for the evolve command","fromName":"","fromEmail":"sxenos@google.com","sentAt":"2018-12-18T16:46:14Z","receivedAt":"2018-12-18T16:47:03Z","isPatch":true,"sender":{"key":"sxenos@google.com","avatar":null},"body":"From: Stefan Xenos <sxenos@google.com>\n\nThis document describes what a change graph for\ngit would look like, the behavior of the evolve command,\nand the changes planned for other commands.\n\nSigned-off-by: Stefan Xenos <sxenos@google.com>\n---\n Documentation/technical/evolve.txt | 1034 ++++++++++++++++++++++++++++\n 1 file changed, 1034 insertions(+)\n create mode 100644 Documentation/technical/evolve.txt\n\ndiff --git a/Documentation/technical/evolve.txt b/Documentation/technical/evolve.txt\nnew file mode 100644\nindex 0000000000..7967c73e5d\n--- /dev/null\n+++ b/Documentation/technical/evolve.txt\n@@ -0,0 +1,1034 @@\n+Evolve\n+======\n+\n+Objective\n+=========\n+Create an \"evolve\" command to help users craft a high quality commit history.\n+Users can improve commits one at a time and in any order, then run git evolve to\n+rewrite their recent history to ensure everything is up-to-date. We track\n+amendments to a commit over time in a change graph. Users can share their\n+progress with others by exchanging their change graphs using the standard push,\n+fetch, and format-patch commands.\n+\n+Status\n+======\n+This proposal has not been implemented yet.\n+\n+Background\n+==========\n+Imagine you have three sequential changes up for review and you receive feedback\n+that requires editing all three changes. We'll define the word \"change\"\n+formally later, but for the moment let's say that a change is a work-in-progress\n+whose final version will be submitted as a commit in the future.\n+\n+While you're editing one change, more feedback arrives on one of the others.\n+What do you do?\n+\n+The evolve command is a convenient way to work with chains of commits that are\n+under review. Whenever you rebase or amend a commit, the repository remembers\n+that the old commit is obsolete and has been replaced by the new one. Then, at\n+some point in the future, you can run \"git evolve\" and the correct sequence of\n+rebases will occur in the correct order such that no commit has an obsolete\n+parent.\n+\n+Part of making the \"evolve\" command work involves tracking the edits to a commit\n+over time, which is why we need an change graph. However, the change\n+graph will also bring other benefits:\n+\n+- Users can view the history of a change directly (the sequence of amends and\n+  rebases it has undergone, orthogonal to the history of the branch it is on).\n+- It will be possible to quickly locate and list all the changes the user\n+  currently has in progress.\n+- It can be used as part of other high-level commands that combine or split\n+  changes.\n+- It can be used to decorate commits (in git log, gitk, etc) that are either\n+  obsolete or are the tip of a work in progress.\n+- By pushing and pulling the change graph, users can collaborate more\n+  easily on changes-in-progress. This is better than pushing and pulling the\n+  changes themselves since the change graph can be used to locate a more\n+  specific merge base, allowing for better merges between different versions of\n+  the same change. \n+- It could be used to correctly rebase local changes and other local branches\n+  after running git-filter-branch.\n+- It can replace the change-id footer used by gerrit.\n+\n+Goals\n+-----\n+Legend: Goals marked with P0 are required. Goals marked with Pn should be\n+attempted unless they interfere with goals marked with Pn-1.\n+\n+P0. All commands that modify commits (such as the normal commit --amend or\n+    rebase command) should mark the old commit as being obsolete and replaced by\n+    the new one. No additional commands should be required to keep the\n+    change graph up-to-date.\n+P0. Any commit that may be involved in a future evolve command should not be\n+    garbage collected. Specifically:\n+    - Commits that obsolete another should not be garbage collected until\n+      user-specified conditions have occurred and the change has expired from\n+      the reflog. User specified conditions for removing changes include:\n+      - The user explicitly deleted the change.\n+      - The change was merged into a specific branch.\n+    - Commits that have been obsoleted by another should not be garbage\n+      collected if any of their replacements are still being retained.\n+P0. A commit can be obsoleted by more than one replacement (called divergence).\n+P0. Must be able to resolve divergence (convergence).\n+P1. Users should be able to share chains of obsolete changes in order to\n+    collaborate on WIP changes.\n+P2. Such sharing should be at the user’s option. That is, it should be possible\n+    to directly share a change without also sharing the file states or commit\n+    comments from the obsolete changes that led up to it, and the choice not to\n+    share those commits should not require changing any commit hashes.\n+P2. It should be possible to discard part or all of the change graph\n+    without discarding the commits themselves that are already present in\n+    branches and the reflog.\n+P2. Provide sufficient information to replace gerrit's Change-Id footers.\n+\n+Similar technologies\n+--------------------\n+There are some other technologies that address the same end-user problem.\n+\n+Rebase -i can be used to solve the same problem, but users can't easily switch\n+tasks midway through an interactive rebase or have more than one interactive\n+rebase going on at the same time. It can't handle the case where you have\n+multiple changes sharing the same parent when that parent needs to be rebased\n+and won't let you collaborate with others on resolving a complicated interactive\n+rebase. You can think of rebase -i as a top-down approach and the evolve command\n+as the bottom-up approach to the same problem.\n+\n+Several patch queue managers have been built on top of git (such as topgit,\n+stgit, and quilt). They address the same user need. However they also rely on\n+state managed outside git that needs to be kept in sync. Such state can be\n+easily damaged when running a git native command that is unaware of the patch\n+queue. They also typically require an explicit initialization step to be done by\n+the user which creates workflow problems.\n+\n+Mercurial implements a very similar feature in its EvolveExtension. The behavior\n+of the evolve command itself is very similar, but the storage format for the\n+change graph differs. In the case of mercurial, each change set can have one or\n+more obsolescence markers that point to other changesets that they replace. This\n+is similar to the \"Commit Headers\" approach considered in the other options\n+appendix. The approach proposed here stores obsolescence information in a\n+separate metacommit graph, which makes exchanging of obsolescence information\n+optional.\n+\n+Mercurial's default behavior makes it easy to find and switch between\n+non-obsolete changesets that aren't currently on any branch. We introduce the\n+notion of a new ref namespace that enables a similar workflow via a different\n+mechanism. Mercurial has the notion of changeset phases which isn't present\n+in git and creates new ways for a changeset to diverge. Git doesn't need\n+to deal with these issues, but it has to deal with picking an upstream branch as\n+a target for rebases and protecting obsolescence information from GC. We also\n+introduce some additional transformations (see obsolescence-over-cherry-pick,\n+below) that aren't present in the mercurial implementation.\n+\n+Semi-related work\n+-----------------\n+There are other technologies that address different problems but have some\n+similarities with this proposal.\n+\n+Replacements (refs/replace) are superficially similar to obsolescences in that\n+they describe that one commit should be replaced by another. However, they\n+differ in both how they are created and how they are intended to be used.\n+Obsolescences are created automatically by the commands a user runs, and they\n+describe the user’s intent to perform a future rebase. Obsolete commits still\n+appear in branches, logs, etc like normal commits (possibly with an extra\n+decoration that marks them as obsolete). Replacements are typically created\n+explicitly by the user, they are meant to be kept around for a long time, and\n+they describe a replacement to be applied at read-time rather than as the input\n+to a future operation. When a replaced commit is queried, it is typically hidden\n+and swapped out with its replacement as though the replacement has already\n+occurred.\n+\n+Git-imerge is a project to help make complicated merges easier, particularly\n+when merging or rebasing long chains of patches. It is not an alternative to\n+the change graph, but its algorithm of applying smaller incremental merges\n+could be used as part of the evolve algorithm in the future.\n+\n+Overview\n+========\n+We introduce the notion of “meta-commits” which describe how one commit was\n+created from other commits. A branch of meta-commits is known as a change.\n+Changes are created and updated automatically whenever a user runs a command\n+that creates a commit. They are used for locating obsolete commits, providing a\n+list of a user’s unsubmitted work in progress, and providing a stable name for\n+each unsubmitted change.\n+\n+Users can exchange edit histories by pushing and fetching changes.\n+\n+New commands will be introduced for manipulating changes and resolving\n+divergence between them. Existing commands that create commits will be updated\n+to modify the meta-commit graph and create changes where necessary.\n+\n+Example usage\n+-------------\n+# First create three dependent changes\n+$ echo foo>bar.txt && git add .\n+$ git commit -m \"This is a test\"\n+created change metas/this_is_a_test\n+$ echo foo2>bar2.txt && git add .\n+$ git commit -m \"This is also a test\"\n+created change metas/this_is_also_a_test\n+$ echo foo3>bar3.txt && git add .\n+$ git commit -m \"More testing\"\n+created change metas/more_testing\n+\n+# List all our changes in progress\n+$ git change list\n+metas/this_is_a_test\n+metas/this_is_also_a_test\n+* metas/more_testing\n+metas/some_change_already_merged_upstream\n+\n+# Now modify the earliest change, using its stable name\n+$ git reset --hard metas/this_is_a_test\n+$ echo morefoo>>bar.txt && git add . && git commit --amend --no-edit\n+\n+# Use git-evolve to fix up any dependent changes\n+$ git evolve\n+rebasing metas/this_is_also_a_test onto metas/this_is_a_test\n+rebasing metas/more_testing onto metas/this_is_also_a_test\n+Done\n+\n+# Use git-obslog to view the history of the this_is_a_test change\n+$ git log --obslog\n+93f110 metas/this_is_a_test@{0} commit (amend): This is a test\n+930219 metas/this_is_a_test@{1} commit: This is a test\n+\n+# Now create an unrelated change\n+$ git reset --hard origin/master\n+$ echo newchange>unrelated.txt && git add .\n+$ git commit -m \"Unrelated change\"\n+created change metas/unrelated_change\n+\n+# Fetch the latest code from origin/master and use git-evolve\n+# to rebase all dependent changes.\n+$ git fetch origin master\n+$ git evolve origin/master\n+deleting metas/some_change_already_merged_upstream\n+rebasing metas/this_is_a_test onto origin/master\n+rebasing metas/this_is_also_a_test onto metas/this_is_a_test\n+rebasing metas/more_testing onto metas/this_is_also_a_test\n+rebasing metas/unrelated_change onto origin/master\n+Conflict detected! Resolve it and then use git evolve --continue to resume.\n+\n+# Sort out the conflict\n+$ git mergetool\n+$ git evolve --continue\n+Done\n+\n+# Share the full history of edits for the this_is_a_test change\n+# with a review server\n+$ git push origin metas/this_is_a_test:refs/for/master\n+# Share the lastest commit for “Unrelated change”, without history\n+$ git push origin HEAD:refs/for/master\n+\n+Detailed design\n+===============\n+Obsolescence information is stored as a graph of meta-commits. A meta-commit is\n+a specially-formatted merge commit that describes how one commit was created\n+from others.\n+\n+Meta-commits look like this:\n+\n+$ git cat-file -p <example_meta_commit>\n+tree 4b825dc642cb6eb9a060e54bf8d69288fbee4904\n+parent aa7ce55545bf2c14bef48db91af1a74e2347539a\n+parent d64309ee51d0af12723b6cb027fc9f195b15a5e9\n+parent 7e1bbcd3a0fa854a7a9eac9bf1eea6465de98136\n+author Stefan Xenos <sxenos@gmail.com> 1540841596 -0700\n+committer Stefan Xenos <sxenos@gmail.com> 1540841596 -0700\n+parent-type c r o\n+\n+This says “commit aa7ce555 makes commit d64309ee obsolete. It was created by\n+cherry-picking commit 7e1bbcd3”.\n+\n+The tree for meta-commits is always the empty tree whose hash matches\n+4b825dc642cb6eb9a060e54bf8d69288fbee4904 exactly, but future versions of git may\n+attach other trees here. For forward-compatibility fsck should ignore such trees\n+if found on future repository versions. Similarly, current versions of git\n+should always fill in an empty commit comment and tools like fsck should ignore\n+the content of the commit comment if present in a future repository version.\n+This will allow future versions of git to add metadata to the meta-commit\n+comments or tree without breaking forwards compatibility.\n+\n+Parent-type\n+-----------\n+The “parent-type” field in the commit header identifies a commit as a\n+meta-commit and indicates the meaning for each of its parents. It is never\n+present for normal commits. It contains a space-deliminated list of enum values\n+whose order matches the order of the parents. Possible parent types are:\n+\n+- c: (content) the content parent identifies the commit that this meta-commit is\n+  describing.\n+- r: (replaced) indicates that this parent is made obsolete by the content\n+  parent.\n+- o: (origin) indicates that this parent was generated from the given commit.\n+- a: (abandoned) used in place of a content parent for abandoned changes. Points\n+  to the final content commit for the change at the time it was abandoned.\n+\n+There must be exactly one content or abandoned parent for each meta-commit and it is\n+always the first parent. The content commit will always be a normal commit and not a\n+meta-commit. However, future versions of git may create meta-commits for other\n+meta-commits and the fsck tool must be aware of this for forwards compatibility.\n+\n+A meta-commit can have zero or more replaced parents. An amend operation creates\n+a single replaced parent. A merge used to resolve divergence (see divergence,\n+below) will create multiple replaced parents. A meta-commit may have no\n+replaced parents if it describes a cherry-pick or squash merge that copies one\n+or more commits but does not replace them.\n+\n+A meta-commit can have zero or more origin parents. A cherry-pick creates a\n+single origin parent. Certain types of squash merge will create multiple origin\n+parents. Origin parents don't directly cause their origin to become obsolete,\n+but are used when computing blame or locating a merge base. The section\n+on obsolescence over cherry-picks describes how the evolve command uses\n+origin parents.\n+\n+A replaced parent or origin parent may be either a normal commit (indicating\n+the oldest-known version of a change) or another meta-commit (for a change that\n+has already been modified one or more times).\n+\n+The parent-type field needs to go after the committer field since git's rules\n+for forwards-compatibility require that new fields to be at the end of the\n+header. Putting a new field in the middle of the header would break fsck. \n+\n+The presence of an abandoned parent indicates that the change should be pruned\n+by the evolve command, and removed from the repository's history. The abandoned\n+parent points to the version of the change that should be restored if the user\n+attempts to restore the change.\n+\n+Changes\n+-------\n+A branch of meta-commits describes how a commit was produced and what previous\n+commits it is based on. It is also an identifier for a thing the user is\n+currently working on. We refer to such a meta-branch as a change.\n+\n+Local changes are stored in the new refs/metas namespace. Remote changes are\n+stored in the refs/remote/<remotename>/metas namespace.\n+\n+The list of changes in refs/metas is more than just a mechanism for the evolve\n+command to locate obsolete commits. It is also a convenient list of all of a\n+user’s work in progress and their current state - a list of things they’re\n+likely to want to come back to.\n+\n+Strictly speaking, it is the presence of the branch in the refs/metas namespace\n+that marks a branch as being a change, not the fact that it points to a\n+metacommit. Metacommits are only created when a commit is amended or rebased, so\n+in the case where a change points to a commit that has never been modified, the\n+change points to that initial commit rather than a metacommit.\n+\n+Changes are also stored in the refs/hiddenmetas namespace. Hiddenmetas holds\n+metadata for historical changes that are not currently in progress by the user.\n+Commands like filter-branch and other bulk import commands create metadata in\n+this namespace.\n+\n+Note that the changes in hiddenmetas get special treatment in several ways:\n+\n+- They are not cleaned up automatically once merged, since it is expected that\n+  they refer to historical changes.\n+- User commands that modify changes don't append to these changes as they would\n+  to a change in refs/metas.\n+- They are not displayed when the user lists their local changes.\n+\n+Obsolescence\n+------------\n+A commit is considered obsolete if it is reachable from the “replaces” edges\n+anywhere in the history of a change and it isn’t the head of that change.\n+Commits may be the content for 0 or more meta-commits. If the same commit\n+appears in multiple changes, it is not obsolete if it is the head of any of\n+those changes.\n+\n+Note that there is an exeption to this rule. The metas namespace takes\n+precedence over the hiddenmetas namespace for the purpose of obsolescence. That\n+is, if a change appears in a replaces edge of a change in the metas namespace,\n+it is obsolete even if it also appears as the head of a change in the\n+hiddenmetas namespace.\n+\n+This special case prevents the hiddenmetas namespace from creating divergence\n+with the user's work in progress, and allows the user to resolve historical\n+divergence by creating new changes in the metas namespace.\n+\n+Divergence\n+----------\n+From the user’s perspective, two changes are divergent if they both ask for\n+different replacements to the same commit. More precisely, a target commit is\n+considered divergent if there is more than one commit at the head of a change in\n+refs/metas that leads to the target commit via an unbroken chain of “obsolete”\n+parents.\n+\n+Much like a merge conflict, divergence is a situation that requires user\n+intervention to resolve. The evolve command will stop when it encounters\n+divergence and prompt the user to resolve the problem. Users can solve the\n+problem in several ways:\n+\n+- Discard one of the changes (by deleting its change branch).\n+- Merge the two changes (producing a single change branch).\n+- Copy one of the changes (keep both commits, but one of them gets a new\n+  metacommit appended to its history that is connected to its predecessor via an\n+  origin edge rather than an obsolete edge. That new change no longer obsoletes\n+  the original.)\n+\n+Obsolescence across cherry-picks\n+--------------------------------\n+By default the evolve command will treat cherry-picks and squash merges as being\n+completely separate from the original. Further amendments to the original commit\n+will have no effect on the cherry-picked copy. However, this behavior may not be\n+desirable in all circumstances.\n+\n+The evolve command may at some point support an option to look for cases where\n+the source of a cherry-pick or squash merge has itself been amended, and\n+automatically apply that same change to the cherry-picked copy. In such cases,\n+it would traverse origin edges rather than ignoring them, and would treat a\n+commit with origin edges as being obsolete if any of its origins were obsolete.\n+\n+Garbage collection\n+------------------\n+For GC purposes, meta-commits are normal commits. Just as a commit causes its\n+parents and tree to be retained, a meta-commit also causes its parents to be\n+retained.\n+\n+Change creation\n+---------------\n+Changes are created automatically whenever the user runs a command like “commit”\n+that has the semantics of creating a new change. They also move forward\n+automatically even if they’re not checked out. For example, whenever the user\n+runs a command like “commit --amend” that modifies a commit, all branches in\n+refs/metas that pointed to the old commit move forward to point to its\n+replacement instead. This also happens when the user is working from a detached\n+head.\n+\n+This does not mean that every commit has a corresponding change. By default,\n+changes only exist for recent locally-created commits. Users may explicitly pull\n+changes from other users or keep their changes around for a long time, but\n+either behavior requires a user to opt-in. Code review systems like gerrit may\n+also choose to keep changes around forever.\n+\n+Note that the changes in refs/metas serve a dual function as both a way to\n+identify obsolete changes and as a way for the user to keep track of their work\n+in progress. If we were only concerned with identifying obsolete changes, it\n+would be sufficient to create the change branch lazily the first time a commit\n+is obsoleted. Addressing the second use - of refs/metas as a mechanism for\n+keeping track of work in progress - is the reason for eagerly creating the\n+change on first commit.\n+\n+Change naming\n+-------------\n+When a change is first created, the only requirement for its name is that it\n+must be unique. Good names would also serve as useful mnemonics and be easy to\n+type. For example, a short word from the commit message containing no numbers or\n+special characters and that shows up with low frequency in other commit messages\n+would make a good choice.\n+\n+Different users may prefer different heuristics for their change names. For this\n+reason a new hook will be introduced to compute change names. Git will invoke\n+the hook for all newly-created changes and will append a numeric suffix if the\n+name isn’t unique. The default heuristics are not specified by this proposal and\n+may change during implementation.\n+\n+Change deletion\n+---------------\n+Changes are normally only interesting to a user while a commit is still in\n+development and under review. Once the commit has submitted wherever it is\n+going, its change can be discarded.\n+\n+The normal way of deleting changes makes this easy to do - changes are deleted\n+by the evolve command when it detects that the change is present in an upstream\n+branch. It does this in two ways: if the latest commit in a change either shows\n+up in the branch history or the change becomes empty after a rebase, it is\n+considered merged and the change is discarded. In this context, an “upstream\n+branch” is any branch passed in as the upstream argument of the evolve command.\n+\n+In case this sometimes deletes a useful change, such automatic deletions are\n+recorded in the reflog allowing them to be easily recovered.\n+\n+Sharing changes\n+---------------\n+Change histories are shared by pushing or fetching meta-commits and change\n+branches. This provides users with a lot of control of what to share and\n+repository implementations with control over what to retain.\n+\n+Users that only want to share the content of a commit can do so by pushing the\n+commit itself as they currently would. Users that want to share an edit history\n+for the commit can push its change, which would point to a meta-commit rather\n+than the commit itself if there is any history to share. Note that multiple\n+changes can refer to the same commits, so it’s possible to construct and push a\n+different history for the same commit in order to remove sensitive or irrelevant\n+intermediate states.\n+\n+Imagine the user is working on a change “mychange” that is currently the latest\n+commit on master, they have two ways to share it:\n+\n+# User shares just a commit without its history\n+> git push origin master\n+\n+# User shares the full history of the commit to a review system\n+> git push origin metas/mychange:refs/for/master\n+\n+# User fetches a collaborator’s modifications to their change\n+> git fetch remotename metas/mychange\n+# Which updates the ref remote/remotename/metas/mychange\n+\n+This will cause more intermediate states to be shared with the server than would\n+have been shared previously. A review system like gerrit would need to keep\n+track of which states had been explicitly pushed versus other intermediate\n+states in order to de-emphasize (or hide) the extra intermediate states from the\n+user interface.\n+\n+Merge-base\n+----------\n+Merge-base will be changed to search the meta-commit graph for common ancestors\n+as well as the commit graph, and will generally prefer results from the\n+meta-commit graph over the commit graph. Merge-base will consider meta-commits\n+from all changes, and will traverse both origin and obsolete edges.\n+\n+The reason for this is that - when merging two versions of the same commit\n+together - an earlier version of that same commit will usually be much more\n+similar than their common parent. This should make the workflow of collaborating\n+on unsubmitted patches as convenient as the workflow for collaborating in a\n+topic branch by eliminating repeated merges.\n+\n+Configuration\n+-------------\n+The core.enableChanges configuration variable enables the creation and update\n+of change branches. This is enabled by default.\n+\n+User interface\n+--------------\n+All git porcelain commands that create commits are classified as having one of\n+four behaviors: modify, create, copy, or import. These behaviors are discussed\n+in more detail below.\n+\n+Modify commands\n+---------------\n+Modification commands (commit --amend, rebase) will mark the old commit as\n+obsolete by creating a new meta-commit that references the old one as a\n+replaced parent. In the event that multiple changes point to the same commit,\n+this is done independently for every such change.\n+\n+More specifically, modifications work like this:\n+\n+1. Locate all existing changes for which the old commit is the content for the\n+   head of the change branch. If no such branch exists, create one that points\n+   to the old commit. Changes that include this commit in their history but not\n+   at their head are explicitly not included.\n+2. For every such change, create a new meta-commit that references the new\n+   commit as its content and references the old head of the change as a\n+   replaced parent.\n+3. Move the change branch forward to point to the new meta-commit.\n+\n+Copy commands\n+-------------\n+Copy commands (cherry-pick, merge --squash) create a new meta-commit that\n+references the old commits as origin parents. Besides the fact that the new\n+parents are tagged differently, copy commands work the same way as modify\n+commands.\n+\n+Create commands\n+---------------\n+Creation commands (commit, merge) create a new commit and a new change that\n+points to that commit. The do not create any meta-commits.\n+\n+Import commands\n+---------------\n+Import commands (fetch, pull) do not create any new meta-commits or changes\n+unless that is specifically what they are importing. For example, the fetch\n+command would update remote/origin/metas/change35 and fetch all referenced\n+meta-commits if asked to do so directly, but it wouldn’t create any changes or\n+meta-commits for commits discovered on the master branch when running “git fetch\n+origin master”.\n+\n+Other commands\n+--------------\n+Some commands don’t fit cleanly into one of the above categories.\n+\n+Semantically, filter-branch should be treated as a modify command, but doing so\n+is likely to create a lot of irrelevant clutter in the changes namespace and the\n+large number of extra change refs may introduce performance problems. We\n+recommend treating filter-branch as an import command initially, but making it\n+behave more like a modify command in future follow-up work. One possible\n+solution may be to treat commits that are part of existing changes as being\n+modified but to avoid creating changes for other rewritten changes.\n+\n+Once the evolve command can handle obsolescence across cherry-picks, such\n+cherry-picks will result in a hybrid move-and-copy operation. It will create\n+cherry-picks that replace other cherry-picks, which will have both origin edges\n+(pointing to the new source commit being picked) and obsolete edges (pointing to\n+the previous cherry-pick being replaced).\n+\n+Evolve\n+------\n+The evolve command performs the correct sequence of rebases such that no change\n+has an obsolete parent. The syntax looks like this:\n+\n+git evolve [--abort][--continue][--quit] [upstream…]\n+\n+It takes an optional list of upstream branches. All changes whose parent shows\n+up in the history of one of the upstream branches will be rebased onto the\n+upstream branch before resolving obsolete parents.\n+\n+Any change whose latest state is found in an upstream branch (or that ends up\n+empty after rebase) will be deleted. This is the normal mechanism for deleting\n+changes. Changes are created automatically on the first commit, and are deleted\n+automatically when evolve determines that they’ve been merged upstream.\n+\n+Orphan commits are commits with obsolete parents. The evolve command then\n+repeatedly rebases orphan commits with non-orphan parents until there are either\n+no orphan commits left, a merge conflict is discovered, or a divergent parent is\n+discovered.\n+\n+When evolve discovers divergence, it will first check if it can resolve the\n+divergence automatically using one of its enabled transformations. Supported\n+transformations are:\n+\n+- Check if the user has already merged the divergent changes in a follow-up\n+  change. That is, look for an existing merge in a follow-up change where all\n+  the parents are divergent versions of the same change. Squash that merge with\n+  its parents and use the result as the resolution for the divergence.\n+\n+- Attempt to auto-merge all the divergent changes (disabled by default).\n+\n+Each of the transformations can be enabled or disabled by command line options.\n+\n+The --abort option returns all changes to the state they were in prior to\n+invoking evolve, and the --quit option terminates the current evolution without\n+changing the current state.\n+\n+If the working tree is dirty, evolve will attempt to stash the user's changes\n+before applying the evolve and then reapply those changes afterward, in much\n+the same way as rebase --autostash does.\n+\n+Checkout\n+--------\n+Running checkout on a change by name has the same effect as checking out a\n+detached head pointing to the latest commit on that change-branch. There is no\n+need to ever have HEAD point to a change since changes always move forward when\n+necessary, no matter what branch the user has checked out\n+\n+Meta-commits themselves cannot be checked out by their hash.\n+\n+Reset\n+-----\n+Resetting a branch to a change by name is the same as resetting to the commit at\n+that change’s head.\n+\n+Commit\n+------\n+Commit --amend gets modify semantics and will move existing changes forward. The\n+normal form of commit gets create semantics and will create a new change.\n+\n+$ touch foo && git add . && git commit -m \"foo\" && git tag A\n+$ touch bar && git add . && git commit -m \"bar\" && git tag B\n+$ touch baz && git add . && git commit -m \"baz\" && git tag C\n+\n+This produces the following commits:\n+A(tree=[foo])\n+B(tree=[foo, bar], parent=A)\n+C(tree=[foo, bar, baz], parent=B)\n+\n+...along with three changes:\n+metas/foo = A\n+metas/bar = B\n+metas/baz = C\n+\n+Running commit --amend does the following:\n+$ git checkout B\n+$ touch zoom && git add . && git commit --amend -m \"baz and zoom\"\n+$ git tag D\n+\n+Commits:\n+A(tree=[foo])\n+B(tree=[foo, bar], parent=A)\n+C(tree=[foo, bar, baz], parent=B)\n+D(tree=[foo, bar, zoom], parent=A)\n+Dmeta(content=D, obsolete=B)\n+\n+Changes:\n+metas/foo = A\n+metas/bar = Dmeta\n+metas/baz = C\n+\n+Merge\n+-----\n+Merge gets create, modify, or copy semantics based on what is being merged and\n+the options being used.\n+\n+The --squash version of merge gets copy semantics (it produces a new change that\n+is marked as a copy of all the original changes that were squashed into it).\n+\n+The “modify” version of merge replaces both of the original commits with the\n+resulting merge commit. This is one of the standard mechanisms for resolving\n+divergence. The parents of the merge commit are the parents of the two commits\n+being merged. The resulting commit will not be a merge commit if both of the\n+original commits had the same parent or if one was the parent of the other.\n+\n+The “create” version of merge creates a new change pointing to a merge commit\n+that has both original commits as parents. The result is what merge produces now\n+- a new merge commit. However, this version of merge doesn’t directly resolve\n+divergence.\n+\n+To select between these two behaviors, merge gets new “--amend” and “--noamend”\n+options which select between the “create” and “modify” behaviors respectively,\n+with noamend being the default.\n+\n+For example, imagine we created two divergent changes like this:\n+\n+$ touch foo && git add . && git commit -m \"foo\" && git tag A\n+$ touch bar && git add . && git commit -m \"bar\" && git tag B\n+$ touch baz && git add . && git commit --amend -m \"bar and baz\"\n+$ git tag C\n+$ git checkout B\n+$ touch bam && git add . && git commit --amend -m \"bar and bam\"\n+$ git tag D\n+\n+At this point the commit graph looks like this:\n+\n+A(tree=[foo])\n+B(tree=[bar], parent=A)\n+C(tree=[bar, baz], parent=A)\n+D(tree=[bar, bam], parent=A)\n+Cmeta(content=C, obsoletes=B)\n+Dmeta(content=D, obsoletes=B)\n+\n+There would be three active changes with heads pointing as follows:\n+\n+metas/changeA=A\n+metas/changeB=Cmeta\n+metas/changeB2=Dmeta\n+\n+ChangeB and changeB2 are divergent at this point. Lets consider what happens if\n+perform each type of merge between changeB and changeB2.\n+\n+Merge example: Amend merge\n+One way to resolve divergent changes is to use an amend merge. Recall that HEAD\n+is currently pointing to D at this point.\n+\n+$ git merge --amend metas/changeB\n+\n+Here we’ve asked for an amend merge since we’re trying to resolve divergence\n+between two versions of the same change. There are no conflicts so we end up\n+with this:\n+\n+E(tree=[bar, baz, bam], parent=A)\n+Emeta(content=E, obsoletes=[Cmeta, Dmeta])\n+\n+With the following branches:\n+\n+metas/changeA=A\n+metas/changeB=Emeta\n+metas/changeB2=Emeta\n+\n+Notice that the result of the “amend merge” is a replacement for C and D rather\n+than a new commit with C and D as parents (as a normal merge would have\n+produced). The parents of the amend merge are the parents of C and D which - in\n+this case - is just A, so the result is not a merge commit. Also notice that\n+changeB and changeB2 are now aliases for the same change.\n+\n+Merge example: Noamend merge\n+Consider what would have happened if we’d used a noamend merge instead. Recall\n+that HEAD was at D and our branches looked like this:\n+\n+metas/changeA=A\n+metas/changeB=Cmeta\n+metas/changeB2=Dmeta\n+\n+$ git merge --noamend metas/changeB\n+\n+That would produce the sort of merge we’d normally expect today:\n+\n+F(tree=[bar, baz, bam], parent=[C, D])\n+\n+And our changes would look like this:\n+metas/changeA=A\n+metas/changeB=Cmeta\n+metas/changeB2=Dmeta\n+metas/changeF=F\n+\n+In this case, changeB and changeB2 are still divergent and we’ve created a new\n+change for our merge commit. However, this is just a temporary state. The next\n+time we run the “evolve” command, it will discover the divergence but also\n+discover the merge commit F that resolves it. Evolve will suggest converting F\n+into an amend merge in order to resolve the divergence and will display the\n+command for doing so.\n+\n+Rebase\n+------\n+In general the rebase command is treated as a modify command. When a change is\n+rebased, the new commit replaces the original.\n+\n+Rebase --abort is special. Its intent is to restore git to the state it had\n+prior to running rebase. It should move back any changes to point to the refs\n+they had prior to running rebase and delete any new changes that were created as\n+part of the rebase. To achieve this, rebase will save the state of all changes\n+in refs/metas prior to running rebase and will restore the entire namespace\n+after rebase completes (deleting any newly-created changes). Newly-created\n+metacommits are left in place, but will have no effect until garbage collected\n+since metacommits are only used if they are reachable from refs/metas.\n+\n+Change\n+------\n+The “change” command can be used to list, rename, reset or delete change. It has\n+a number of subcommands.\n+\n+The \"list\" subcommand lists local changes. If given the -r argument, it lists\n+remote changes.\n+\n+The \"rename\" subcommand renames a change, given its old and new name. If the old\n+name is omitted and there is exactly one change pointing to the current HEAD,\n+that change is renamed. If there are no changes pointing to the current HEAD,\n+one is created with the given name.\n+\n+The \"forget\" subcommand deletes a change by deleting its ref from the metas/\n+namespace. This is the normal way to delete extra aliases for a change if the\n+change has more than one name. By default, this will refuse to delete the last\n+alias for a change if there are any other changes that reference this change as\n+a parent.\n+\n+The \"update\" subcommand adds a new state to a change. It uses the default\n+algorithm for assigning change names. If the content commit is omitted, HEAD is\n+used. If given the optional --force argument, it will overwrite any existing\n+change of the same name. This latter form of \"update\" can be used to effectively\n+reset changes.\n+\n+The \"update\" command can accept any number of --origin and --replace arguments.\n+If any are present, the resulting change branch will point to a metacommit\n+containing the given origin and replacement edges.\n+\n+The \"replace\" command records a replacement in the obsolescence graph, given a\n+list of obsolete commits or metacommits followed by their replacement. This\n+behaves like a normal \"modify\" command, except that the replacement is an\n+existing commit. If an obsolete commit points to a metacommit, only a change\n+branch pointing to exactly that metacommit moves forward. If an obsolete commit\n+points to a normal commit, all change branches pointing to that commit move\n+forward. If no change branches moved forward, a new change branch is created\n+using the default name.\n+\n+The \"abandon\" command deletes a change using obsolescence markers. It marks the\n+change as being obsolete and having been replaced by its parent. If given no\n+arguments, it applies to the current commit. Running evolve will cause any\n+abandoned changes to be removed from the branch. Any child changes will be\n+reparented on top of the parent of the abandoned change. If the current change\n+is abandoned, HEAD will move to point to its parent.\n+\n+The \"restore\" command restores a previously-abandoned change.\n+\n+The \"prune\" command deletes all obsolete changes and all changes that are\n+present in the given branch. Note that such changes can be recovered from the\n+reflog.\n+\n+Combined with the GC protection that is offered, this is intended to facilitate\n+a workflow that relies on changes instead of branches. Users could choose to\n+work with no local branches and use changes instead - both for mailing list and\n+gerrit workflows.\n+\n+Log\n+---\n+When a commit is shown in git log that is part of a change, it is decorated with\n+extra change information. If it is the head of a change, the name of the change\n+is shown next to the list of branches. If it is obsolete, it is decorated with\n+the text “obsolete, <n> commits behind <changename>”.\n+\n+Log gets a new --obslog argument indicating that the obsolescence graph should\n+be followed instead of the commit graph. This also changes the default\n+formatting options to make them more appropriate for viewing different\n+iterations of the same commit.\n+\n+Pull\n+----\n+\n+Pull gets an --evolve argument that will automatically attempt to run \"evolve\"\n+on any affected branches after pulling.\n+\n+We also introduce an \"evolve\" enum value for the branch.<name>.rebase config\n+value. When set, the evolve behavior will happen automatically for that branch\n+after every pull even if the --evolve argument is not used.\n+\n+Next\n+----\n+\n+The \"next\" command will reset HEAD to a non-obsolete commit that refers to this\n+change as its parent. If there is more than one such change, the user will be\n+prompted. If given the --evolve argument, the next commit will be evolved if\n+necessary first.\n+\n+The \"next\" command can be thought of as the opposite of\n+\"git reset --hard HEAD^\" in that it navigates to a child commit rather than a\n+parent.\n+\n+Other options considered\n+========================\n+We considered several other options for storing the obsolescence graph. This\n+section describes the other options and why they were rejected.\n+\n+Commit header\n+-------------\n+Add an “obsoletes” field to the commit header that points backwards from a\n+commit to the previous commits it obsoletes.\n+\n+Pros:\n+- Very simple\n+- Easy to traverse from a commit to the previous commits it obsoletes.\n+Cons:\n+- Adds a cost to the storage format, even for commits where the change history\n+  is uninteresting.\n+- Unconditionally prevents the change history from being garbage collected.\n+- Always causes the change history to be shared when pushing or pulling changes.\n+\n+Git notes\n+---------\n+Instead of storing obsolescence information in metacommits, the metacommit\n+content could go in a new notes namespace - say refs/notes/metacommit. Each note\n+would contain the list of obsolete and origin parents, and an automerger could\n+be supplied to make it easy to merge the metacommit notes from different remotes.\n+\n+Pros:\n+- Easy to locate all commits obsoleted by a given commit (since there would only\n+  be one metacommit for any given commit).\n+Cons:\n+- Wrong GC behavior (obsolete commits wouldn’t automatically be retained by GC)\n+  unless we introduced a special case for these kinds of notes.\n+- No way to selectively share or pull the metacommits for one specific change.\n+  It would be all-or-nothing, which would be expensive. This could be addressed\n+  by changes to the protocol, but this would be invasive.\n+- Requires custom auto-merging behavior on fetch.\n+\n+Tags\n+----\n+Put the content of the metacommit in a message attached to tag on the\n+replacement commit. This is very similar to the git notes approach and has the\n+same pros and cons.\n+\n+Simple forward references\n+-------------------------\n+Record an edge from an obsolete commit to its replacement in this form:\n+\n+refs/obsoletes/<A>\n+\n+pointing to commit <B> as an indication that B is the replacement for the\n+obsolete commit A.\n+\n+Pros:\n+- Protects <B> from being garbage collected.\n+- Fast lookup for the evolve operation, without additional search structures\n+  (“what is the replacement for <A>?” is very fast).\n+\n+Cons:\n+- Can’t represent divergence (which is a P0 requirement).\n+- Creates lots of refs (which can be inefficient)\n+- Doesn’t provide a way to fetch only refs for a specific change.\n+- The obslog command requires a search of all refs.\n+\n+Complex forward references\n+--------------------------\n+Record an edge from an obsolete commit to its replacement in this form:\n+\n+refs/obsoletes/<change_id>/obs<A>_<B>\n+\n+Pointing to commit <B> as an indication that B is the replacement for obsolete\n+commit A.\n+\n+Pros:\n+- Permits sharing and fetching refs for only a specific change.\n+- Supports divergence\n+- Protects <B> from being garbage collected.\n+\n+Cons:\n+- Creates lots of refs, which is inefficient.\n+- Doesn’t provide a good lookup structure for lookups in either direction.\n+\n+Backward references\n+-------------------\n+Record an edge from a replacement commit to the obsolete one in this form:\n+\n+refs/obsolescences/<B>\n+\n+Cons:\n+- Doesn’t provide a way to resolve divergence (which is a P0 requirement).\n+- Doesn’t protect <B> from being garbage collected (which could be fixed by\n+  combining this with a refs/metas namespace, as in the metacommit variant).\n+\n+Obsolescences file\n+------------------\n+Create a custom file (or files) in .git recording obsolescences.\n+\n+Pros:\n+- Can store exactly the information we want with exactly the performance we want\n+  for all operations. For example, there could be a disk-based hashtable\n+  permitting constant time lookups in either direction.\n+\n+Cons:\n+- Handling GC, pushing, and pulling would all require custom solutions. GC\n+  issues could be addressed with a repository format extension.\n+\n+Squash points\n+-------------\n+We create and update change branches in refs/metas them at the same time we\n+would in the metacommit proposal. However, rather than pointing to a metacommit\n+branch they point to normal commits and are treated as “squash points” - markers\n+for sequences of commits intended to be squashed together on submission.\n+\n+Amends and rebases work differently than they do now. Rather than actually\n+containing the desired state of a commit, they contain a delta from the previous\n+version along with a squash point indicating that the preceding changes are\n+intended to be squashed on submission. Specifically, amends would become new\n+changes and rebases would become merge commits with the old commit and new\n+parent as parents.\n+\n+When the changes are finally submitted, the squashes are executed, producing the\n+final version of the commit.\n+\n+In addition to the squash points, git would maintain a set of “nosquash” tags\n+for commits that were used as ancestors of a change that are not meant to be\n+included in the squash.\n+\n+For example, if we have this commit graph:\n+\n+A(...)\n+B(parent=A)\n+C(parent=B)\n+\n+...and we amend B to produce D, we’d get:\n+\n+A(...)\n+B(parent=A)\n+C(parent=B)\n+D(parent=B)\n+\n+...along with a new change branch indicating D should be squashed with its\n+parents when submitted:\n+\n+metas/changeB = D\n+metas/changeC = C\n+\n+We’d also create a nosquash tag for A indicating that A shouldn’t be included\n+when changeB is squashed.\n+\n+If a user amends the change again, they’d get:\n+\n+A(...)\n+B(parent=A)\n+C(parent=B)\n+D(parent=B)\n+E(parent=D)\n+\n+metas/changeB = E\n+metas/changeC = C\n+\n+Pros:\n+- Good GC behavior.\n+- Provides a natural way to share changes (they’re just normal branches).\n+- Merge-base works automatically without special cases.\n+- Rewriting the obslog would be easy using existing git commands.\n+- No new data types needed.\n+Cons:\n+- No way to connect the squashed version of a change to the original, so no way\n+  to automatically clean up old changes. This also means users lose all benefits\n+  of the evolve command if they prematurely squash their commits. This may occur\n+  if a user thinks a change is ready for submission, squashes it, and then later\n+  discovers an additional change to make.\n+- Histories would look very cluttered (users would see all previous edits to\n+  their commit in the commit log, and all previous rebases would show up as\n+  merges). Could be quite hard for users to tell what is going on. (Possible\n+  fix: also implement a new smart log feature that displays the log as though\n+  the squashes had occurred).\n+- Need to change the current behavior of current commands (like amend and\n+  rebase) in ways that will be unexpected to many users.\n-- \n2.20.0.405.gbc1bbc6f85-goog\n\n"}]}