mirror of
https://we.phorge.it/source/phorge.git
synced 2024-12-23 22:10: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 =
|
= 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.
|
||||||
|
|
Loading…
Reference in a new issue