1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-11-10 08:52:39 +01:00

Document mercurial and immutable history doctrines

Summary: Explains how to use the immutable history doctrine and mercurial.
Recommends "one idea is one commit".

Test Plan: Read documentation.

Reviewers: fratrik, Makinde, aran, jungejason, tuomaspelkonen, cpiro

Reviewed By: cpiro

CC: aran, cpiro, epriestley, ide

Differential Revision: 861
This commit is contained in:
epriestley 2011-08-25 17:42:16 -07:00
parent b64f252f8b
commit cd4f954b99
2 changed files with 181 additions and 15 deletions

View file

@ -0,0 +1,82 @@
@title Recommendations on Revision Control
@group flavortext
Project recommendations on how to organize revision control.
This document is purely advisory. Phabricator works with a variety of revision
control strategies, and diverging from the recommendations in this document
will not impact your ability to use it for code review and management.
This is my (epriestley's) personal take on the issue and not necessarily
representative of the views of the Phabricator team as a whole.
= Overview =
There are a few ways to use SVN, a few ways to use Mercurial, and many many many
ways to use Git. Particularly with Git, every project does things differently,
and all these approaches are valid for small projects. When projects scale,
strategies which enforce **one idea is one commit** are better.
= One Idea is One Commit =
Choose a strategy where **one idea is one commit** in the authoritative
master/remote version of the repository. Specifically, this means that an entire
conceptual changeset ("add a foo widget") is represented in the remote as
exactly one commit (in some form), not a sequence of checkpoint commits.
- In SVN, this means don't ##commit## until after an idea has been completely
written. All reasonable SVN workflows naturally enforce this.
- In Git, this means squashing checkpoint commits as you go (with ##git commit
--amend##) or before pushing (with ##git rebase -i##), or having a strict
policy where your master/trunk contains only merge commits and each is a
merge between the old master and a branch which represents a single idea.
Although this preserves the checkpoint commits along the branches, you can
view master alone as a series of single-idea commits.
- In Mercurial, it is not generally possible to avoid having ideas spread
across multiple commits. Mercurial does not support liberal mutability
doctrines (so you can't squash commits) and does not let you build a default
out of only merge commits, so it is not possible to have an authoritative
repository where one commit represents one idea in any real sense. Thus,
Mercurial is not recommended for projects which may ever need to scale.
= Why This Matters =
A strategy where **one idea is one commit** has no real advantage over any other
strategy until your repository hits a velocity where it becomes critical. In
particular:
- Essentially all operations against the master/remote repository are about
ideas, not commits. When one idea is many commits, everything you do is more
complicated because you need to figure out which commits represent an idea
("the foo widget is broken, what do I need to revert?") or what idea is
ultimately represented by a commit ("commit af3291029 makes no sense, what
goal is this change trying to accomplish?").
- Release engineering is greatly simplified. Release engineers can pick or
drop ideas easily when each idea corresponds to one commit. When an idea
is several commits, it becomes easier to accidentally pick or drop half of
an idea and end up in a state which is virtually guaranteed to be wrong.
- Automated testing is greatly simplified. If each idea is one commit, you
can run automated tests against every commit and test failures indicate a
serious problem. If each idea is many commits, most of those commits
represent a known broken state of the codebase (e.g., a checkpoint with a
syntax error which was fixed in the next checkpoint, or with a
half-implemented idea).
- Understanding changes is greatly simplified. You can bisect to a break and
identify the entire idea trivially, without fishing forward and backward in
the log to identify the extents of the idea. And you can be confident in
what you need to revert to remove the entire idea.
- There is no clear value in having checkpoint commits (some of which are
guaranteed to be known broken versions of the repository) persist into the
remote. Consider a theoretical VCS which automatically creates a checkpoint
commit for every keystroke. This VCS would obviously be unusable. But many
checkpoint commits aren't much different, and conceptually represent some
relatively arbitrary point in the sequence of keystrokes that went into
writing a larger idea. Get rid of them
or create an abstraction layer (merge commits) which allows you to ignore
them when you are trying to understand the repository in terms of ideas
(which is almost always).
All of these become problems only at scale. Facebook pushes dozens of ideas
every day and thousands on a weekly basis, and could not do this (at least, not
without more people or more errors) without choosing a repository strategy where
**one idea is one commit**.

View file

@ -16,9 +16,9 @@ Arcanist has technical, contributor-focused documentation here:
= Overview = = Overview =
Arcanist is a wrapper script that sits on top of other tools (e.g., Arcanist is a wrapper script that sits on top of other tools (e.g.,
Differential, linters, unit test frameworks, SVN, and git) and provides a Differential, linters, unit test frameworks, git, Mercurial, and SVN) and
simple command-line API to manage code review and some related revision control provides a simple command-line API to manage code review and some related
operations. revision control operations.
Arcanist allows you to do things like: Arcanist allows you to do things like:
@ -30,6 +30,7 @@ Arcanist allows you to do things like:
- download a patch from Differential with ##arc export## - download a patch from Differential with ##arc export##
- update Git commit messages after review with ##arc amend## - update Git commit messages after review with ##arc amend##
- commit SVN changes with ##arc commit## - commit SVN changes with ##arc commit##
- merge Git and Mercurial changes with ##arc merge##
- view enhanced information about Git branches with ##arc branch## - view enhanced information about Git branches with ##arc branch##
Once you've configured lint and unit test integration, you can also: Once you've configured lint and unit test integration, you can also:
@ -47,8 +48,8 @@ Arcanist has some advanced features as well, you can:
- ...or extend Arcanist and add new commands. - ...or extend Arcanist and add new commands.
Except where otherwise noted, these workflows are generally agnostic to the Except where otherwise noted, these workflows are generally agnostic to the
underlying version control system and will work properly in git or SVN underlying version control system and will work properly in git, Mercurial, or
repositories. SVN repositories.
= Installing Arcanist = = Installing Arcanist =
@ -71,8 +72,9 @@ to your ##.bashrc##, ##.profile## or similar:
= Running Arcanist = = Running Arcanist =
Arcanist is a context-sensitive command which you should run in a working copy, Arcanist is a context-sensitive command which you should run in a working copy,
like ##svn## or ##git##. Generally speaking, ##arc## commands operate on changed like ##git##, ##hg##, or ##svn##. Generally speaking, ##arc## commands operate
files in the working copy in svn, and on the commit at HEAD in git. on changed files in the working copy in SVN, a commit range you specify in git,
and outgoing changes on the current branch in Mercurial.
== SVN Basics == == SVN Basics ==
@ -95,7 +97,73 @@ been accepted, you can commit it like this:
== Git Basics == == Git Basics ==
There are a lot of ways to use git, and Arcanist is flexible enough to handle There are a lot of ways to use git, and Arcanist is flexible enough to handle
several of them. Use a commit template similar to this one: several of them. Git workflows divide into two major groups based on your
**doctrine of history mutability**.
Choose a **history mutability doctrine** by setting ##"immutable_history"## in
your ##.arcconfig##. Valid values are ##true## to enforce a **conservative
history mutability doctrine** or ##false## to enforce a **liberal history
mutability doctrine**.
A **liberal history mutability doctrine** means you rewrite local history. You
develop in feature branches, but squash or amend before pushing by using ##git
commit --amend## or ##git rebase -i##. Generally, one idea in the remote is
represented by one commit. Arc will read revision information from commit
messages, and you will finalize commits with ##arc amend##.
A **conservative history mutability doctrine** means that you do not rewrite
local history. This is similar to how Mercurial works. You develop in feature
branches and push them without squashing commits. You do not use ##git commit
--amend## or ##git rebase -i##. Generally, one idea in the remote is represented
by many commits. You will specify revision information via web workflows, and
finalize commits with ##arc merge##.
You can also choose no doctrine, which allows you to use both ##arc amend##
and ##arc merge##. This isn't recommended, but Phabricator explicitly tries to
support a broad range of git workflows. For recommendations on how to use git
and why this choice of doctrines exists, see @{article:Recommendations on
Revision Control}. If you aren't compelled by this and want to use a mixed
workflow, you can pick and choose parts of each workflow.
Phabricator associates commits to revisions (code reviews) by using commit
messages and commit hashes. It will be unable to detect that you have committed
a revision if you rebase (which changes all the hashes), and don't ##arc amend##
or ##arc merge##, and don't ##arc diff## to update Differential with the new
local hashes. You can use ##arc mark-committed## to explicitly mark revisions
committed.
=== Git: Conservative Mutability Doctrine ===
NOTE: This doctrine is new and experimental.
This section assumes you are using git with a **conservative history mutability
doctrine** and have set ##"immutable_history" : true## in your ##.arcconfig##.
To **create or update a revision** in git with a conservative doctrine:
$ git checkout master
$ git checkout -b feature # Create a feature branch
$ git commit -m '...' # Make a commit
$ arc diff master # Send changes since master for review
$ git commit -m '...' # Make more changes
$ git commit -m '...'
$ arc diff master # Update the revision
Once the revision has been accepted:
$ git checkout master # Checkout master
$ arc merge feature # Merge your feature branch with "arc merge" to
# generate a rich merge commit.
Now you can ##git push## or similar.
=== Git: Liberal Mutability Doctrine ===
This section assumes you are using git with a **liberal history mutability
doctrine** and have set ##"immutable_history" : false## in your ##.arcconfig##.
Under a liberal mutability doctrine, arc will read revision information from
your commit message. Use a commit template similar to this one:
arcanist/resources/git/commit-template.txt arcanist/resources/git/commit-template.txt
@ -111,19 +179,19 @@ To **create a revision** in git:
$ nano source_code.c # Make changes. $ nano source_code.c # Make changes.
$ git commit -a # Fill out the template. $ git commit -a # Fill out the template.
$ arc diff $ arc diff origin/master # Send changes from the origin for review.
To **update a revision** in git by amending HEAD: To **update a revision** in git by amending HEAD:
$ nano source_code.c # Make changes. $ nano source_code.c # Make changes.
$ git commit -a --amend # Amend into HEAD. $ git commit -a --amend # Amend into HEAD.
$ arc diff $ arc diff origin/master # Send changes from the origin for review.
To **update a revision** in git by stacking local commits: To **update a revision** in git by stacking local commits:
$ nano source_code.c # Make changes. $ nano source_code.c # Make changes.
$ git commit -a -m '...' # Make another local commit. $ git commit -a -m '...' # Make another local commit.
$ arc diff HEAD^^ # Update with the last two changes. $ arc diff origin/master # Send changes from the origin for review.
To **create and update a revision** using feature branches: To **create and update a revision** using feature branches:
@ -138,11 +206,27 @@ To **create and update a revision** using feature branches:
Once your revision has been accepted, use ##arc amend## to finalize it. Once your revision has been accepted, use ##arc amend## to finalize it.
$ arc amend # If you used an --amend workflow. $ git rebase -i # Squash commits if necessary.
$ arc amend # Update HEAD with final revision information.
If you used a multiple-commit workflow, you need to squash commits first with
##git rebase -i## or similar, then amend the squashed commit.
After amending, you can push the commit to the remote with ##git push## or After amending, you can push the commit to the remote with ##git push## or
##git svn dcommit## or via whatever other channel your project uses as ##git svn dcommit## or via whatever other channel your project uses as
applicable. applicable.
== Mercurial ==
Mercurial works similarly to git's immutable history doctrine.
To **create or update** a revision in Mercurial:
$ hg commit -m '...' # Create a commit
$ arc diff # Creates or updates a revision with outgoing changes
# on this branch.
Once a revision has been accepted, you can finalize it with ##arc merge##:
$ arc merge # Works like "hg merge && hg commit" but gives you a
# rich commit message.
This won't work if there are no remote changes on your branch, since the merge
is linear. In this case, just skip this step. You can now "hg push" or similar.