mirror of
https://we.phorge.it/source/phorge.git
synced 2024-12-19 12:00:55 +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:
parent
b64f252f8b
commit
cd4f954b99
2 changed files with 181 additions and 15 deletions
|
@ -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**.
|
|
@ -16,9 +16,9 @@ Arcanist has technical, contributor-focused documentation here:
|
|||
= Overview =
|
||||
|
||||
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
|
||||
simple command-line API to manage code review and some related revision control
|
||||
operations.
|
||||
Differential, linters, unit test frameworks, git, Mercurial, and SVN) and
|
||||
provides a simple command-line API to manage code review and some related
|
||||
revision control operations.
|
||||
|
||||
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##
|
||||
- update Git commit messages after review with ##arc amend##
|
||||
- commit SVN changes with ##arc commit##
|
||||
- merge Git and Mercurial changes with ##arc merge##
|
||||
- view enhanced information about Git branches with ##arc branch##
|
||||
|
||||
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.
|
||||
|
||||
Except where otherwise noted, these workflows are generally agnostic to the
|
||||
underlying version control system and will work properly in git or SVN
|
||||
repositories.
|
||||
underlying version control system and will work properly in git, Mercurial, or
|
||||
SVN repositories.
|
||||
|
||||
= Installing Arcanist =
|
||||
|
||||
|
@ -71,8 +72,9 @@ to your ##.bashrc##, ##.profile## or similar:
|
|||
= Running Arcanist =
|
||||
|
||||
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
|
||||
files in the working copy in svn, and on the commit at HEAD in git.
|
||||
like ##git##, ##hg##, or ##svn##. Generally speaking, ##arc## commands operate
|
||||
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 ==
|
||||
|
||||
|
@ -95,7 +97,73 @@ been accepted, you can commit it like this:
|
|||
== Git Basics ==
|
||||
|
||||
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
|
||||
|
||||
|
@ -111,19 +179,19 @@ To **create a revision** in git:
|
|||
|
||||
$ nano source_code.c # Make changes.
|
||||
$ 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:
|
||||
|
||||
$ nano source_code.c # Make changes.
|
||||
$ 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:
|
||||
|
||||
$ nano source_code.c # Make changes.
|
||||
$ 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:
|
||||
|
||||
|
@ -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.
|
||||
|
||||
$ arc amend # If you used an --amend workflow.
|
||||
|
||||
If you used a multiple-commit workflow, you need to squash commits first with
|
||||
##git rebase -i## or similar, then amend the squashed commit.
|
||||
$ git rebase -i # Squash commits if necessary.
|
||||
$ arc amend # Update HEAD with final revision information.
|
||||
|
||||
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
|
||||
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.
|
||||
|
|
Loading…
Reference in a new issue