(no commit message)
[openafs-wiki.git] / devel / GitDevelopers.mdwn
1 Git opens up a number of new options for contributing to OpenAFS. For the first
2 time, it is easy to review code that is pending addition to the OpenAFS tree.
3 In fact, reviewing code is one of the best ways to ensure that the releases
4 that OpenAFS ships remain stable and functional. If you are interested purely
5 in reviewing, then please skip to that section towards the end of this
6 document.
7
8 Git also changes the way that developers interact with the OpenAFS tree.
9 Instead of just having a single version of the tree on your local machine, you
10 have a compressed copy of the entire repository. Additionally, you no longer
11 have to produce patches to send code upstream - any developer can push into the
12 OpenAFS repository directly, through gerrit, our code review tool.
13
14 Whilst git is a far more powerful tool than CVS it is also, inevitably, more
15 complex. This document can only scratch the surface of what's possible with git
16 - there are many, many, documents available that describe git in greater
17 detail, and references to some of them are provided at the end.
18
19 ## <a name="Getting git">Getting git</a>
20
21 Firstly, if your machine doesn't already have it installed, get a copy of the 'git' version control system. This is available for many platforms from their upstream package repositories or, failing that, can be downloaded in both source and binary form from 
22
23 ## <a name="Getting the _OpenAFS repository">Getting the OpenAFS repository</a>
24
25 You can download the entire OpenAFS repository by running
26
27     git clone git://git.openafs.org/openafs.git
28
29 to place the clone in a directory called 'openafs' or
30
31     git clone git://git.openafs.org/openafs.git <name-of-directory>
32
33 to place your clone in a specific directory
34
35 This will give you a complete copy of the OpenAFS repository and unless you are exceptionally short of either disk space, or time, is the approach we recommend. Unlike CVS, you are not just checking out a particular branch, but making a local copy of the project's whole revision history, across all of it's branches. This does make the download pretty large - around 150Mbytes at the time of writing.
36
37 ## <a name="Updating the local copy">Updating the local copy</a>
38
39 When you want to update the local repository with the central OpenAFS one, running
40
41     git pull
42
43 will pull all of the new changes into your local repository, and merge those changes into your current working tree. Note that whilst this is fine when you are browsing the repository, you may want to exercise more control over how upstream changes are merged into your development code.
44
45 ## <a name="Checkout a particular branch">Checkout a particular branch</a>
46
47 The OpenAFS repository contains many branches, most of which are historical and should not be used for new development. Current development should be targetted at 'master' (for feature work, and general bugfixing), or at 'openafs-stable-1\_4\_x' (bug fixes specific to the current stable release). Note that the openafs-devel-1\_5\_x branch is now effectively dead - future 1.5 releases will occur from the 'master' branch.
48
49 A complete list of all branches in the upstream OpenAFS repository can be obtained by running
50
51     git branch -r
52
53 If all you wish to do is browse code, then you can directly check out these remote branches, using
54
55     git checkout origin/<branch>
56
57 For example, to checkout the 'openafs-stable-1\_4\_x' branch:
58
59     git checkout origin/openafs-stable-1_4_x
60
61 Note that if you wish to do development on a particular branch, you should either make a local branch which tracks the remote one, using something like
62
63     git checkout -b openafs-stable-1_4_x origin/openafs-stable-1_4_x
64
65 or, more simply
66
67     git checkout --track origin/openafs-stable-1_4_x
68
69 or by creating a topic branch as discussed below.
70
71 ## <a name="Checkout a particular release">Checkout a particular release</a>
72
73 Every release version of [[OpenAFS]] is marked in the repository by means of a tag.
74
75 A complete list of all tags can be obtained by running
76
77     git tag
78
79 To checkout a particular tag
80
81     git checkout openafs-stable-1_4_10
82
83 Again, whilst a direct checkout of a remote tag is fine for code browsing, it
84 should not be used as a place to start development. If you must do development
85 against a tag, then create a local topic branch with it as a starting point, as
86 is discussed below. However, in general, please don't develop from a particular
87 tag, but instead work from a branch tip. It makes it much easier to integrate
88 your changes!
89
90 ## <a name="Viewing deltas">Viewing deltas</a>
91
92 OpenAFS's original CVS repository used the concept of deltas as a means of
93 grouping a large number of related changes into a single item, which could be
94 easily fetched and referred to. In git, a delta should be simply a single
95 commit. Deltas are represented by means of a special form of git tag, allowing
96 you to locally view the change and commit message that corresponds to each one.
97 In order to keep down the transfer size, deltas are not included in the
98 repository you get when you do a git clone - there are over 10,000 delta
99 references, and having them in your local repository can cause performance
100 issues. If you really wish to be able to locally browse deltas, then run the
101 following
102
103     git config --add remote.origin.fetch '+refs/deltas/*:refs/remotes/deltas/*'
104     git fetch origin
105
106 You can then view a specific delta by doing
107
108     git show refs/remotes/deltas/<branch>/<delta>
109
110 Sadly, historical accidents mean that not all of our deltas can be represented
111 by means of single commit. Where this is the case, a delta-name will have a
112 trailing -part-, where each of these numbers must be used to form the complete
113 delta. This only applies to some deltas created before the git conversion - all
114 deltas created from now on will be single commits.
115
116 ## <a name="Introducing yourself to git">Introducing yourself to git</a>
117
118 Before you begin development, you should let git know who you are. This
119 provides it with a name, and email address, that is used to attribute all
120 commits in your repository, and in any that you share code with.
121
122     git config user.name "Joe Bloggs"
123     git config user.email "joe.bloggs@example.org"
124
125 If you want to make this settings for all of your repositories, then add the --global switch.
126
127     git config --global user.name "Joe Bloggs"
128     git config --global user.email "joe.bloggs@example.org"
129
130 Note that this email address is the address by which you will be identified in
131 [[OpenAFS]]'s revision history - it is also the address to which the gerrit
132 code review tool will send all email related to the review of your code.
133
134 If you plan on making changes to OpenAFS (and why else would you be reading
135 this?) you should probably also grab <b>The change id hook</b> described in
136 <b>Registering With gerrit</b> below. You can grab and apply the hook before
137 registering, and it'll make sure your pre-registration development has the
138 appropriate change IDs in the log. The hook only applies to your openafs
139 development, so you're not going to mess up any of your non-OpenAFS work.
140
141 ## <a name="Helpful git tips">Helpful git tips</a>
142
143 Here are a few other git settings that may be helpful when working with the source.
144
145 Prevent C labels from being treated as function names by git diff:
146
147     git config diff.default.xfuncname '^[[:alpha:]$_].*[^:]$'
148
149 Changes the style used to indicate merge conflicts in source files: 
150
151     git config merge.conflictstyle diff3
152
153 Whitespace handling settings:
154
155     git config apply.whitespace fix
156     git config core.whitespace trailing-space,space-before-tab,indent-with-non-tab
157     git config config.cleanup whitespace
158
159 ## <a name="Starting development">Starting development</a>
160
161 We strongly recommend that you do all of your development upon 'topic branches'
162 This allows you to isolate multiple unrelated changes, and makes it easier to
163 keep your tree in sync with the upstream [[OpenAFS]] one.
164
165 Before creating a new topic branch, running
166
167     git fetch
168
169 will make sure that your repository knows about the latest upstream changes (unlike git pull, this will not update any files that you may have checked out)
170
171 To create a new topic branch:
172
173     git checkout -b <branch>
174
175 For example, to work on a patch to fix printf warnings, based on the current development code, I would do:
176
177     git checkout -b fix-printf-warnings origin/master
178
179 This puts me on a new branch, ready to start writing code. All new development should be based upon the origin/master branch, submissions based upon other branches are unlikely to be accepted, unless they address issues that are solely present in that branch.
180
181 'git add' is used to tell git about any new files you create as part of your patch. If your patch results in any new compilation products (object files, new executables, etc) that git should not be tracking, please make sure that they're caught by the .gitignore mechanism. You can do this by checking that they don't appear in the output from 'git status'.
182
183 'git mv' and 'git rm' are used to move and delete files respectively.
184
185 'git commit -a' is used to commit code to all of the files that git is currently tracking (that is, all of the files that you have checked out from the repository, and all those which you have run git add on)
186
187 ## <a name="When you can&#39;t see the wood for">When you can't see the wood for the trees</a>
188
189 If, in the middle of development, you discover that you've gone down a blind alley, and wish to go back to the state of your last commit
190
191     git reset --hard
192
193 will discard all of the changes you have made since the last commit, or
194
195     git checkout -f <file>
196
197 will restore &lt;file&gt; to the state it was in at the last commit.
198
199 ## <a name="Keeping up with the Jones&#39;">Keeping up with the Joneses</a>
200
201 If you're working on a long running development project, you will find that the point your created your topic branch rapidly recedes into history. At some point (and at least before you share your code with us), you'll probably want to update your tree. There are a number of ways of doing this.
202
203 If you haven't shared your tree with anyone else, then you can use
204
205     git rebase <branch> <topic>
206
207 (Where &lt;branch&gt; is the name of the upstream branch - for example origin/master, and &lt;topic&gt; is the name of the topic branch you are currently working on)
208
209 Note that git rebase changes your local history (it moves the branch point of your topic branch to the tip of the upstream branch), and is a bad idea if others have cloned your repository. See 'man git-rebase' for more details.
210
211 If you can't rebase, then consider either merging the changes onto your local branch, or creating a new topic branch and cherry picking your changes onto it. The man pages for 'git merge' and 'git cherry-pick' provide more detail on these options.
212
213 ## <a name="Sharing your code with us">Sharing your code with us</a>
214
215 How you work from this point onwards depends on how you intend making your code available to OpenAFS. We're still happy to receive submission by patch (by sending your changes to <openafs-bugs@openafs.org>), but it makes it much easier for us if you push directly from your git tree to our code review system, gerrit.
216
217 ## <a name="Registering with gerrit"></a> Registering with gerrit
218
219 To register with gerrit, visit <http://gerrit.openafs.org/> and log in using any OpenID. OpenIDs are available from a large number of internet services, including Google Accounts.  Note that we're aware of problems using LiveJournal OpenIDs to access the service.
220
221 If, when you introduced yourself to git, you gave it a different email address than the one gerrit currently has listed for you, you need to introduce yourself to gerrit under that name, too. Click on 'Contact Information', and select "Register New Email...". If gerrit thinks you are an "Anonymous Coward" then you can fix that on this page as well.
222
223 In order to be able to upload code, you now need to create a ssh key that gerrit can use to identify you, or tell gerrit about one that already exists.
224
225 To create a new ssh key, if you don't already have one, run
226
227     ssh-keygen -t rsa -f ~/.ssh/id_rsa
228
229 The public key for this is now stored in ~/.ssh/id\_rsa.pub.
230
231 To tell gerrit about your key, log in, and go to 'Settings'. Select 'SSH Keys', and paste your _public_ key into the "Add SSH Public Key" box. Click on 'Add' to add the new public key. Now go to 'Profile' and make a note of the Username that is listed on that page.
232
233 To make things easier, set up OpenSSH so that it knows about the defaults for the gerrit server. Edit ~/.ssh/config, and add a section like:
234
235     Host gerrit.openafs.org
236     User <Username>
237     IdentityFile ~/.ssh/id_rsa
238     Port 29418
239
240 (where Username is the username you noted down from the 'Profile' page)
241
242 ### <a name="The change id hook">The change id hook</a>
243
244 Gerrit introduces the concept of "change IDs". This is a unique reference for a particular change, which remains constant regardless of any changes that are made to the implementation. This allows a single reference to be attached to a given modification, irrespective of any rewrites that may occur as a result of review comments. Manually maintaining change Ids is a pain, so gerrit provides a git hook which can be used to automatically add a change Id to any new modifications you create.
245
246 The hook should be downloaded from the [[OpenAFS]] gerrit server by running the following, in the top level of your git tree
247
248     scp -p -P 29418 gerrit.openafs.org:hooks/commit-msg .git/hooks/
249
250 ## <a name="Uploading to gerrit">Uploading to gerrit</a> 
251
252 When submitting to gerrit, it's important to realise that each commit in your branch will become a changeset in the upstream OpenAFS, typically with no modification at our end. It is therefore important that these commits follow some simple rules...
253
254 First, each commit should be complete. The maxim "one change per commit, one commit per change" applies here. Each commit should build and test in its own right. Typically, this means that a change will be in a small number of commits. If, during development, you have created many more than this (for example, you've created a large number of bug fix commits), please use 'git rebase', or cherry pick these commits into a separate tree before uploading them.  Note, however, that "one change" could equate to a change to source code and a change to the corresponding documentation for that code specific change.
255
256 Secondly, each commit should have a meaningful revision log. The internals of git means that we can't easily edit these before pushing them into the tree, so we'd like you to get them right for us! A commit message should be comprised of a single 'subject' line (which must **not** end with a full stop), followed by a blank line, followed by one or more paragraphs explaining the purpose of the patch. Gerrit warns if the 'subject' is longer than 65 characters. If it is intended to fix a bug in OpenAFS RT, then the word 'FIXES' followed by the bug number or comma-separated list of bug numbers should be included on a line of its own. The 'LICENSE' keyword can be used to indicate code which is covered under a license other than the IPL, although please speak to a gatekeeper if you intend using this. An example commit message would be
257
258       Add option to disable syscall probing
259
260       Add a --disable-linux-syscall-probing flag to allow the probing of
261       kernel memory for the syscall table to be disabled at compile time. This
262       helps with Linux architectures which have compile, run or load time
263       issues with syscall probing by providing a band-aid fix until we can
264       improve the configuration logic in this area.
265
266       FIXES 123456
267
268 Thirdly, each commit should have a valid changeID. Manually maintaining these is difficult and error prone, so we would strong advise that you install the changeID hook detailed earlier. This will automatically add a [[ChangeId]] line to your commit message if it doesn't already contain one.
269
270 Fourthly, each commit must adhere to the OpenAFS whitespace policy whereby new commits will not be accepted if they have trailing spaces, spaces before tabs, or indentation without tabs.  Git can be configured to highlight the whitespace policy violation with the following global setting:
271
272     git config --global core.whitespace trailing-space,space-before-tab,indent-with-non-tab
273
274 and 
275
276     git rebase --whitespace=fix
277
278 can be used to automatically fix any policy violations on your local branch before pushing the changes to Gerrit.   Finally, Git 1.8.2 and above can be configured to apply this policy to all local commits:
279
280     git config --global config.cleanup whitespace
281
282 Then commit your changes, for example with a file that contains your log message
283
284     git commit -a -F <your-log-message-in-this-file>
285
286 (where &lt;your-log-message-in-this-file&gt; is a file with the log message)
287
288 Once your commits have a proper commit message and have all whitespace errors fixed, use
289
290     git log -p origin/<branch>..HEAD
291
292 (where &lt;branch&gt; is the upstream branch upon which you are basing this patch).
293
294 to check that what you're giving us makes sense. Then, upload the commits to gerrit using
295
296     git push ssh://gerrit.openafs.org/openafs.git HEAD:refs/for/<branch>/<topic>
297
298 (again &lt;branch&gt; is the name of the upstream branch that you are pushing the changes into, not the name of any local branch you may have been developing on and &lt;topic&gt; is an optional name that can be used to group your commits together for easier reviewing.)
299
300 In this case, refs/for is a literal string. So, if you had been developing against the "master" branch and the change replaced "strcpy" with "strlcpy", you might upload your changes with:
301
302     git push ssh://gerrit.openafs.org/openafs.git HEAD:refs/for/master/strcpy-to-strlcpy
303
304 Although, it would be sufficient to simply issue the command as:
305
306     git push ssh://gerrit.openafs.org/openafs.git HEAD:refs/for/master
307
308 This relies upon the ssh configuration you performed earlier. If it fails to work, please consult the troubleshooting notes at <http://gerrit.googlecode.com/svn/documentation/2.0/user-upload.html>
309
310 Assuming all has gone well, this will have added the entry to the code review queue. The output from git review will give you a change number - this is a unique reference for this particular set of changes. During review you'll be emailed with any comments anyone makes, and can respond to those comments using the gerrit web interface (see the section on reviewing, below). It's possible that issues with your change may be noticed during the review process, and you may be asked to revise it, or update changes to the tip of the tree.
311
312 ## <a name="Revising your change">Revising your change</a> 
313
314 It's possible that your modifications won't be accepted first time. In this case, you need to revise your changes, and resubmit them to gerrit. Please note that this should always be done by modifying your original changeset, _not_ by submitting a new change that makes the required fixes. Either git commit -a --amend, or git rebase should be used to combine your changes with the original changeset, and then you should push this to gerrit with
315
316     git push ssh://gerrit.openafs.org/openafs.git <revision>:refs/for/<branch>
317  
318 where &lt;revision&gt; is the SHA-1 hash of the revised change that follows the word <tt>commit</tt> in the log message, or simply <tt>HEAD</tt> if the revised change is the most recent change on your topic branch.  You can obtain the SHA-1 hash of a commit by using 'git log'.  Note that pushing to <tt>refs/for/...</tt> _requires_ a change-id in your commit message, so that Gerrit can match to the new change with the one you submitted previously.  See <http://gerrit.googlecode.com/svn/documentation/2.0/user-upload.html> for full details.
319
320 ## <a name="Updating your change">Updating your change</a> 
321
322 It's possible that your change may have been made against a tree which is too old for it to apply to the tip. In this case, gerrit will let you know that there is a collision, and request that you update the change to the tip.
323
324 You can do this with
325
326     git rebase origin/master <topic>
327
328 (assuming your patch is against the 'master' git branch, and lives on the &lt;topic&gt; branch)
329
330 When a rebase is performed there may be conflicts that cannot be automatically resolved by git.   The default style of conflict resolution displays the current version of the code on HEAD and the version from the commit that is being rebased.  This level of detail is often insufficient to determine how to resolve the conflict.  Switching to conflict style "diff3" will also show the original version of the code which your commit modified.   Turn on "diff3" by applying the following configuration setting:
331
332     git config --global merge.conflictstyle diff3 
333
334 After you have resolved all conflicts and are once again happy with the commit, simply resubmit your change in the same way as if you had been asked to revise it (see notes above)
335
336 ## <a name="Pulling up a change to a stable branch">Pulling Up a Change to a Stable Branch</a> 
337
338 After a change has passed through the Gerrit Code Review process it will be merged onto the <tt>master</tt> branch of the OpenAFS repository.  The merged commit will then become visible in your local repository the next time a 
339
340     git pull
341
342 command is executed.  OpenAFS releases are not issued from the <tt>master</tt> branch.  Instead releases such as <tt>1.6.11</tt> are issued from stable branches such as <tt>openafs-stable-1_6_x</tt>.  After a change is approved on <tt>master</tt> the change must be pulled up to the stable branch, be modified for any differences between <tt>master</tt> and <tt>openafs-stable-N_N_x</tt> and be resubmitted to Gerrit.
343
344 Start by checking out the stable branch of interest.  For example, if you want to submit a change to the next 1.6 series release you would first checkout <tt>master</tt>.
345
346     git checkout --track origin/master
347
348 or just
349
350     git checkout master
351
352 if you have already created the tracking branch.  Then
353
354     git pull
355
356 to import any new changes to the local repository and fast-forward <tt>HEAD</tt> to the most recent commit merged onto <tt>origin/master</tt>.   It is important to note that if any local changes were committed onto the local <tt>master</tt> tracking branch that a fast-forward operation will not occur.  Instead the local changes and the changes to origin will be merged producing a merge commit.  A merge commit is not compatible with the OpenAFS management of the git repository.   To rebase local changes onto of the <tt>HEAD</tt> of <tt>origin/master</tt> the command
357
358     git pull --rebase
359
360 can be performed.  If there are conflicts when applying the local changes to current <tt>HEAD</tt> of <tt>origin/master</tt> you will have an opportunity to resolve them.
361
362 Once the change you wish to pullup to the stable branch is in the local repository you can use
363
364     git log master
365
366 to find it and record the sha1 identifier for the commit.   Now you are ready to switch to the stable branch for example, <tt>openafs-stable-1_6_x</tt>.   If you do not already have a local tracking branch execute
367
368     git checkout --track origin/<stable-branch>
369
370 and if you do then execute
371
372     git checkout <stable-branch>
373
374 After switching branches you might be told that the local branch is behind the <tt>origin</tt>.  If so, execute
375
376     git pull
377
378 to fast-forward the branch <tt>HEAD</tt> to the most recent commit.   Now you can pull the commit from <tt>master</tt> by using performing a cherry-pick:
379
380     git cherry-pick -x <sha1>
381
382 This will create a new commit on the current branch with a commit message that has been modified with
383
384     (cherry picked from commit <sha1>)
385
386 This commit message will include the <tt>Change-Id</tt> from the original submission to Gerrit.  It is important that the <tt>Change-Id</tt> values submitted to Gerrit be unique but Gerrit will not enforce this.  You <b>must</b> remember to edit the commit message and remove the old <tt>Change-Id</tt> using
387
388     git commit --amend
389
390 so that the Change ID Hook will generate a new <tt>Change-Id</tt> before pushing the change to Gerrit.
391
392 Once the commit message has been ammended and any other required changes are made it is time to push the change to Gerrit:
393
394     git push ssh://gerrit.openafs.org/openafs.git HEAD:refs/for/<stable-branch>/<topic>
395
396 The change will then be evaluated by the OpenAFS Release team for incorporation into a future stable release.
397
398 ## <a name="Submitting by patch"></a> Submitting by patch
399
400 If all of this seems too daunting (and please don't let it put you off) you can still, of course, submit patches by email.
401
402     git diff HEAD
403
404 will give you the set of changes if you don't do local commits. If you make topic branches, and commit things locally, but don't want to go through the whole gerrit process,
405
406     git diff master..<topic>
407
408 will give all of the changes between the branch point of you topic branch (assuming you branched from 'master') and the last commit.
409
410 A better approach is to generate a patch file.  To do so commit your changes to a local branch in your repository as you would if you were submitting to gerrit.  If your changes are against the "master" branch, instead of pushing the patch execute the command:
411
412     git format-patch origin/master..HEAD
413
414 For each commit on your local branch after the most recent patch on "master", a separate patch file will be generated.
415
416 Regardless of which approach you use, you can e-mail the changes to <openafs-bugs@openafs.org> as before. Note, however, by doing this you're making someone else take the patch, create a topic branch in their local tree, apply the patch, push it into gerrit, and become responsible for managing the review process. Things would be much more efficient if you pushed into gerrit yourself. Please?
417
418 ## <a name="Reviewing changes">Reviewing changes</a> 
419
420 We'll now look at how changes that have made it into gerrit can be reviewed. All code review now happens via the <http://gerrit.openafs.org> interface. You should log in there as detailed above (using any OpenID), and make sure that the email address points to somewhere you'll read regularly.
421
422 You'll be presented with a list of patches requiring review or, if someone has asked, patches you've been explicitly requested to review. There are two types of review - Code Review and Verification. Code Review means that you have read through the code, and are satisfied that it works properly, follows the tree's style, and generally doesn't suck. Verification means that you have taken a copy of the patch and tested it. We hope to eventually automate the verification step, but for now both must be performed by hand.
423
424 To perform a code review, go through each of the diffs in the current changeset for the code you have decided to review. You can double click on a line to leave a comment. Once you have completed commenting, click on the 'Review' button that's about 3/4 of the way down the page containing the list of patch sets. You will then be asked to score the patch, with a range from -1 to +1. -1 means that you don't think the code should be applied, +1 means that it is good to apply. You can also leave further, general, comments for the patch submitter.
425
426 Note that no matter how many +1 or -1 comments a patch receives, the gatekeepers can override these to either permit or forbid submission. Also, at least one gatekeeper must approve a patch before it can be submitted to the tree.
427
428 To verify the code, pull the git copy into your local tree, using the git command listed as part of the patch details. For sanity's sake, we'd strongly recommend you do this pull into a topic branch you've created for the purpose. Build it, test it, and report your results. Note that a single -1 to verification can block patch submission, so please use these options wisely. If in doubt, score 0 and leave your comments. Please indicate when verifying which platforms you have tested on, and what testing you performed.
429
430 And that's pretty much it. All of this is very new. If you encounter any problems at all, please ask for help on IRC in #openafs, Jabber in <openafs@conference.openafs.org> or on <openafs-devel@openafs.org>. Private pleas may be addressed to <simon@sxw.org.uk>
431
432 ## <a name="Further Reading">Further Reading</a> 
433
434 Git Magic <http://www-cs-students.stanford.edu/~blynn/gitmagic/>
435
436 Git User's Manual <http://www.kernel.org/pub/software/scm/git/docs/user-manual.html>
437
438 Git Community Book <http://book.git-scm.com/>
439
440 Gerrit Documentation <http://gerrit.googlecode.com/svn/documentation/2.0/index.html> (only the first 'User Guide' section of this document is relevant)
441
442
443
444 Five advanced Git merge techniques <http://blog.ezyang.com/2010/01/advanced-git-merge/>
445
446 ## <a name="Acknowledgments">Acknowledgments</a> 
447
448 Thanks to everyone who has reviewed this document, and offered corrections and contributions.
449
450 -- Simon Wilkinson - 07 Jul 2009