#pragma section-numbers 2 <> = Default destination plan = A lot of Mercurial command as "default" behavior, some needs revisiting. <> == Default Update Destination == (we discuss the case without bookmark) === Current Behavior === Current default value for update is: `max(branch(.))` ==== Pure Consumer Case ==== If the user is just using `hg pull` and `hg update` this work fine. #1 User is on branch `foo`, {{{#!dot digraph { rankdir=LR node [shape=box] A -> B; B [label="@B"]; } }}} #2 `hg pull` bring new changeset in branch `foo`, {{{#!dot digraph { rankdir=LR node [shape=box] A -> B -> C -> D; B [label="@B"]; } }}} #3 `hg update` bring the user on the latest (just pulled) changeset on `foo`. {{{#!dot digraph { rankdir=LR node [shape=box] A -> B -> C -> D; D [label="@D"]; } }}} * (./) `hg update` updated working copy content to latest version, mission accomplished. ==== Consumer Case: pulling multiple heads ==== #1 User is on branch `foo`, {{{#!dot digraph { rankdir=LR node [shape=box] A -> B; B [label="@B"]; } }}} #2 `hg pull` bring multiple heads in branch `foo`, {{{#!dot digraph { rankdir=LR node [shape=box] A -> B -> C -> D; B -> X -> Y; B [label="@B"]; } }}} #3 `hg update` bring the user one of the head. {{{#!dot digraph { rankdir=LR node [shape=box] A -> B -> C -> D; B -> X -> Y; D [label="@D"]; } }}} * {X} user got only half of the pulled changesets * used head is "arbitrary" * {X} No warning about the user heads (, pulling multiple heads on a branch point at FeatureBranchesStruggle or advanced usecase/users) ==== Producer Case: simple ==== #1 User is on branch `foo`, with some own local changesets on branch `foo` {{{#!dot digraph { rankdir=LR node [shape=box] A -> B -> X -> Y; Y [label="@Y"]; } }}} #2 `hg pull` bring new changeset in branch `foo`, The pull commend mention that a new head is created. {{{#!dot digraph { rankdir=LR node [shape=box] A -> B -> X -> Y; B -> C -> D; Y [label="@Y"]; } }}} #3 `hg update` refuse to update for "non-linear update" * (./) This prompt the use toward doing a merge * The message is not quite explicit about that * {X} Message is not issued if Y is created after the pull (quite common, if pulling with uncommitted change) * {X} `hg update --clean` will happily move to `D` without any message ==== Producer Case: pulling multiple heads ==== #1 User is on branch `foo`, with some own local changesets on branch `foo` {{{#!dot digraph { rankdir=LR node [shape=box] A -> B -> X; X [label="@X"]; } }}} #2 `hg pull` bring new changeset on the same topological branch and another new heads, (could also be achieved with use have X, Y locally but went back on X for some reason before the pull) The pull commend mention that a new head is created. {{{#!dot digraph { rankdir=LR node [shape=box] A -> B -> X -> Y; B -> C -> D; X [label="@X"]; } }}} #3 `hg update` refuse to update for "non-linear update" * (./) This prompt the use toward doing a merge * The message is not quite explicit about that a merge * {X} Merging will not reduce the number of heads becase we are not a head * {X} `hg update --clean` will happily move to `D` without any message, actually 'updating backward' our current working copy. * {X} if Y existed before the pull, `hg up` would have brought us on Y. === Analysis === 1. '''`hg update` moving to branch head:''' is a sensible default. Named branch denote a meaningful context/target/topic/product and `hg update` getting the most up-to-date content for that context. 2. '''complains about non-linear:''' one of the interresting side effect of the complains about non-linear update is that in the common very basic operation, it help the user to spot that three is multiple heads and that something is to be done. However, the message are very bad at pointing this and there is a significant number of cases were this won't point it out. 3. '''ignoring other lower heads:''' `hg update` while on `max(branch(.))` will simply says "nothing to update" ignoring all the other unmerged heads on the branch. 4. '''behavior change with --clean:''' The behavior change (allowing non linear update) seems very debatable. While the initial motivation is probably that merging uncommitted change from on head to another was technically impossible in early ages, this fit badly in the logical framework for point (1) and (2). It also make the command less consistent. === Behavior Change Proposal === There is multiple tier of change we use to improve the situation: 1. {x} Improving `not a linear update` message to mention heads and merge (waiting to be implemented). 2. (./) Adding warning message to update that end on a head when there is other heads on the branch. 3. (./) Having update destination become ``max(branch(.) and .::)`` as warning added in (2) provide the same usefulness as the non-linear abort. 4. --(Abort is there there is more than one possible destination heads (probably too radical)[REJECTED])-- == Default Rebase Destination == (we discuss the case without bookmark) === Current Behavior === Current default value for update is: `max(branch(.))` ==== Optimal case: pull things and rebase ==== #1 User is on branch `foo`, with some own local changesets on branch `foo` {{{#!dot digraph { rankdir=LR node [shape=box] A -> B -> X -> Y; Y [label="@Y"]; } }}} #2 `hg pull` bring new changeset in branch `foo`, The pull commend mention that a new head is created. {{{#!dot digraph { rankdir=LR node [shape=box] A -> B -> X -> Y; B -> C -> D; Y [label="@Y"]; } }}} #3 `hg rebase` move current local branch on newly pulled changesets {{{#!dot digraph { rankdir=LR node [shape=box] A -> B -> C -> D -> X -> Y; Y [label="@Y"]; } }}} * (./) local changed, rebased on remote change: mission accomplished. * (./) This is the `hg pull --rebase` case. ==== Optimal case: pull things, commit and rebase ==== #1 User is on branch `foo`, with some own local changesets on branch `foo` {{{#!dot digraph { rankdir=LR node [shape=box] A -> B -> X; X [label="@X"]; } }}} #2 `hg pull` bring new changeset in branch `foo`, The pull commend mention that a new head is created. {{{#!dot digraph { rankdir=LR node [shape=box] A -> B -> X; B -> C -> D; X [label="@X"]; } }}} #3 user commit his changes {{{#!dot digraph { rankdir=LR node [shape=box] A -> B -> X -> Y; B -> C -> D; Y [label="@Y"]; } }}} #4 `hg rebase` says "nothing to rebase" (Latest commit in the branch in working directory parent and default target.) * There is another obvious candidate for rebase. {{{#!dot digraph { rankdir=LR node [shape=box] A -> B -> X -> Y; B -> C -> D; Y [label="@Y"]; } }}} ==== Optimal case: pulling multiple heads and rebase ==== #1 User is on branch `foo`, with some own local changesets on branch `foo` {{{#!dot digraph { rankdir=LR node [shape=box] A -> B -> X; X [label="@X"]; } }}} #2 `hg pull` bring multiple heads in branch `foo`, The pull commend mention that a new head is created. {{{#!dot digraph { rankdir=LR node [shape=box] A -> B -> X; B -> C -> D; B -> E -> F; X [label="@X"]; } }}} #3 `hg rebase` silently pick a branch as destination. {{{#!dot digraph { rankdir=LR node [shape=box] A -> B; B -> C -> D; B -> E -> F -> X; X [label="@X"]; } }}} Last pulled commit was 'F' (because reasons…), * {X} case was ambiguous, it should have aborted like merge. === Analysis === Rebase default destination is likely as it is to prevent possible issue with rebasing "public" changeset (from the pre-phases era). As it is: * `hg pull && hg rebase` will likely do the right and safe thing (local producer case). * anything will probably refuse to work However we now have phase, that prevent user to rebase bad things The current destination has some obvious issues: * Fails to spot the other single head we just pull before comitting, * silently pick an "arbitrary" head when multiple are available. == Merge behavior == * {x} We want merge to print a warning, when not reducing the number of heads. === Changes Proposal === Rebase semantic is very similar to merge semantic. We could and should share the same logic for default destination: * (./) If there is just one head → nothing to merge/rebase * (./) If there is one other head, merge/rebase with it. * (./) If there is more than one other heads, refuse to pick and ask for explicit target. == Progress == ((./) done, in progress, {X} Need to be done) * (./) extra default destinations into their own functions, * {X} make the report/failure logic optional, * {X} expose default destination as revsets, * {X} rework default update destination, * (./) rework default rebase destination, * {X} rework default merge destination, ---- CategoryDeveloper and CategoryNewFeatures