Size: 23657
Comment: change "state" in "phase" whenever appropriate
|
← Revision 56 as of 2014-12-04 14:50:53 ⇥
Size: 11498
Comment:
|
Deletions are marked like this. | Additions are marked like this. |
Line 3: | Line 3: |
/!\ This page is intended for developers /!\ For more generic details check [[MutableHG]] page |
|
Line 7: | Line 5: |
== Abstract == The phase concept aims to introduce a clear distinction between the part of the history that can't be rewritten and the part of history that you can safely rewrite. |
== Introduction == Phases improve safety of history rewriting and provide control over changesets exchanged among different repositories ([[#Available_Phases|read more]]). Phases are intended to transparently "just work" for most users ([[#Phase_Movements|read more]]). Phases are a part of the core Mercurial client code, enabled in any new client without preventing older clients from working on a repository ([[#Upgrade_Notes|read more]]). Advanced users may decide to handle phases manually to provide finer control ([[#Publishing_Repository|read more]]). |
Line 10: | Line 8: |
A basic usage of phases will track changesets on which you have full control because they exist only in your repository, and will prevent you to rewrite the other changesets that have been shared. | Like bookmarks, phases are not stored in history; as such, they're not permanent and leave no audit trail. |
Line 12: | Line 10: |
A more advanced usage of phases will allow to exchange a part of the history and yet keep it mutable. The actual rewritting of the existing changesets is covered by MutableHG. This will ease the setup of test or review processes. | Phases are introduced in Mercurial 2.1. See the [[Topic:phases|built-in help]] for details. |
Line 14: | Line 12: |
The phase concept also adds a layer to help people to control what changesets they exchange with others. | /!\ If you are new to phases you may want to read [[http://www.logilab.org/blogentry/88203|Introduction To Mercurial Phases]] first. |
Line 16: | Line 14: |
== related discussion == | == Available Phases == Phases are used to: |
Line 18: | Line 17: |
phases of changeset when no data are available: | * Prevent accidentally rewriting part of the history expected to be immutable * Prevent immature changesets from being exchanged by mistake |
Line 20: | Line 20: |
* http://selenic.com/pipermail/mercurial-devel/2012-January/036745.html Phase name and property name: * http://selenic.com/pipermail/mercurial-devel/2011-December/036632.html * this wiki page * constraint got property name here: http://selenic.com/pipermail/mercurial-devel/2012-January/036760.html == Changeset phases == A 'changeset phases' is an indicator that tells us how a changeset is manipulated and communicated. The details of each phase is described below, here we describe the properties they have in common. Like bookmarks, phases are not stored in history and thus are not permanent and leave no audit trail. First, no changeset can be in two phases at once. Phases are ordered, so they can be considered from lowest to highest. The default, lowest phase is 'public' - this is the normal phase of existing changesets. A child changeset can not be in a lower phase than its parents. The proposed phases are: public < draft < secret These phases share a hierarchy of traits: |
To achieve this, three phases share a hierarchy of traits: |
Line 41: | Line 22: |
||public ||x||x|| ||draft || ||x|| |
||public ||x ||x || ||draft || ||x || |
Line 48: | Line 29: |
These names are subject to change. See the Naming section. | * '''The public phase''' holds changesets that have been exchanged publicly. Changesets in the public phase are expected to remain in your repository history and are said to be ''immutable''. History rewriting extensions will refuse to delete these '''immutable''' changesets. Every changeset you push or pull from or to a public server is put in the public phase. |
Line 50: | Line 31: |
=== Usage === The current phase of changesets is displayed in the log with a 'phase:' header. The public phase is not displayed at all. |
* '''The draft phase''' holds changesets that are not yet considered a part of the repository's permanent history. You can safely rewrite them. New commits are in the draft phase by default. |
Line 53: | Line 33: |
There are corresponding revset predicates for each phase as well. | * '''The secret phase''' holds changesets that you do not want to exchange with other repositories. Secret changesets are hidden from remote peers and will not be included in push operations. Manual operations or extensions may move a changeset into the secret phase. |
Line 55: | Line 35: |
Most phase change will be made automatically by standard mercurial command. Read each phase section for details. | Phases split the history in a coherent set of changesets. Every changeset in a phase has an ancestor in a phase ''compatible'' with its phase. ''Compatible'' means an changeset's ancestors must at least have the same traits as the changeset itself, e.g.: a ''shared'' changeset always has ''shared'' ancestors and an ''immutable'' changeset always has ''immutable'' ancestors. |
Line 57: | Line 37: |
phases are manipulated via the 'hg phase' command, ie 'hg phase -p x' to mark x as public. Each phase puts its own constraints on how it can be manipulated. | In other words the phase of a changeset is always equal to or higher than the phase of its ancestors, according to the following order: |
Line 59: | Line 39: |
=== Implementation === Contexts provide a phase() method that returns the current phase as an integer. Contexts provide a phasestr() method that returns the current phase as a string. |
. public < draft < secret |
Line 62: | Line 41: |
public phase is stored as 0, draft phase is stored 1, secret phase is stored 2 | A changeset is not expected to automatically move from a lower phase to an higher phase (eg: from ''public'' to ''draft'') but automatic movement from ''draft'' to ''public'' can happen on transferring changesets between clones. Secret changesets have to be moved explicitly (except for bundles specified with the --base option). |
Line 64: | Line 43: |
== Public changesets == Public changesets are changesets that are considered permanently immutable. This matches the history model presented by legacy Mercurial: changesets cannot be changed or removed without using history editing features from extensions. The name reflect the fact that once a changeset is published, it becomes very difficult to remove it from distributed history. |
== Phase Movements == Phase movements are automatic and transparent; most users don't have to care much about them. The base rule is very simple: |
Line 67: | Line 46: |
=== Usage === Changeset are automatically set in the public phase when they are known to exist outside the repository. This behaviour can be altered using the publish option describe in the draft changeset section. |
. "''Any changesets seen in a remote repository are public''" |
Line 70: | Line 48: |
* changeset pulled are public * changeset pushed are public * changeset unbundled are public |
On standard exchange commands, phases of changesets on both sides are compared. If phases on both sides are not equal, the lowest phase is chosen, e.g.: a changeset known as ''draft'' locally but '''public''' remotely is set to public locally, because public < draft in the phase hierarchy. |
Line 74: | Line 50: |
Changeset can be manually moved to the public phase via 'hg phase --public X'. | This update happens during standard exchange commands: |
Line 76: | Line 52: |
Tagged changeset should probably be public | * '''pull''': remote phase data are used to update the phase data on the local repo. As pull is read-only, it does not change changeset's phases on the remote |
Line 78: | Line 54: |
=== implementation === === Legacy clients === N/A |
* '''push''': remote phase data are used to update the phase data on the local repo, then local phase data are pushed to the remote repo |
Line 82: | Line 56: |
== draft changesets == draft changesets are changesets that the user is permitted to use history-modifying operations like rebase or mq on. They may not be tagged. They may also be thought of as 'unpublished'. |
The real behavior is a bit more complicated than ''changesets on a remote repository are seen as public'', but this is true for simple repository setups. If you need finer-grained behavior, consult the section on ''[[#Publishing_Repository|publishing repositories]]''. |
Line 85: | Line 58: |
Various operations such as pushing to other repo will move changesets into the public phase. Changesets cannot be moved from public to draft without a forcing operation. | New changesets committed locally are in the ''draft'' phase, but some extensions (like ''mq'') may create ''secret'' changesets and handle the move from ''secret'' to ''draft'' in some other way. |
Line 87: | Line 60: |
A phase.publish option will allow exchange of changeset without moving them in the public phase. | Consult the [[#upgrade_Notes]] section to check how phases will move the first time a new version of Mercurial touches an existing repository. |
Line 89: | Line 62: |
* Changeset adding changeset to a phase.publish=True server are set public. This is the default. | == Command line interface == Phases are intended to be transparent for most users. People should not need to manually handle them and won't generally run into any behavior changes except to prevent common mistakes. Advanced users may want finer control over phase changes; this section describes how to change phases manually. |
Line 91: | Line 65: |
* Changeset adding changeset to a phase.publish=False server do not alter their phase. This allows people to collaborate on work in progress before it becomes finalized. | === Core Command === The phase concept introduces a single new command: {{{phase}}}. This command will allow users to see and change phases of changesets. |
Line 93: | Line 68: |
The phase of changesets are communicated between compatible servers and clients. | {{{ $ hg phase -r 8183::8186 8183: public 8184: public 8185: secret 8186: secret }}} {{{ $ hg phase -v --draft 8185 phase change for 1 changesets $ hg phase -r 8183::8186 8183: public 8184: public 8185: draft 8186: secret }}} See the command documentation for details. |
Line 95: | Line 86: |
This should generally be engineered such that users don't have to give any additional thought to draft vs public in their day-to-day usage. | The {{{hg log}}} command displays changeset phases when --debug is used. |
Line 97: | Line 88: |
=== Usage === Changesets must be in a draft phase when they are created. So new commits will start in a draft phase by default. |
All commands related to changeset exchange will ignore secret changesets, including: |
Line 100: | Line 90: |
=== Implementation === The set of draft changesets is stored as a list of draft roots. All descendants of these roots are draft (or in a higher phase). This set is known as the 'draft barrier' and defines the 'draft set'. This barrier is intended only to advance. The draft barrier is communicated via the pushkey protocol to servers that support it. The client is responsible for advancing the barrier on both the client and server sides. On each operation, the client reduces the phase set on both sides to the intersection of the sets on the client and server. That is, if a changeset is frozen on either side, it becomes frozen on both sides. By default servers are configured as 'publishing servers'. Legacy servers are publishing servers by default. These are recognized by not having the 'phase' pushkey namespace. When pushing to a publishing server, all pushed changesets are moved into the public phase on the client. Similarly, all changesets pulled from a publishing server are treated as public See this conversation for details: http://selenic.com/pipermail/mercurial-devel/2011-December/036279.html === Legacy clients === See above for pushing to legacy clients. Legacy clients are allowed to pull draft changesets, though a round trip will make them public. == secret changesets == secret changesets are changesets that are not visible to remote clients. This is useful to mark work private and to avoid inadvertently publishing changesets. === Usage === secret changesets (and their descendents) can be marked with 'hg phase --secret'. Some extension as mq may automatically set changeset as secret. === Implementation === Like draft changesets, secret changesets are implemented via a local barrier set. This set is used to filter changesets from remote clients for push/pull/hgweb. === Legacy clients === Legacy clients cannot see secret changesets so will not pull them. New clients will not push secret changesets by default == More phase == No more phase are planned, One argument that such functionality is better implemented by hooks, dedicated extensions or tool external to the dvcs itself. In particular, Suggested usages often try to register property (e.g., "passed QA" or "production ready") on a specific node and not just a whole consisted part of the dag. Just because node X passes QA doesn't imply that its ancestor did - in fact, it is almost certainly not the case One argument about other usage for phase is that such functionality is better implemented by hooks, dedicated extensions or tool external to the dvcs itself. In particular, Suggested usages often try to register property (e.g., "passed QA" or "production ready") on a specific node and not just a whole consisted part of the dag. Just because node X passes QA doesn't imply that its ancestor did - in fact, it is almost certainly not the case. In particular no trash phase: The big issue with using phase mechanism to mark changeset as dead is that phase define set of node which are consistent regarding rules that are problematic for the dead semantic. A golden semantic of phase are """all descendant of a changeset in phase X have the same property that this changeset""". We **can not** alter this: (1) We created phase for this exact semantic and we need it for all other usage. (2) The way phase are store and exchanged rely on this [1]. Having a trash phase means that a Y changeset children of X may be seen dead "by mistake" just because X was marked as dead. This is very problematic if X is added **after** the data than X is dead was created. Small local example bellow: $ hg pull Getting changeset "X" You look "X" and decide it's a silly changeset $ hg trash "X" Getting patch-y.diff from your mailbox patch-y.diff is a nice bugfix and you want it by chance, patch-y.diff have been created above "X" $ hg import --exact patch-y.diff As "Y" is a descendant of "X", "Y" is trashed too! This example highlight that phase are not very well suited to store "dead" information. We may have dedicated logic that detect new changesets is added on top of trashed one and handle this situation. But phase for dead still have two major issue! 1) It prevents any sane synchronisation of this trash phase with the outside. As phase only store un-versionned data, you have no way to know if the trash phase information were created before or after a changeset was committed. This means that you can't decide if the trash data really applies to changeset when you transfers it. 2) Adding non-trashed changeset on top on trashed changeset means moving the boundary and loosing the information that the parents trashed changeset are "not welcome in the final history". To conclusion about trash phase: * You can not safely exchange data about "dead" changeset through phase. Loosing most of point of having this data. * Safely using phase locally mean loosing the "dead" information whenever there is a conflict. == Naming == /!\ Until these names are officially nailed down, please use the names above for discussion of the concepts. The 'state' name was somewhat problematic because it's rather overloaded already and conflicts with hg stat. Thus 'phase' was picked instead. The 'frozen/liquid/local/dead' name set is also not ideal. The ideal set of phase names will: * all be on an obvious continuum or theme * not have conflicting initials (liquid vs local) (why?) * not conflict with common options (frozen vs force) * not insist too much on a particular workflow (review) * imply that changesets phases can be moved only in one direction (frozen vs public) * shouldn't conflict with the attributes of phases (mutable, hidden) * have fairly obvious semantics Current proposals: * public < draft < secret < ? (public implicates publishing: push or pull) * finished < draft < private (a draft gets finished) * final < liquid < private (a liquid reaches a final phase) Discarded proposals: * public < local < secret < ? (local is a bit misleading, doesn't imply mutability) * public < local < private (initials clash) * published < review or ready < draft < trash (review or ready is a bit too much workflow) * published < mutable < private < discarded (mutable isn't thematic, conflicts with attributes) * public < draft < private < deprecated (name clash) * public < liquid < private < deprecated (liquid strongly implies mutability and unfinishedness) (but name clash) From a UI perspective, transitions between phases are done either implicitly or by setting a phase. So there should be no 'freeze' or 'publish' verb in the UI. == Controlling and Hooking on phase movement == === Basic use case for control === I expect this use case to be a good mirror of how we would like to use phase at Logilab or for mercurial development. There is three kind of people working with three kind of repo. ==== People ==== This scenario includes three kind of people : :Developer: are part of a team that write code and create changesets. They are not allowed to validate their own changesets. :Admin: are part of the same team but are allowed to validate changeset. :External: are not part of the team and not allowed to validate their changesets either. ''Note: In this team, only a few people are allowed to validate changesets, but it could be different, for example with everyone but the interns allowed to validate their changesets.'' ==== Repositories ==== This scenario includes three kind of repositories: :Public: is the repository used as a reference for the project. The phases.publish option is set to True. Only people:admin people are allowed to push to it. For the mercurial project it is the repository available at http://selenic.com/hg :Devel: is a development repository used by the team. The phases.publish option is set to False. Both people:developer and people:admin can push to it but only people:admin are expected to move the phase boundary to mark changeset as validated and immutable. :External: repositories created and controlled by people:external where they work on their contributions. The core development team can only pull contribution from these repositories. ==== Vocabulary ==== * people:XXX refer to a mercurial user in the group XXX described above. * repo:XXX refer to a mercurial repositoty in the group XXX described above. * phase:Public refer to the 0 phase in which a changeset are immutable * ctx:X refer to a changeset "X" ==== Expectation ==== We need a way to make sure no changeset can be set in the phase:Public by something else than being pushed into repo:Public. Below are some variants: * We don't expect changeset of repo:Devel to in the public phase if they are not in repo:Public. * Phase movement on repo:Devel should be only accepted is triggered by * pull from repo:Public * push from people:admin * Changeset in a repo owned by people:devel of people:external should not be . set in public phase by something else than pulling from repo:Public or repo:Devel ==== Examples ==== ===== Example A ===== . (1) A people:Admin push ctx:A to repo:Public (2) A people:Developer pull ctx:A from repo:Public. The phase:Public boundary . move to ctx:A (3) The same people:Developer push ctx:A to repo:Devel. The public:Phase boundary move . to ctx:A * A.1 MUST be allowed * A.2 MUST be allowed * A.3 COULD be allowed ===== Example B ===== . (1) A people:Developer make ctx:B public locally. (2) The same people:Developer push ctx:B to Dev. Public phase boundary move . to B * B.1 COULD be denied. But we can't ensure it is. * B.2 MUST be denied ===== Example C ===== . (1) A people:Developer set ctx:C as phase:Public locally (2) A people:Admin pull from people:Developer which have publish=False. . phase:Public boundary move to ctx:C (2) The same people:Admin push to repo:Devel. The phase:Public . boundary move to ctx:C * C.1 COULD be denied. But we can't ensure it is * C.2 and C.3 are more about people:Admin making a mistake. But if the phase . movement is silent enough it won't be hard to make such mistake. ===== example D ===== . (1) A people:Developer set ctx:D as phase:Public locally (2) Another people:Developer pull from the first one. The phase:Public . boundary move to ctx:D (3) The second people:Developer push to Dev. The phase:Public boundary . move to ctx:D * D.1 COULD be denied. But we can't ensure it is. * D.2 CAN NOT be denied if we what to stay a ''Decentralized'' VCS (make it . clear by having a case where dev exchange valid public changeset) * D.3 SHOULD be denied (this conflict with A.1) ===== example E ===== . (1) A people:External push ctx:E to it's repo:External repo using old . mercurial version. (2) A people:Developer pull ctx:E from repo:External. The phase:Public . boundary move to ctx:E (3) The same people:Developer push ctx:E to repo:Devel for sharing. The . phase:Public boundary move to ctx:E. * E.1 Is not something we have control on. * E.2 SHOULD not deny the pull but COULD deny the phase movement (at least warn about it) * E.3 MUST be denied === Basic use case for hook === MQ: When mq managed changeset move from secret to ready (or public) mq will want to detect it and either: * qfinish those patches * Abort the transaction QA: When changeset are made public QA bot may trigger. SYNC: When changeset are made public in a devel repo, automatic sync with public repo may be wished. === Hook change === The way to controll this should be through hooks. Bellow is proposal ==== existing hook change ==== The following existing hooks familly might receive and additional argument about phase: * changegroup * commit |
* push * pull |
Line 347: | Line 94: |
* bundle * clone |
|
Line 348: | Line 97: |
They would receive a new argument $IN_PHASE. This argument hold a generic information about the maximum phase the changeset may take. | A warning will be displayed when outgoing operation (outgoing, push, bundle) fails to push anything in the face of unsynchronized secret changesets. |
Line 350: | Line 99: |
This would affect the following case: | {{{ no changes to push but 7 secret changesets }}} To see the changesets that are secret, use {{{hg log -r "secret()"}}}. (The same searching can be done for public and draft statuses by searching for {{{public()}}} and {{{draft()}}} respectively) |
Line 352: | Line 104: |
* hg unbundle into publish == True * '''changegroup''' IN_PHASE=0 * '''incoming''' IN_PHASE=0 |
Note that when using the {{{--base}}} option of bundle, secret changeset are included. |
Line 356: | Line 106: |
* hg unbundle into publish == False * '''changegroup''' IN_PHASE=1 * '''incoming''' IN_PHASE=1 (I'm not sur bundle should be seens as public or not…) |
=== Impact on extension(s) === Extensions that rewrite history (like MQ, rebase, collapse or histedit) will refuse to work on immutable changesets. When applying any of these extensions to a public changeset, an error will be thrown: |
Line 360: | Line 109: |
* hg pull into publish == True from publish == True * '''changegroup''' IN_PHASE=0 * '''incoming''' IN_PHASE=0 * '''remote outgoing''' IN_PHASE=0 |
{{{ abort: revision 8184 is not mutable }}} |
Line 365: | Line 113: |
* hg pull into publish == True from publish == False * '''changegroup''' IN_PHASE=0 * '''incoming''' IN_PHASE=1 * '''remote outgoing''' IN_PHASE=1 |
=== Impact on cloning === |
Line 370: | Line 115: |
* hg pull into publish == False from publish == True * '''changegroup''' IN_PHASE=1 * '''incoming''' IN_PHASE=0 * '''remote outgoing''' IN_PHASE=0 |
If you have any secret changesets in your repository, then a local [[Topic:clone]] will be forced to use the relatively slow pull protocol, instead of the faster methods of making file copies or HardlinkedClones. |
Line 375: | Line 117: |
* hg pull into publish == False from publish == False * '''changegroup''' IN_PHASE=1 * '''incoming''' IN_PHASE=1 * '''remote outgoing''' IN_PHASE=1 |
== Publishing Repository == By default, any changeset exchanged over the wire protocol is set to public. Advanced users may want some other behavior; the publishing repository concept is designed for this purpose. |
Line 380: | Line 120: |
* hg push from publish == True to publish == True * '''remote changegroup''' IN_PHASE=0 * '''outgoing''' IN_PHASE=0 * '''remote incoming''' IN_PHASE=0 (how should it know about it ?) |
==== What is a "publishing repository"? ==== Setting a repository as "publishing" alters its behavior **when used as a server**: all changesets in the repository are **seen** as public changesets by clients. |
Line 385: | Line 123: |
* hg push from publish == True to publish == False * '''remote changegroup''' IN_PHASE=1 * '''outgoing''' IN_PHASE=0 * '''remote incoming''' IN_PHASE=0 (how should it know about it ?) |
So, pushing to a "publishing" repository is the most common way to make changesets public: pushed changesets are seen as public on the remote side and marked as such on local side. |
Line 390: | Line 125: |
* hg push from publish == False to publish == True * '''remote changegroup''' IN_PHASE=0 * '''outgoing''' IN_PHASE=0 * '''remote incoming''' IN_PHASE=1 (how should it know about it ?) |
A repository is "publishing" by default. To make a repository non-publishing, add these lines to its hgrc configuration: |
Line 395: | Line 127: |
* hg push from publish == False to publish == False * '''remote changegroup''' IN_PHASE=1 * '''outgoing''' IN_PHASE=1 * '''remote incoming''' IN_PHASE=1 (how should it know about it ?) |
{{{ [phases] publish = False }}} Note: the "publish" property has no effects for local operations. |
Line 400: | Line 133: |
* hg commit * '''commit''' IN_PHASE=1 |
==== Old repository are publishing ==== Phase is the first step of a series of features aimed at better handling mutable history within Mercurial. Old clients do not support this feature and are unable to keep track of phase data. The safest solution is to consider as public any changeset going through an old client. |
Line 403: | Line 136: |
* hg commit --phase=secret * '''commit''' IN_PHASE=2 |
Moreover, most hosting solutions will not support phases from the beginning. Having old clients seen as public repositories will not change their usage: public repositories where you push *immutable* public changesets *shared* with others. |
Line 406: | Line 138: |
* hg commit --phase=public * '''commit''' IN_PHASE=0 |
==== Why is "publishing" the default? ==== We discussed above that any changeset from a non-phase aware repository should be seen as public. This means that in the following scenario, X is pulled as public:: |
Line 409: | Line 141: |
==== Introducing new hook ==== I can see two new possible famillies of hook: |
{{{ ~/A$ cd ../B ~/B$ new-hg pull ../A # let's pretend A is served by old-hg ~/B$ new-hg log -r tip summary: X phase: public }}} We want to keep this behavior while creating/serving the A repository with new-hg, although committing with any new-hg creates a draft changeset. To stay backward compatible, the pull must see the new commit as public. Non-publishing servers will advertise them as draft. Having publishing repository be the default is thus necessary to ensure this backward compatibility. |
Line 412: | Line 149: |
movephase: triggered when we move phase boundary locally. | This default value can also be expressed with the following sentence: "By default, without any configuration, everything you exchange with the outside is immutable". |
Line 414: | Line 151: |
* reason (commit, pull, push, remotepush, addchangegroup, unbundle, other (eg manual)) * source * publishing_source (true or false) * user ? * boundaries (how to serialise this for shell…) |
==== Why allow draft changeset in publishing repository ==== Note: The publish option is aimed at controlling the behavior of ''server''. Changeset in any state on a publishing server will '''always''' be seen as public by other client. "Passive" repository which are only used as server for pull and push operation are not "affected" by this section. |
Line 420: | Line 154: |
pushphase: triggered when we send phase data to remote | As in the choice for default, the main reason to allow draft changeset in publishing server is backward compatibility. With an old client, the following scenario is valid:: |
Line 422: | Line 156: |
* source (aka remote) * user ? * publishing_source (true or false) * boundaries (how to serialise this for shell…) |
{{{ ~/A$ old-hg init ~/A$ echo 'babar' > jungle ~/A$ old-hg commit -mA 'X' ~/A$ old-hg qimport -r . # or any other mutable operation on X }}} '' '' |
Line 427: | Line 164: |
Stuff that move phase: | If the default is publishing and new commits in such repository are "''public''" The following operation will be denied as X will be an '''immutable''' public changeset. However as other clients see X as public, any pull//push (or event pull//pull) will mark X as ''public'' in repo A. |
Line 429: | Line 166: |
* hg commit * hg pull * hg push * hg unbundle |
== Upgrade Notes == The important points to remember are: |
Line 434: | Line 169: |
The more details we can get about phase movement is | * repositories with phase data can still be accessed by old client(s) * the new client will add phase data to any repository it touches * if everything you plan to mutate is handled by MQ you don't have to care about anything |
Line 436: | Line 173: |
* hg commit: * premovephase with precommit * prexnmovephase with prexncommit * movephase with commit |
=== Backward Compatibility === Phase data are stored in a new file and do not alter any part of the existing Mercurial repository format. This means that a new client can safely write phase related data without preventing an old client to work with the repository. This allows new client to store and handle phase related logic on **all repositories**. |
Line 441: | Line 176: |
* hg pull * (Do we need something before discovery ?) * '''premovephase''' . before we pull changegroup (move computed using remote phase data) ? (phase did not moved yet) * '''prexnmovephase''' after we pull changegroup (move computed using remote phase data). . (phase did moved) * '''movephase''' after changegroup have been added (or nothing to pull) . (phase did moved) |
=== Adding phase data to an old repo === There are a lot of repositories out there with plenty of changesets but lacking any phase data. When looking at such a repository, a new client will take the safe road and decide everything is 'public'. Some extensions register logic to tune this choice; for example, mq will set every changeset under its control as secret in this situation. |
Line 450: | Line 179: |
* hg push * (Do we need something before discovery ?) |
You can set all changesets not pushed to a repository in the draft phase again using: |
Line 453: | Line 181: |
* '''premovephase''' before we push changegroup (move computed using remote phase data ?) | {{{ hg phase --force --draft "outgoing() and public()" }}} === Touching a phased repo with an old client === Beware that any old client won't be able to move phases when touching a repo. |
Line 455: | Line 187: |
* '''prepushphase''' before we push changegroup (move computed using remote phase data ?) | * An old client can mutate immutable changesets * An old client will push secret changesets * An old client will commit new changesets in the phase of their parent * An old client will add changesets in the phase of their parent |
Line 457: | Line 192: |
* '''prexnmovephase''' after we pushed changegroup but before we push phases to remote * '''prexnpushphase''' after we pushed changegroup but before push phases to remote * '''pushphase''' after changegroup have been pushed (or nothing to push) and phase pushed * '''movephase''' after changegroup have been added (or nothing to pull) and phase pushed === Solving usecase with Hook === ==== Control usecase ==== repo:Devel set a '''premovephase''' hook that deny phase move for user outside people:Admin. This deny A.1, B.2, D.3, E.3 people:Admin set a '''movephase'' hook that display warning when movephase is called on something else than: ''''' * '''''local operation ''''' * '''''pull from repo:Public ''''' * '''''push to repo:Public ''''' '''''people:Developer set a ''movephase'' hook that display flashy warning when movephase is called on something else than: ''''' * '''''pull from repo:Public ''''' '''''people:Developer set a ''pushphase'' hook that display flashy warning in all case: ||case ||requirement ||behavior || ||A.1 ||MUST be allowed ||works || ||A.2 ||MUST be allowed ||works || ||A.3 ||COULD be allowed ||denied (because D.3 and E.3) '' '' || ||B.1 ||COULD be denied ||flashy warning it's not too late || ||B.2 ||MUST be denied ||denied || ||C.1 ||COULD be denied ||flashy warning it's not too late || ||C.2 ||COULD be denied ||flashy warning it's not too late || ||C.3 ||MUST be denied ||allowed || ||D.1 ||COULD be denied ||flashy warning it's not too late || ||D.2 ||Wrong ||flashy warning it's not too late || ||D.2' ||Wrong ||flashy warning contact the other dev || ||D.3 ||MUST be denied ||denied || ||E.1 ||No control ||No Control || ||E.2 ||DENY phase move ||flashy warning fixable locally || ||E.3 ||MUST be denied ||denied || ''''' === other case === '''''MQ: can use a prexnmovephase hook to deny pull over mq patches. ''''' '''''MQ: can use a movephase hook to qfinish mqpatch made public. ''''' '''''QA: can use movephase hook to trigger build bot. ''''' '''''SYNC: can use movephase hook to trigger build bot. ''''' == Implementation status == === Done === * '''Basic phase mechanism'''. phase summary can be move and are properly save in mercurial transaction. * '''Public and draft phases'''. Changeset are committed as draft. Changeset are set as public on exchange operation. * phase.publish option to control if echanged changeset are set in the public phase. * Exchange phase boundary exchange === Implementation in progress === * secret phase === To be implemented === * display phase data in log + template support * phase command to view and alter changeset phase * revset support * clone support * '''Public changeset are immutable''', rebase and mq refuse to delete them. * Hide secret changeset in hgweb graft. * have ui.status about phase movement. * Hide secret changeset from outgoing. === Still in discussion === * Hooks on Phase movement * phases number: 0,1,2 or 0,10,20 keep room for future addition ? |
(If you were looking for the developer oriented page: PhasesDevel) |
Line 551: | Line 195: |
''''' CategoryDeveloper ''''' | CategoryProject |
Phases
Contents
1. Introduction
Phases improve safety of history rewriting and provide control over changesets exchanged among different repositories (read more). Phases are intended to transparently "just work" for most users (read more). Phases are a part of the core Mercurial client code, enabled in any new client without preventing older clients from working on a repository (read more). Advanced users may decide to handle phases manually to provide finer control (read more).
Like bookmarks, phases are not stored in history; as such, they're not permanent and leave no audit trail.
Phases are introduced in Mercurial 2.1. See the built-in help for details.
If you are new to phases you may want to read Introduction To Mercurial Phases first.
2. Available Phases
Phases are used to:
- Prevent accidentally rewriting part of the history expected to be immutable
- Prevent immature changesets from being exchanged by mistake
To achieve this, three phases share a hierarchy of traits:
|
immutable |
shared |
public |
x |
x |
draft |
|
x |
secret |
|
|
The public phase holds changesets that have been exchanged publicly. Changesets in the public phase are expected to remain in your repository history and are said to be immutable. History rewriting extensions will refuse to delete these immutable changesets. Every changeset you push or pull from or to a public server is put in the public phase.
The draft phase holds changesets that are not yet considered a part of the repository's permanent history. You can safely rewrite them. New commits are in the draft phase by default.
The secret phase holds changesets that you do not want to exchange with other repositories. Secret changesets are hidden from remote peers and will not be included in push operations. Manual operations or extensions may move a changeset into the secret phase.
Phases split the history in a coherent set of changesets. Every changeset in a phase has an ancestor in a phase compatible with its phase. Compatible means an changeset's ancestors must at least have the same traits as the changeset itself, e.g.: a shared changeset always has shared ancestors and an immutable changeset always has immutable ancestors.
In other words the phase of a changeset is always equal to or higher than the phase of its ancestors, according to the following order:
public < draft < secret
A changeset is not expected to automatically move from a lower phase to an higher phase (eg: from public to draft) but automatic movement from draft to public can happen on transferring changesets between clones. Secret changesets have to be moved explicitly (except for bundles specified with the --base option).
3. Phase Movements
Phase movements are automatic and transparent; most users don't have to care much about them. The base rule is very simple:
"Any changesets seen in a remote repository are public"
On standard exchange commands, phases of changesets on both sides are compared. If phases on both sides are not equal, the lowest phase is chosen, e.g.: a changeset known as draft locally but public remotely is set to public locally, because public < draft in the phase hierarchy.
This update happens during standard exchange commands:
pull: remote phase data are used to update the phase data on the local repo. As pull is read-only, it does not change changeset's phases on the remote
push: remote phase data are used to update the phase data on the local repo, then local phase data are pushed to the remote repo
The real behavior is a bit more complicated than changesets on a remote repository are seen as public, but this is true for simple repository setups. If you need finer-grained behavior, consult the section on publishing repositories.
New changesets committed locally are in the draft phase, but some extensions (like mq) may create secret changesets and handle the move from secret to draft in some other way.
Consult the #upgrade_Notes section to check how phases will move the first time a new version of Mercurial touches an existing repository.
4. Command line interface
Phases are intended to be transparent for most users. People should not need to manually handle them and won't generally run into any behavior changes except to prevent common mistakes. Advanced users may want finer control over phase changes; this section describes how to change phases manually.
4.1. Core Command
The phase concept introduces a single new command: phase. This command will allow users to see and change phases of changesets.
$ hg phase -r 8183::8186 8183: public 8184: public 8185: secret 8186: secret
$ hg phase -v --draft 8185 phase change for 1 changesets $ hg phase -r 8183::8186 8183: public 8184: public 8185: draft 8186: secret
See the command documentation for details.
The hg log command displays changeset phases when --debug is used.
All commands related to changeset exchange will ignore secret changesets, including:
- push
- pull
- incoming
- outgoing
- bundle
- clone
A warning will be displayed when outgoing operation (outgoing, push, bundle) fails to push anything in the face of unsynchronized secret changesets.
no changes to push but 7 secret changesets
To see the changesets that are secret, use hg log -r "secret()". (The same searching can be done for public and draft statuses by searching for public() and draft() respectively)
Note that when using the --base option of bundle, secret changeset are included.
4.2. Impact on extension(s)
Extensions that rewrite history (like MQ, rebase, collapse or histedit) will refuse to work on immutable changesets. When applying any of these extensions to a public changeset, an error will be thrown:
abort: revision 8184 is not mutable
4.3. Impact on cloning
If you have any secret changesets in your repository, then a local clone will be forced to use the relatively slow pull protocol, instead of the faster methods of making file copies or HardlinkedClones.
5. Publishing Repository
By default, any changeset exchanged over the wire protocol is set to public. Advanced users may want some other behavior; the publishing repository concept is designed for this purpose.
5.0.1. What is a "publishing repository"?
Setting a repository as "publishing" alters its behavior **when used as a server**: all changesets in the repository are **seen** as public changesets by clients.
So, pushing to a "publishing" repository is the most common way to make changesets public: pushed changesets are seen as public on the remote side and marked as such on local side.
A repository is "publishing" by default. To make a repository non-publishing, add these lines to its hgrc configuration:
[phases] publish = False
Note: the "publish" property has no effects for local operations.
5.0.2. Old repository are publishing
Phase is the first step of a series of features aimed at better handling mutable history within Mercurial. Old clients do not support this feature and are unable to keep track of phase data. The safest solution is to consider as public any changeset going through an old client.
Moreover, most hosting solutions will not support phases from the beginning. Having old clients seen as public repositories will not change their usage: public repositories where you push *immutable* public changesets *shared* with others.
5.0.3. Why is "publishing" the default?
We discussed above that any changeset from a non-phase aware repository should be seen as public. This means that in the following scenario, X is pulled as public::
~/A$ cd ../B ~/B$ new-hg pull ../A # let's pretend A is served by old-hg ~/B$ new-hg log -r tip summary: X phase: public
We want to keep this behavior while creating/serving the A repository with new-hg, although committing with any new-hg creates a draft changeset. To stay backward compatible, the pull must see the new commit as public. Non-publishing servers will advertise them as draft. Having publishing repository be the default is thus necessary to ensure this backward compatibility.
This default value can also be expressed with the following sentence: "By default, without any configuration, everything you exchange with the outside is immutable".
5.0.4. Why allow draft changeset in publishing repository
Note: The publish option is aimed at controlling the behavior of server. Changeset in any state on a publishing server will always be seen as public by other client. "Passive" repository which are only used as server for pull and push operation are not "affected" by this section.
As in the choice for default, the main reason to allow draft changeset in publishing server is backward compatibility. With an old client, the following scenario is valid::
~/A$ old-hg init ~/A$ echo 'babar' > jungle ~/A$ old-hg commit -mA 'X' ~/A$ old-hg qimport -r . # or any other mutable operation on X
If the default is publishing and new commits in such repository are "public" The following operation will be denied as X will be an immutable public changeset. However as other clients see X as public, any pull//push (or event pull//pull) will mark X as public in repo A.
6. Upgrade Notes
The important points to remember are:
- repositories with phase data can still be accessed by old client(s)
- the new client will add phase data to any repository it touches
- if everything you plan to mutate is handled by MQ you don't have to care about anything
6.1. Backward Compatibility
Phase data are stored in a new file and do not alter any part of the existing Mercurial repository format. This means that a new client can safely write phase related data without preventing an old client to work with the repository. This allows new client to store and handle phase related logic on **all repositories**.
6.2. Adding phase data to an old repo
There are a lot of repositories out there with plenty of changesets but lacking any phase data. When looking at such a repository, a new client will take the safe road and decide everything is 'public'. Some extensions register logic to tune this choice; for example, mq will set every changeset under its control as secret in this situation.
You can set all changesets not pushed to a repository in the draft phase again using:
hg phase --force --draft "outgoing() and public()"
6.3. Touching a phased repo with an old client
Beware that any old client won't be able to move phases when touching a repo.
- An old client can mutate immutable changesets
- An old client will push secret changesets
- An old client will commit new changesets in the phase of their parent
- An old client will add changesets in the phase of their parent
(If you were looking for the developer oriented page: PhasesDevel)