Working with Git

Hints, tips, conventions and dire warnings about working with Git in the Shibboleth project go here.

Note that these are all notes about working with Git in this specific project; Git has such a flexible array of composable tools that each project needs to establish its own conventions, and one project's workflow may not suit another project's requirements.

Files and Directories

Git does not track empty directories. If there's a reason you need the Git repository to contain a directory with no contents (one common case is a src/main/resources or src/test/resources expected by Eclipse) then the closest you can get is a directory with a hidden placeholder file. One common convention for this is to use an empty file called .gitkeep.

Tags

Lightweight tags created by git tag mytag are just synonyms for commits; they have no associated comment and no signature. You can use these freely in private repositories as bookmarks, creating, deleting or moving them as appropriate. You should never push lightweight tags to public repositories.

Any tag pushed to a public repository should have been created as an annotated (git tag -a) or signed (git tag -s) tag. These have an associated comment: this can be provided through the -m option; if you don't provide one, an editor will be started for you.

Most tags in our public repositories are used to label releases, and are given corresponding names without including a leading "v" or "V". For example: 1.2.11.3a2.7RC1.

Release tags should always include a comment of the form Tag version release and be signed using the releaser's GPG key using the -s option. For this to work, you will need to set your user-level ("global") configuration appropriately:

git config --global user.signingkey 0xkeyId

Then, sign as follows:

git tag -s -m "Tag 1.2.2 release" 1.2.2

You will be prompted for your GPG passphrase.

When you want to share a tag by pushing it to a public repository, push the single tag by name using git push remote tagname, for example git push origin 1.2.3

Although some online documents may suggest the use of git push --tags, you should never use this, as it pushes all tags in your repository.

Branches

The main branch in a Shibboleth project repository is where work on the next feature release occurs. Maintenance work for previously released versions of the software is performed on branches named maint-release, such as maint-3 or maint-3.4.

Feature and topic branches are named dev/something, such as dev/IDP-1234.

Comments

In Git and many other modern version control systems, comments on commits are by convention divided into two parts. Some tools assume this structure, so it's an important convention to stick to whenever you can.

The first line of a comment should be a summary. This should be short; limiting it to between 40 and 50 characters allows it to be properly displayed in things like short logs. If you want to include a JIRA case number, put that at the start of the line separated out by a " - " sequence, e.g.:

ABC-123 - fix typo in log message

If more detail is required in the comment than can be conveyed by the summary, it is followed by a blank line and any amount of detailed additional text. The blank line is important to some tools, so don't omit it. The additional text should be line-wrapped at something like 72 columns or thereabouts, as not all contexts in which it is displayed will wrap lines for you and overlong lines will make the comment hard to read.

Tim Pope's note about Git commit messages goes into more detail, and is well worth reading. A later summary of best practices is here.

Referring to the JIRA case using a URL sometimes makes sense if done in the additional text, not in the summary. Referring to other URLs can make sense too, as long as you're referencing something that won't move elsewhere in the lifetime of the codebase. For example, referring to another commit by referring to a web view URL is prone to failure in the longer term.

Workflow

Simple changes to repositories are often committed directly to the appropriate main or maint- branches. Permissions on these branches is set to enforce the rule that contributions should be "fast-forward" only; merge commits are not permitted.

More complex changes, or feature development over a longer period of time, can be performed in "development" branches named under dev/, such as dev/IDP-1234. Permissions for these branches allow committers complete freedom to create, delete, and use merge commits.

The normal workflow for such a development branch would be:

  • Start from the main branch: git checkout main 

  • Create local development branch: git checkout -b dev/my-feature 

  • Publish development branch: git push -u origin dev/my-feature

  • ... perform feature development ...

  • ... any series of commits, merges, rebases, squashes, etc. ...

  • Prepare to merge development branch: git rebase -i main 

  • Move back to parent branch: git checkout main 

  • Bring in the feature's final set of commits: git merge --ff-only dev/my-feature 

  • Publish: git push 

  • Delete remote development branch: git push --delete dev/my-feature

  • Delete local development branch: git branch -d dev/my-feature

Permissions

To support (and to some extent enforce) the above workflow, our repository software implements the following rules:

  • Committers can perform any operation (fast-forward push, rewind, create, delete, merge) on branches under the dev/ prefix.

  • All branches not under dev/ are protected: they only accept fast-forward pushes from committers, can not be created or deleted, and do not accept merge commits.

  • Committers can create release tags (named starting with a digits) only. Release tags can not be moved or deleted.

  • Administrators can do most of the things committers are restricted from, to allow fixing up certain classes of mistake or taking care of cases we didn't think of.

  • No-one can push merge commits to a protected branch.