@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.)
hq.recaptime.dev/wiki/Phorge
phorge
phabricator
1@title Arcanist User Guide: Commit Ranges
2@group userguide
3
4Explains how commit ranges work in Arcanist.
5
6This is an advanced user guide which covers a complicated topic in detail. If
7you're just getting started, you don't need to look at this yet. Instead, start
8with the @{article:Arcanist User Guide}.
9
10= Overview =
11
12//In Subversion, `arc` commands always operate on the uncommitted changes in the
13working copy. If you use Subversion, this document is not relevant to you.//
14
15In Git and Mercurial, many `arc` commands (notably, `arc diff`) operate on a
16range of commits beginning with some commit you specify and ending with the
17working copy state.
18
19Since the end of the range is fixed (the working copy state), you only need to
20specify the beginning of the range. This is called the "base commit". You can do
21this explicitly when you run commands:
22
23 $ arc diff HEAD^ # git: just the most recent commit
24 $ arc diff .^ # hg: just the most recent commit
25
26You can also configure `arc` so it defaults to some base commit, or figures out
27the base commit using a (potentially sophisticated) ruleset.
28
29NOTE: This document describes a new mechanism for determining base commits. It
30is subject to change. There are currently several other mechanisms available as
31well, mentioned in other documents. As this mechanism matures, it should replace
32other mechanisms and defaults.
33
34= Configuring Base Commit Rules =
35
36Base commit rule configuration may be more complicated than you expect. This is
37because people use many different workflows in Git and Mercurial, and have very
38different expectations about what base commit `arc` should pick when run. To
39make matters worse, some of the most common ways of thinking about which commits
40represent a change are incompatible when selecting defaults.
41
42Historically, we tried to use a number of heuristics and simpler approaches to
43determine the base commit, but there is so much diversity in how people think
44about version control and what they expect to happen that some users were always
45unhappy.
46
47Although ruleset configuration is fairly complex, it's powerful enough that you
48should be able to get exactly the behavior you want.
49
50To determine the base commit, `arc` processes //rules// one at a time until it
51gets a match (a rule which identifies a valid commit). The first match is the
52base commit that is used to determine the beginning of the commit range.
53
54A //rule// looks like this:
55
56 arc:upstream
57
58A rule may //match//, meaning that it identifies some valid commit in the
59working copy, or //fail//, meaning that it does not identify a valid commit. For
60instance, the rule `arc:upstream` will //match// if the current Git branch
61tracks an upstream branch, but //fail// if the current Git branch does not track
62an upstream branch, or the working copy isn't a Git working copy. When a rule
63fails, processing continues with the next rule. Some rules can never match but
64produce useful side effects instead. These are described below.
65
66A //ruleset// is a comma-separated list of rules:
67
68 arc:upstream, arc:prompt
69
70`arc` reads five rulesets:
71
72 # `args`, specified with `--base <ruleset>` on the command line when you run
73 a command. This ruleset is processed first.
74 # `local`, specified with `arc set-config --local base <ruleset>`. This
75 ruleset is local to the working copy where it is set, and is processed
76 second.
77 # `project`, specified by setting the "base" key in `.arcconfig`. This
78 ruleset is bound to the project where it is configured, and is processed
79 third.
80 # `global`, specified with `arc set-config base <ruleset>`. This ruleset is
81 global for the current user, and is processed fourth.
82 # `system`, specified in a system-wide configuration file. This ruleset is
83 global for all users on the system, and is processed last.
84
85The rules in each ruleset are processed one at a time until a valid base commit
86is found. Valid rules are listed below. In this list, "*" means "any string".
87
88 - `git:*` Use the specified symbolic commit, if it exists.
89 - `git:merge-base(*)` Use the merge-base of HEAD and the specified symbolic
90 commit, if it exists.
91 - `git:branch-unique(*)` Attempt to select changes unique to this branch (that
92 is, changes between the branch point and HEAD). This rule is complicated and
93 has limitations, see below for a detailed description.
94 - `hg:*` Use the specified symbolic commit, if it exists.
95 - `hg:gca(*)` Use the greatest common ancestor of `.` and the specified
96 symbolic commit, if it exists.
97 - `arc:upstream` Use the merge-base of the current branch's upstream and
98 HEAD, if it exists. (git-only)
99 - `arc:outgoing` Use the most recent non-outgoing ancestor of the working
100 copy parent. (hg-only)
101 - `arc:exec(*)` Execute the specified command. The command should determine
102 the base revision to use and print it to stdout, then exit with return code
103 `0`. If the command exits with another return code, the rule will fail. The
104 command will be executed with the root directory of the working copy as the
105 current working directory.
106 - `arc:bookmark` Use the most recent non-outgoing ancestor of the working
107 copy parent or the most recent bookmark, whichever is more recent. This
108 rule is complicated and has limitations, see below for a detailed
109 description.
110 - `arc:amended` Use the current commit (`HEAD` in Git, or `.` in Mercurial) if
111 it has been amended to include a "Differential Revision:" field. Otherwise,
112 fail.
113 - `arc:prompt` Prompt the user to provide a commit.
114 - `arc:empty` Use the empty state (as though the repository were completely
115 empty, the range will include every commit that is an ancestor of the
116 working copy).
117 - `arc:this` Use the current commit. This means `.` under Mercurial and HEAD
118 under Git.
119
120Rules are also available which change the processing order of rulesets:
121
122 - `arc:args`, `arc:local`, `arc:project`, `arc:global`, `arc:system` Stop
123 processing the current ruleset and begin processing the specified ruleset.
124 The current ruleset will resume processing after the specified ruleset is
125 exhausted.
126 - `arc:yield` Stop processing the current ruleset and begin processing the
127 next ruleset. The current ruleset will resume processing after other
128 rulesets have processed or when it next appears in the processing order,
129 whichever comes first.
130 - `arc:halt` Stops processing all rules. This will cause the command you ran
131 to fail, but can be used to avoid running rules which would otherwise
132 be processed later.
133
134Additionally, there are some rules which are probably useful mostly for testing
135or debugging rulesets:
136
137 - `arc:verbose` Turns on verbose logging of rule processing.
138 - `arc:skip` This rule has no effect.
139 - `literal:*` Use the specified commit literally. Almost certainly wrong in
140 production rules.
141
142= Examples =
143
144Diff against `origin/master` if it exists, and prompt if it doesn't:
145
146 git:merge-base(origin/master), arc:prompt
147
148Diff against the upstream if it exists, or just use the last commit if it
149doesn't:
150
151 arc:upstream, git:HEAD^
152
153As a user, ignore project rules and always use my rules:
154
155 (local) arc:global, arc:halt
156
157As a project maintainer, respect user rules over project rules:
158
159 (project) arc:yield, <defaults>
160
161Debug your rules:
162
163 $ arc diff --base arc:verbose
164
165Understand rules processing:
166
167 $ arc which
168 $ arc which --base '<ruleset>'
169 $ arc which --base 'arc:verbose, <ruleset>'
170
171= Detailed Rule Descriptions =
172
173Some rules have complex operation, described here in more detail. These rules
174are advanced features for expert users wishing to optimize their workflow and
175save a little typing. You do not need to understand the behavior of these rules
176to use `arc` (you can always specify a base commit explicitly).
177
178== git:branch-unique(*) ==
179
180This rule only works in Git.
181
182This rule tries to find commits that are unique to the current branch. It is
183most likely to be useful if you develop using one branch per feature, update
184changes by amending commits (instead of stacking commits) and merge changes by
185rebasing (instead of merging).
186
187The rule operates by first determining the merge-base of the specified commit
188and HEAD, if it exists. If no such commit exists, the rule fails. If such a
189commit exists, the rule counts how many branches contain HEAD, then walks from
190HEAD to the merge-base commit, counting how many branches contain each commit.
191It stops when it finds a commit which appears on more branches than HEAD,
192or when it reaches the merge-base commit.
193
194This rule works well for trees that look like this:
195
196```
197 | * Commit B1, on branch "subfeature" (HEAD)
198 | /
199 | * Commit A1, on branch "feature"
200 |/
201 * Commit M1, on branch "master"
202 |
203```
204
205This tree represents using feature branches to develop one feature ("feature"),
206and then creating a sub-branch to develop a dependent feature ("subfeature").
207
208Normally, if you run `arc diff` on branch "subfeature" (with HEAD at `B1`), a
209rule like `arc:merge-base(master)` will select `M1` as the base commit and thus
210incorrectly include `A1` in the commit range.
211
212For trees like this, `git:branch-unique(master)` will instead select `A1` as the
213base commit (because it is the first commit between `B1` and `M1` which appears
214on more branches than `B1` -- `B1` appears on only "subfeature" while `A1`
215appears on "subfeature" and "feature") and include only `B1` in the commit
216range.
217
218The rule will also do the right thing when run from "feature" in this case.
219
220However, this rule will select the wrong commit range in some cases. For
221instance, it will do the wrong thing in this tree:
222
223```
224 |
225 | * Commit A2, on branch "feature" (HEAD)
226 | |
227 | | * Commit B1, on branch "subfeature"
228 | |/
229 | * Commit A1, on branch "feature"
230 |/
231 * Commit M1, on branch "master"
232 |
233```
234
235This tree represents making another commit (`A2`) on "feature", on top of `A1`.
236
237Here, when `arc diff` is run from branch "feature" (with HEAD at `A2`), this
238rule will incorrectly select only `A2` because `A2` (which is HEAD) appears on
239one branch ("feature") while `A1` appears on two branches ("feature",
240"subfeature").
241
242You can avoid this problem by amending changes into `A1` instead of adding new
243commits, or by rebasing "subfeature" before running `arc diff`.
244
245This rule will also select the wrong commit range in a tree like this:
246
247```
248 |
249 | * Commit A1', on branch "feature", created by amending A1
250 | |
251 | | * Commit B1, on branch "subfeature" (HEAD)
252 | |/
253 | o Commit A1, no longer on "feature" but still on "subfeature"
254 |/
255 * Commit M1, on branch "master"
256 |
257```
258
259This tree represents amending `A1` without rebasing "subfeature", so that `A1`
260is no longer on "feature" (replaced with `A1'`) but still on "subfeature". In
261this case, running `arc diff` from "subfeature" will incorrectly select both
262`B1` and `A1`, because they now are contained by the same number of branches.
263
264You can avoid this problem by rebasing sub-branches before running `arc diff`,
265or by using a rule like `arc:amended` before `git:branch-unique(*)`.
266
267== arc:bookmark ==
268
269This rule only works in Mercurial.
270
271This rule finds outgoing changes, but stops when it encounters a bookmark. It is
272most likely to be useful if you use one bookmark per feature.
273
274This rule operates like `arc:outgoing`, but then walks the commits between
275`.` and the selected base commit. It stops when it encounters a bookmark. For
276example, if you have a tree like this:
277
278```
279 |
280 | * C4 (outgoing, bookmark: stripes)
281 | |
282 | * C3 (outgoing, bookmark: zebra)
283 | |
284 | * C2 (outgoing, no bookmark)
285 |/
286 * C1 (pushed, no bookmark)
287 |
288```
289
290When run from `C4`, this rule will select just `C4`, stopping on `C3` because
291it has a different bookmark. When run from `C3`, it will select `C2` and `C3`.
292
293However, this rule will select the wrong commit range in some cases (for
294example, if the "zebra" bookmark has moved on, the rule will no longer stop on
295`C3` and will select `C2`, `C3` and `C4` when run from `C4`).
296
297== arc:exec(*) ==
298
299This rule runs some external script or shell command. It is intended for
300advanced users who want specialized behavior that can't be expressed with other
301rules.
302
303To use this rule, provide some script or shell command. For example:
304
305 arc:exec(git merge-base origin/master HEAD)
306 arc:exec(/path/to/some/script.sh)
307
308The command will be executed with the working copy as its working directory,
309and passed no arguments. To //match//, it should print the name of a base commit
310on stdout and then exit with return code 0. To //fail//, it should exit with
311any other return code.
312
313= Next Steps =
314
315Continue by:
316
317 - learning about `arc diff` in more detail with
318 @{article:Arcanist User Guide: arc diff}; or
319 - returning to @{article:Arcanist User Guide}.