Home Uniquely NZ Travel Howto Pauline Small Firms Search
An Introduction to Git and it's
Application to Cinnamon Applet Development

The first part has an introduction to Git, its advantages, enough explanation of how it works and its basic commands to be able to use it for a simple software development. There is a comprehensive list of references I go back to time and time again.

The second part is specific to the use of Git for Cinnamon Applet and Desklet Development and use of the Cinnamon Repositories on GitHub.

An Introduction to Git - what is it and why use it?

Git is a very sophisticated and versatile distributed version control system where every folder with a Git repository on every computer becomes a full-fledged independent repository with complete history and full version-tracking capabilities, without requiring continuous online access or a central server for its use. Most programmers, developers or whatever one likes to call them, eventually realises that they need some way of keeping a track of what they have done and the difficulties compound rapidly when several people are involved and become impossible with a large team without some form of control system.

I started to use Git because I had written several applets and I believed once one has made software available one has a responsibility to maintain it properly and make sure that future continuity is guarantied - at that time there were too many applets where there have been no updates for 3 or 4 years and no easy route for anyone else to take over their updating as Cinnamon changes. By use of Git and GitHub (a free, for public use, central repository service) as a remote server others could see, clone and fork my work and even send me patches. It also made it much easier to locate and fix any bugs. The situation has changed and Mint/Cinnamon have their own repository for applets and disklets which authors use and my applets are nowintergrated into their system which also uses Git and GitHub so my earlier work was extremely useful and I can still go back to, in theory, it if I need to. This document covers both my earlier work, which is largely applicable to any project, and then on to the specific use of Git for Cinnamon Applet development using the Cinnamon repository on GitHub.

I must starting by saying that using Git is not easy and requires use of the terminal/command line and only use leads to understanding. I will however, explain why it is so useful that it is worth the learning curve. Firstly a major advantage over other version control systems is everything is local, you can work for long periods and then push the details to a remote server or pull updates from it once and at your convenience - you do not need to be continuousl connected to a network. The second major advantage is that there is a simple mechanism to break away from the main development path (usually known as the master) to try things out simultaneously in many different areas and them bring them all back together through branching and merging. In fact many projects have a stable and development branches always running in parallel with the development branch periodically merged into the stable branch. In fact this can be done by a large team all working in parallel yet able to bring their work together. One can start a new branch from the current point (referred to as the HEAD) or from points in the past and then switch between branches very easily and commit a series of changes on each branch. Every time you change (checkout) branch your working directory reflects what's in that branch and its history. If you are not successful you can just delete the branch or if you have solved the problem you can merge it back along with changes pulled from the server and changes in other branches. This all sounds very fine in theory but what happens if the same file has been changed in different places? The system detects that and forces you to resolve the differences. In its most basic form Git marks the conflicts within the files so you can edit them. It also allows you to use meld or other GUI utilities to help by showing you the files side by side and move, delete or otherwise resolve by editing the differences. I have marked some of the terms and Git commands you need to understand in bold. In fact the terms above include almost all the basic commands you will need to know, only add, fetch, status, rebase, diff and help are missing from the essential command list.

There are some GUI interfaces to use Git but you really do need to understand Git by running it from the command line before using them. The gitk visualiser is however very helpful, if not essential. But before we start to look at the important commands we need a basic understanding of the principles behind how Git works. I took a long time to use Git effectively and that was because I do not think I really understood the fundamentals of Git.

But to use Git it is essential to really understand more of the way it works. Firstly Git has a working directory where you edit files and a repository where a series of snapshots of the working directory are stored very efficiently. Note we are talking about snapshots not differences. You can load a snapshot into your working area by a checkout (as in supermarket!) To start with you probably have the simplest case with a single path (branch) by default called master and you are checked out at its HEAD. When you have made a series of edits on the files in your working area you need to take another snapshot and add it into the Git repository - this is called a commit and once that is done it is very difficult to lose data especially if your local repository is connected to a remote repository on a different machine. Git is also very efficient, for example the complete Git repository for the linux kernel back to 2005 when it was switched into Git is only 2 Gbytes and that it has every commit (snapshot) made since then (~450,000 of them). I suspect there are few larger and more complex projects.

Security is guaranteed as each snapshot (commit) is identified by it's checksum. The mechanism that Git uses for this checksum is called a SHA1 hash. This is a 40-character string composed of hexadecimal characters (0-9 and a-f) and calculated from the contents of a file or directory structure in Git. This is a unique identifier which also confirms that the contents have not been changed. These checksums are also the fundamental way you identify a particular snapshot to, for example, return to and reload your working area (checkout) or start a branch from. Each commit also has information attached to it including its parent(s), a message and the name and email of who made the commit.

The above are the most vital things you have to understand but are a simplification as the adding of the snapshot to the repository is actually two steps, the first is that individual files are added to a staging area and then those in the staging area are in the list to be committed. This unique “staging area” concept does allow you to determine exactly which changes shall be included in your next commits, even down to single lines. It is possible to modify the file further but the modifications will not be in the next commit unless it is restaged. Once a snapshot has been committed to the repository is very difficult to change. I have so far never needed to separately stage files and use a simplified command which adds and commits all changed files and appends the message. Even so you must be aware that it is actually two steps and eventually you may need the flexibility. You should note that many people call the staging area the index, a term I find confusing.

So far I have only been covering local activity and have ignored the fact that in most cases you will be working collaboratively or making your work openly available through a remote Git repository which you will push your commits to and others will pull or fetch the changes back to your machine to merge into their work, likewise you may pull from their or other centralised repositories. So it is time to look at how you might want to use Git, in order of complexity.

  1. Take an existing 'project folder' such as an applet you are developing and add a Git repository to it so you have an audit trail for your changes and backup for mistakes.
  2. Combine the above with a hosted Git service like GitHub so you can make the work available to others and you can work on several machines as well as having a remote backup. This is what I initially did with my applets.
  3. Clone a copy of an existing repository to make modifications for your own purposes, possibly adding patches which have not been included in the master at that time or testing patches with the intention of providing feedback or to get round problems. This is what I did to test and incorporate a patch to the kernel I was using on the Helios to get round some problems with the Skylake architecture used without waiting 6 months it to reach a kernel I could use.
  4. Fork an existing project repository so you can contribute to a major project such as Cinnamon. Cinnamon has close to 1 million lines of code, over 5000 commits, 122 releases, 373 forks and 143 contributors. Only a handful of people can make changes to the master, in the case of Cinnamon 95% of commits come from just two people - it is the only way to keep control. In the case of the Linux kernel almost everything is finally approved by Linus Torvalds himself. A fork starts as a copy of the main repository on the server which is then cloned to your machine after which it can be updated with your contributions. You then create a pull request which, if and when approved, can be pulled into the master and merged. This allows total control over the process. This is how Cinnamon Applets are now managed.

Reference Information

I have been looking for a good place to add some reference information and this seems as good a place as any before we get on to the real subject of this document as the information I can give in a short article is only a small but relevant part of the whole Git story. I will also be referring to some of the documents here several times in the text that follows.

We can now get to the main business of this document.

Using Git to manage software development

This section covers use of git in general - it is no longer specific to applet development although my early use of git and then GitHub for applets is used in some of the examples.

Cinnamon has now adopted the use of GitHub for applet development and maintenance and there is a cinnamon-spices-applets GitHub repository which holds all the applets which have been written other than system applets (a total of about 230). This imposses addition constraints and this is all covered in later sections.

I already had several applets already published before I decided it was time to bring them under proper control by using Git. They were already sub folders within an existing folder and I turned the whole into a Git repository ( I will come to details of how latter) - this was kept entirely local to start with and for the period where I was trying to put earlier versions together to form a history to start everything going. Once I had worked back through some critical parts of the development I added a remote repository to one of of the Git hosting services. GitHub is the single largest host for Git repositories, and is the central point of collaboration for millions of developers and projects. A large percentage of all open source Git repositories are hosted on GitHub including Mint and Cinnamon. So while it's not a direct part of the Git open source project, there's a good chance that you'll want or need to interact with GitHub at some point which is why I chose it.

What this means, without going into too many details yet, is that anyone can 'clone' (obtain a local copy of) a Git repository which will give them a Working Directory (a folder usually within your home folder) containing a current copy of the repository folders with a hidden folder within it called .git which contains all the information to go back through the development history and see every change made between 'commits' which can be thought of as a sort of snapshot. One can also go back ('checkout' in GitSpeak) to any of the 'commits' and every file in the working area will change to what it was at that time. If the latest version does not work on your machine you can go back and collect an older version. All these important points can have had a 'tag' added to them to make them easy to find. As time goes on you can 'pull' or 'fetch' the latest version down without having to clone again.

Git is however not the most user friendly piece of software and is mostly used from the command line. However what I have described above is not difficult and there is a first class graphical visualisation program called gitk which makes it easy to see what the commands have done and in the limit you can just clone another time!

That is only a tiny part of what Git can do. You can edit the files for your own purposes and can 'merge' the latest updates in automatically provided there are no conflicts. Even if there are there are ways to handle the conflicts manually. One of the most powerful features is that you can create 'branches' to make your modifications and test them and then merge them back to the master.

Git is now the most widely used source code management tool, with over 40% of professional software developers using Git as their primary source control system. Before you worry the whole installation and setup of Git is done in about 9 terminal commands you can cut and pasted from below. This assumes you have already set up an account on GitHub which is easy and well explained on their site and their is a repository you want to use..

sudo apt-get install git gitk leafpad meld

git config --global user.name "your_username_on_github"
git config --global user.email "yourname@your_git_email"

git config --global core.editor leafpad
git config --global diff.tool meld
git config --global merge.tool meld
git config --global color.ui auto
git config --global push.default simple

git config --global credential.helper 'cache --timeout=7200'

git clone https://github.com/username/repositoryname
cd repositoryname

The apt-get install command not only installs Git but its visualiser (gitk), a merging program (meld) and a simple editor (leafpad) which will only be used by Git.

The first two pieces of configuration are to provide a suitable name and email which is added to every commit you make. If you intend to use GitHub there are great advantages to having them the same and using your GitHub username rather than your full name.

The use of a simple text editor called leafpad avoids all sorts of problems in using the same editor for your editing of files and within Git. meld is a really good difference tool and is also set to be the default for hand crafting conflicts in a merge. The color.ui auto allows suitable terminal programs to display some information in colour. The push.default simple is to make sure that you only push your master to the remote repository by default - it should not be needed with the latest versions of Git but I started before that was the default so some of my early repositories seemed to have a different setting. I have put details of how to obtain the most recent version of Git in an Appendix.

The --global credential.helper 'cache --timeout=7200' means that Git will save your password in memory for some time after you have entered it the first time in a session - here I have set it to 2 hours.

Everything in the configuration with --global is available throughout your machine but needs to be redone on other machines. the command git config --list shows your current configuration. Run it outside of a working directory to see just the global settings you have made.

pcurtis@defiant:~$ git config --list
credential.helper=cache --timeout=7200

The git clone sets up a folder (the Working Directory) which is below the folder it is called from, normally your home folder. It populates the Working Directory and including the local Git repository (.git) with all the history. It also sets up the links to the remote required to push and pull from it. The repository can be a repository you have set up or, in the case of cinnamon applets you set up and clone a fork of https://github.com/linuxmint/cinnamon-spices-applets ie in my case the fork is at https://github.com/pdcurtis/cinnamon-spices-applets and the local folder is cinnamon-spices-applets

gitk - a graphic viewer for Git.

Now have a look at what you have with gitk which is a graphic viewer for Git. gitk is run in a terminal from within the folder containing the Git repository and helps enormously in helping one see what one is doing.

cd ~/cinnamon-applets
gitk &

These commands change to the cinnamon-applets folder in home from wherever you were then calls gitk. The & at the end makes gitk run a separate process so the terminal is available to continue with git commands. Note: the command is just gitk without a git before it

The following is a typical output from gitk when I was doing some work on Cinnamon. I have deliberately chosen to show something a little more complex than you will see with my applets to show its power - but you should not worry about the details! I plan to show an example of the simplest possible output latter. The following example was actually after a second attempt at merging in a pull request from somebody else's repository having created a new branch for my testing (development) and a extra branch for a particular test when I was investigating a menu problem with Cinnamon (menu-issue).

Firstly you will note that gitk provides a lot of information:

At the top left panel is a list of all the commits with their commit messages and the two panels to the right identify the author, his email and the date. The commits are linked to their parents and children and the SHA1 hash IDs are shown. The position of master, the remote, the branches and tags are shown with the active branch in bold.

Below is full information on the highlighted commit at the top and the changes in every file (in diff format) is normally shown below although it can easily be switched to show a tree on the right with view to the left where every file can be examined.

For completeness I should note there are a number of obscure options which can be specified when you start gitk one of which is worth noting and one I almost always use which is --all which shows all of the branches as will as the active one:

gitk --all &

I am not going into gitk more here as there are two excellent web pages of explanation on the internet

Back to exploring git after the diversion to look at gitk

Tags. Tags are just pointers and you can use the SHA1 IDs (Hashes) instead but they are very convenient for checking out previous versions.

Note: Tags are important in my own initial repository and many developments but are not used in cinnamon-spices-applets so my example is from before I started using the cinnamon-spices-applets repository.

Find a list of tags. Now lets go back to NUMA version 2.4.2 which was the one used for 18 months with Cinnamon versions up to 2.8. First we look and see exactly what the tag is before by git tag before the next step where we call checkout:

pcurtis@defiant:~/cinnamon-applets$ git tag

Load an earlier snapshot into the working folder. We can now checkout NUMA-2.4.2

pcurtis@defiant:~/cinnamon-applets$ git checkout NUMA-2.4.2
Note: checking out 'NUMA-2.4.2'.

You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by performing another checkout.

If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -b with the checkout command again. Example:

git checkout -b <new-branch-name>

HEAD is now at bc5b1e3... 2.4.2 Changes to automatically select first active interface at start-up Uploaded 6 May 2014

First note that Git provides a lot of useful information in the terminal.

Now have a look at what you can now see the change with gitk or look at the files in an editor. It is sensible to use gitk to follow even the simple changes here to get a feeling of what is going on and to be able to see the features of each individual commit (remember to refresh gitk from the its file menu). I found gitk very useful, arguably essential for me, to understand Git although it could be considered an overkill at this point

Using checkout to return to the current version. Time to go back to the current version which is master using checkout

pcurtis@defiant:~/cinnamon-applets$ git checkout master
Previous HEAD position was bc5b1e3... 2.4.2 Changes to automatically select first active interface at start-up Uploaded 6 May 2014
Switched to branch 'master'
Your branch is up-to-date with 'origin/master'.

Have a look at the files and use gitk and you will find everything is as it was when you cloned it - easy wasn't it?

Useful Git Commands

Before we go much further you need to know some details of a few of the most important commands namely help, init, config, status, add, commit, log, checkout, branch, diff, merge, clone, pull, push, fetch, rebase, reset and stash. Those commands and the explanation which follows is very much a subset or what is available. Even so well 0ver 95% of what I do only uses those commands and their various options. For the rest there are some good 'cheat sheets', including one from GitHub, which you may want to print and stick on the wall.

You should use the git help command to get extra information before trying anything complex the first time or use in conjunction with the The Pro Git book, written by Scott Chacon - I download the pdf from that link and keep it on my desktop .

I am also putting together a list of every command I have every used as an 'aid memoir' and may add it to a repository and make it available.

The following covers the commands I use most often and will get you started, you will probably never need all of these.

How to get an overall status report: git status shows you the most important information such as the branch you are on, what files are untracked, changed and staged ready to commit as well as any problems such as incomplete merges. You will use it frequently.

git status

pcurtis@defiant:~/cinnamon-applets$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)
              modified: README.md
              modified: status.txt
no changes added to commit (use "git add" and/or "git commit -a")

You will see that git is normally quite verbose and gives useful and help information on what you can do

How to get help for any command: You can get instant information about a command and any options it takes by git help <command>. You can get some useful information by calling help for help itself and try git help everyday which gives access to one of several guides and almost does away with writing here! Try them out:

git help <command>
git help help
git help everyday

How to commit a change. A commit takes a snapshot of all the changes you have made in the working directory and saves it complete with the information on the commiter (email and name you set up earlier) and a message. It is then identified by the SHA1 associated with it. I am deliberately simplifying by leave out any reference to staging as you will not need it yet if ever as the following format does everything in one go.

git commit -a -m "Commit Message"

The -a effectively adds every changed file to the commit and the -m allows the message to be given in the same command.

Get a list of commits you have made. The git log enables you to look at a log of what you have done which is most useful to see the commits you have made so you can find their IDs. It shows every commit unless you escape by q but option -n 5 enables you to limit the number to say 5. A useful format to get at the SHA1 hash in short format is obtained by option --oneline Try them out.

git log
git log -n 10
git log --oneline -n 8

Go back to an earlier snapshot by checkout <ID> where ID can be the start of the SHA1 hash, the start of the message or a tag or relative to the HEAD. eg

git checkout HEAD~3

takes you back three commits.

Start a new branch: Assuming you have already used checkout to go to a place in the history and if you look at the terminal output you will see that it tells you how to start a branch from that place and switch to it by to make changes. The most common commands are:

git branch <new-branch-name>

Create a new branch from your current HEAD position

Switch to any branch by:

git checkout <branch-name>

And switch back to the master branch by

git checkout master

Merging back work in a branch to your master when you have finished. Following called from master.

git merge <branch>

You may have conflicts when you merge if the master has updates to the same areas of files which you have pulled from a remote repository and the following are two options of what to do.

git merge --abort
git mergetool -t meld

Delete a branch - you are only permitted to do this when the branch has been merged into another branch to avoid loss of information although you can force a delete with loss of what has been done in the branch by the -D option.

git -d <branch>

Marking a point such as a version by adding a simple tag: You can mark a point with a simple tag (label) using git tag <tagname> at the current position or add an ID for a different point by git tag <tagname> <ID>. You can get a list of your tags by just git tag without any parameters:

git tag tag_name
git tag tag_name HEAD~5
git tag

Keeping up-to-date with your own remote repository:

This is very rarely used if you are working with a GitHub forked repository unless possibly when you are setting up a new machine or switching machines.

git pull

This assumes there are no conflicting changes in work you have done and what has been done on the remote master this will download all the new commits. For information this is from master/origin ie the master on remote origin which was all set up when you cloned so you only need to know about pull at the present!

Keeping up to date with an upstream remote repository

If you are working on GitHub and have forked a repository you are more interested in keeping up to date with that repository which by convention is called upstream.

First there is a one off activity to 'connect' to that repository

git remote add upstream git://github.com/linuxmint/cinnamon-spices-applets

secondly there you need a couple of commands every time you want to fetch and merge the changes

git fetch upstream
git merge upstream/master

If you are working in this mode you now need to push all those changes to your fork of the repository by

git push origin

What are the differences between two commits?

This can be as simple as what was in the last commit but sometime you want to know everything that differences between versions or in your current branch. The full command is git diff <ID1> <ID2> The first example is just the last commit, the second example is the difference between the last but two and the last but one commits, both with the output in the terminal (stdout). The third calls your specified gui difference program (which we have already configured to meld) which gives a more useful look at what we have. The last example sends the diff output to a file which can be used as a patch so it has a --no-color parameter as some patch application programs do not like a coloured input!

git diff HEAD~1
git diff HEAD~2 HEAD~1
git difftool HEAD~2 HEAD~1
git diff --no-color HEAD~3 > last_three_commits.patch

Applying a patch you have been sent. This is not really basic stuff but I did mention patches above and git has a simple internal command to apply a patch created by git diff . If you start a new branch (for safety) where the patch starts and switch to it you can use git apply < patchfile.patch to apply it to the branch (assumes file in Working Directory), assess it and merge it if you like it.

git apply < patchfile.patch

For information there is another command which creates individual patches for each commit in an email friendly format called git format-patch and if you receive one then you can use git am < patchfile.patch and it will recreate the original patch complete with the commit message and the other commit information.

Push back to a remote repository - you can not push back to my repository but if you have set up your own repository or have forked an existing one and cloned from it all the links will be set up for a basic push from your master to the remote master

git push

The small set of commands above combined with knowledge of how to merge conflicts is all you really need to get most of the value out of Git, in most cases I have given a few extra options to wet your appetite but you can get away with very little.

Exploring setting up branches and merging conflicts:

I have referred several times above to conflicts and sooner or latter one has to face up to dealing with one. The following shows setting up two branches, editing a file called git_applet_intro.txt in both until they are in conflict and merging them using meld and then cleaning up by deleting the merged branches.

pcurtis@matrix:~$ cd cinnamon-applets
pcurtis@matrix:~/cinnamon-applets$ git status
On branch master
Your branch is ahead of 'origin/master' by 3 commits.
(use "git push" to publish your local commits)

Changes to be committed:
(use "git reset HEAD <file>..." to unstage)

new file: git_applet_intro.txt

pcurtis@matrix:~/cinnamon-applets$ gitk &
[1] 6049

pcurtis@matrix:~/cinnamon-applets$ git commit -a -m "Add help file"
[master 85638c3] Add help file
1 file changed, 74 insertions(+)
create mode 100644 git_applet_intro.txt
pcurtis@matrix:~/cinnamon-applets$ git branch test1
pcurtis@matrix:~/cinnamon-applets$ git branch test2
pcurtis@matrix:~/cinnamon-applets$ git checkout test1
Switched to branch 'test1'
pcurtis@matrix:~/cinnamon-applets$ git checkout test2
M git_applet_intro.txt
Switched to branch 'test2'
########### Made Edits to git_applet_intro.txt
pcurtis@matrix:~/cinnamon-applets$ git commit -a -m "Changes to test2 branch"
[test2 771aa7b] Changes to test2 branch
1 file changed, 4 insertions(+), 1 deletion(-)
pcurtis@matrix:~/cinnamon-applets$ git checkout test1
Switched to branch 'test1'
############## Made Conflicting Edits to git_applet_intro.txt
pcurtis@matrix:~/cinnamon-applets$ git commit -a -m "Changes to test1 branch"
[test1 20e307c] Changes to test1 branch
1 file changed, 1 insertion(+), 1 deletion(-)
pcurtis@matrix:~/cinnamon-applets$ git checkout test2
Switched to branch 'test2'
pcurtis@matrix:~/cinnamon-applets$ git merge test1
Auto-merging git_applet_intro.txt
CONFLICT (content): Merge conflict in git_applet_intro.txt
Automatic merge failed; fix conflicts and then commit the result.
pcurtis@matrix:~/cinnamon-applets$ git status
On branch test2
You have unmerged paths.
(fix conflicts and run "git commit")

Unmerged paths:
(use "git add <file>..." to mark resolution)

both modified: git_applet_intro.txt

no changes added to commit (use "git add" and/or "git commit -a")
pcurtis@matrix:~/cinnamon-applets$ git mergetool


Normal merge conflict for 'git_applet_intro.txt':
{local}: modified file
{remote}: modified file
Hit return to start merge resolution tool (meld):
########## Resolved conflicts in meld which was opened at this points. Changes saved before closing it. We now need the commit to complete
pcurtis@matrix:~/cinnamon-applets$ git commit -a
[test2 a6960b1] Merge branch 'test1' into test2
pcurtis@matrix:~/cinnamon-applets$ git checkout master
Switched to branch 'master'
Your branch is ahead of 'origin/master' by 4 commits.
(use "git push" to publish your local commits)
pcurtis@matrix:~/cinnamon-applets$ git merge test2
Updating 85638c3..a6960b1
git_applet_intro.txt | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
pcurtis@matrix:~/cinnamon-applets$ git branch --merged
* master
pcurtis@matrix:~/cinnamon-applets$ git branch --no-merged
pcurtis@matrix:~/cinnamon-applets$ git branch -d test1
Deleted branch test1 (was 20e307c).
pcurtis@matrix:~/cinnamon-applets$ git status
On branch master
Your branch is ahead of 'origin/master' by 7 commits.
(use "git push" to publish your local commits)

Untracked files:
(use "git add <file>..." to include in what will be committed)


nothing added to commit but untracked files present (use "git add" to track)
########## Deleted git_applet_intro.txt.orig

pcurtis@matrix:~/cinnamon-applets$ git status
On branch master
Your branch is ahead of 'origin/master' by 7 commits.
(use "git push" to publish your local commits)

nothing to commit, working directory clean
pcurtis@matrix:~/cinnamon-applets$ git branch -d test2
Deleted branch test2 (was a6960b1).
pcurtis@matrix:~/cinnamon-applets$ git status
On branch master
Your branch is ahead of 'origin/master' by 7 commits.
(use "git push" to publish your local commits)

nothing to commit, working directory clean

Merging conflicting files: Looking at the above it is obvious that another very important feature which Git has to have is the ability to merge changes from different places such as the remote files on the server or your own branches. This can be done from the command line and it automatic identifies the common root and does a three way merge into the merged file provided there are no conflicts (ie the same lines changed in both files). If there are conflicts they are marked in the resulting file ready for you to edit. But you can also use a GUI tool such as meld as I did above so read on.

Using meld for resolving conflicts during merging - highly recommended: Git also offers the ability to specify and use a graphic merge tool, one of the best being meld. Once the conflict has been identified you just run meld as below (for fictitious conflict in README). I have handled all my conflicts with meld and never done a manual edit.

$ git mergetool -t meld
Merging the files: README

Normal merge conflict for 'README':
{local}: modified
{remote}: modified

Hit return to start merge resolution tool (meld):

When meld opens meld, three areas are shown, and left to right are:

  1. The local branch (usually the master branch)
  2. The current version that you are merging into
  3. The other (or “remote”) version (from the a test branch in example above)

To choose the text from the master branch, click on the black arrow in the left-most window at the top right or from the other branch using the black arrow in the right pane

So far we have covered how you can install and set up Git, clone my repository, examine it with the gitk graphic visualiser, create a branch to make your own modifications and commit them, keep it up to date with my repository with pull and merge in any conflicts between what I have done and your modification.

Sharing your changes with others.

Note: This section is very general and initially concentrates on sharing via patches however if you are using GitHub it offers a much simpler and more powerful mechanism via Pull Requests for sharing changes which is virtually mandatory for contributing to the cinnamon-spices-applets repository. Patches can however be a useful mechanism at an early stage of a collaboration before one wishes to expose a suggestion on the Spices repository so I have kept it for the present.

Let us now look at what to do if you want to share some of your enhancements with others. There are several ways to collaborate but one of the oldest and still one of the easiest and most versatile is via patches. Patches are small files which contain all the differences which can be applied to another unchanged file or one with, at a minimum, no conflicting changes. This is a basic mechanism used whether you are using Git or not but Git has a few enhancements and makes it all very easy. The Linux Kernel development is almost entirely managed through patches.

First there is git diff which is very like an ordinary linux diff and takes SHA1 IDs, tags or positions relative to the HEAD for the two parameters. There is one caution and that is that Git is often set up to provide a coloured output to a terminal and it is best to use the --no-color option. If one parameter is missing it assumes the diff is relative to HEAD and both gives the diff from the staging area. HEAD~2 denotes two commits back from the current HEAD

The following makes a patch file for the combination of the last two commits and saves in LastTwoCommits.patch in current folder

git diff --no-color HEAD~2 > LastTwoCommits.patch

The patch can be applied easily by

git apply < /path/to/file LastTwoCommits.patch

Making Contributions to a Collaboration.

There is still a lot of planning needed for successful collaboration to work, for example you have enhanced one of my applets or want to pass the results to me to incorporate. You should, at a minimum:

Although the above was written for patches the majority is equally appropriate to sharing via Pull Requests on GitHub

More Complex Patches: If the patch is complex and has multiple commits a better way to create it may be use git format-patch to generate mbox-formatted files. This was originally intended as a way to email to a list by turning each commit into an email message with the first line of the commit message as the subject and the rest of the message plus the patch that the commit introduces as the body. The nice thing about this is that applying a patch from an email generated with git format-patch preserves all the commit information properly. It generates a set of patch files, one for each commit which look rather like the information available in the lower panel of gitk. The name is created from the start of the commit message and when called it outputs the names of the patch files it creates.

pcurtis@defiant:~/cinnamon-applets$ git format-patch nvidiaprime-3.1.4

These days it is easier to send the files as attachments as many of email packages do not preserve formatting etc information correctly. I have listed the last very simple change here so you can see how full the information is:

pcurtis@defiant:~/cinnamon-applets$ cat 0005-Ignore-.patch-files.patch
From 38e74fa5c9e019715b55a859fd48046c50b2305e Mon Sep 17 00:00:00 2001
From: pdcurtis <gitmail@pcurtis.com>
Date: Tue, 9 Aug 2016 04:11:38 +0100
Subject: [PATCH 5/5] Ignore .patch files

.gitignore | 1 +
1 file changed, 1 insertion(+)

diff --git a/.gitignore b/.gitignore
index b1e5d66..9015710 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,5 @@



This patch file can be applied by:

git am 0005-Ignore-.patch-files.patch

This should automatically created the new commit. The author information is taken from the email's From and Date headers, and the message of the commit is taken from the Subject and Body (before the patch) of the email.

Again when applying a patch it should be to a branch starting at the origin of the incoming patch series. If need the the current master can be merged in for testing.

NOTE: I have not tried out git format-patch and git am to check the above out whilst I have used the usual git diff and git apply

Creating your own Git Repository with possible use of GitHub as example.

We have concentrated so far on using, modifying and contributing to an existing repository. However you may wish to take your own project and create a Git repository to bring it under version control and eventually look at making it available to others as I did initially during applet development. I will start from scratch but you may have done most of the preliminary setting up already if you have cloned an existing repository.

First we need to install and configure Git. If you thing you have done it before then git config --list will show you what you have (best done outside of a Git repository (folder) so you only see the global settings.

pcurtis@defiant:~$ git config --list

If you have nothing or are only partially set up then do the appropriate parts of:

sudo apt-get install git gitk leafpad meld

git config --global user.name "your_username_on_github_is_best"
git config --global user.email "yourname@your_git_email_is_best"

git config --global core.editor leafpad
git config --global diff.tool meld
git config --global merge.tool meld
git config --global color.ui auto
git config --global push.default simple

You have at this point two choices depending on what you eventually expect to do.

  1. The first is to add Git to an existing folder turning it into a repository by git init, setting up .gitignore and then making the first commit. It is easiest if the folder is in your home folder. You can of course link to GitHub at a latter stage.
  2. If you are serious about making your work available you may want to set up a GitHub account from the start, create your repository within GitHub, clone it as an empty repository, set up .gitignore, add all your current files and make the first commit which you then push back to the repository on GitHub. This is what I did as I already had a GitHub account from working on Cinnamon and Git was already set up for me. I believe this is worth doing from the start as all the push and pull links are made for you if you every decide to use a remote repository.

Avoiding committing transient files. In both cases you need to go through the Git setup as above and you also need a specific .gitignore file. This file contains a list of files/filetypes etc that git should ignore such as temporary files, patch files, zip files - anything you do not want in every commit as they are transient. Mine is very simple for the applet development but if you are working on code you will want to ignore all sorts of log files, intermediate files and compilation results:


Linking an existing Git repository to GitHub: If you have an existing Git repository you can link it to GitHub at a latter stage. First get a GitHub account and create a new repository with the same name as the Git repository (folder) on your machine which should be in your home folder. You then create a remote called origin (by convention) and push your existing work to it. I would always back up the folder first!

cd new-repo
git remote add origin https://github.com/pdcurtis/new-repo
git push -u origin master

The git push -u origin master is only needed the first time and makes sure that you have all the tracking set up - this is the way that is recommended on GitHub

The following goes one further and shows my trial after setting up a new repository on GitHub called git-documents. The are a couple of calls to git status so you can see the effects better.

pcurtis@defiant:~$ cd git-documents
pcurtis@defiant:~/git-documents$ ls -A
.gitignore README.md status.txt
pcurtis@defiant:~/git-documents$ git init
Initialised empty Git repository in /home/pcurtis/git-documents/.git/
pcurtis@defiant:~/git-documents$ git status
On branch master

Initial commit

Untracked files:
(use "git add <file>..." to include in what will be committed)


nothing added to commit but untracked files present (use "git add" to track)
pcurtis@defiant:~/git-documents$ git add .
pcurtis@defiant:~/git-documents$ git status
On branch master

Initial commit

Changes to be committed:
(use "git rm --cached <file>..." to unstage)

new file: .gitignore
new file: README.md
new file: status.txt

pcurtis@defiant:~/git-documents$ git commit -a -m "first commit"
[master (root-commit) c7a06db] first commit
3 files changed, 36 insertions(+)
create mode 100644 .gitignore
create mode 100644 README.md
create mode 100644 status.txt
pcurtis@defiant:~/git-documents$ gitk &
[1] 14048
pcurtis@defiant:~/git-documents$ git remote add origin https://github.com/pdcurtis/git-documents.git
pcurtis@defiant:~/git-documents$ git push -u origin master
Username for 'https://github.com': pdcurtis
Password for 'https://pdcurtis@github.com':
Counting objects: 5, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (4/4), done.
Writing objects: 100% (5/5), 1.36 KiB | 0 bytes/s, done.
Total 5 (delta 0), reused 0 (delta 0)
To https://github.com/pdcurtis/git-documents.git
* [new branch] master -> master
Branch master set up to track remote branch master from origin.
  # check that eveything is set up
pcurtis@defiant:~/git-documents$ git remote show origin
* remote origin
Fetch URL: https://github.com/pdcurtis/git-documents.git
Push URL: https://github.com/pdcurtis/git-documents.git
HEAD branch: master
Remote branch:
master tracked
Local branch configured for 'git pull':
master merges with remote master
Local ref configured for 'git push':
master pushes to master (fast-forwardable)

Testing Applets - linking

We now have the ability to develop the applets under version control using Git but for use and testing they need to be within ~/.local/share/cinnamon/applets and that has lots in it we do not want under our version control.

It is a pain to keep making copies so I have instead linked the applets I have been working on rather than make copies but this has the disadvantage that an an accidental update from the Cinnamon Spices web site could lose a lot of updates unless Git had been used to commit frequently but it still seems the lesser of the evils. An example of use of a symbolic link is:

ln -s ~/cinnamon-applets/bumblebee@pdcurtis ~/.local/share/cinnamon/applets/bumblebee@pdcurtis

or if one is using the new cinnamon-spices-applets repository with format UUID/files/UUID

ln -s ~/cinnamon-spices-applets/batterymonitor@pdcurtis/files/batterymonitor@pdcurtis ~/.local/share/cinnamon/applets/batterymonitor@pdcurtis

both the above should be cut and pasted as single lines.

Important Warning : Linking is not one way. This is important as the built in mechanisms for loading and controlling applets can make changes which you do not expect or want.

You should therefore always check using git status before you commit for such changes and reverse them. You can reverse such changes in file(s) by git checkout filename which can have a wildcard eg:

git checkout *.json

More Advanced Git use

Tidying up a topic branch before merging and pushing to the remote repository using reset.

Note: This is one of two (or more) common ways of tidying up before pushing to your remote repository. Another which I will cover latter is to use rebase --interactive which is arguably more flexible and what I normally use..

The tidying up is important as it means others can have a simplified and more logical view of your development steps. You should however never use these techniques when the branch is visible or used by others as you are changing history.

Development of a new feature should be done in a separate branch which is not pushed to the remote repository. One should commit frequently during this stage and not merge back into the master until one has finished and certainly not push to the remote. This can lead to a lot of commits some reversing earlier actions and you may confuse people following your work. Git has many ways to tidy up some easier and safer than others! The simplest is when you want to squash the last few commits into a single new one with a new message which can be done by the reset command.

git reset HEAD~3
git commit -a -m "Merged three commits into one"

The git reset HEAD~3 leaves the working directory unchanged and takes you back three commits and then all the changes can be committed in one go.

You can also choose to make the changes selectively - you might for example have made similar bug fixes or enhancements in several applets but want to show them as separate commits with different messages to describe them. That is fairly easy but does need an understanding of the staging area which I have avoided up till now! The git commit -a -m "Merge Message" command does two things. Firstly it adds all the changed files to a list which will be used for the commit and then does the commit. These can be separated and the files you want to commit can be individually added to the 'staging area' or 'index' as it is sometimes called and then a commit without the -a just adds those files in the staging area to the commit snapshot.

Files are added to the staging area by:

git add <filename>


git add netusagemonitor@pdcurtis/*.*
git commit -m "Updates to NUMA"
git add stopwatch@pdcurtis/*.*
git commit -m "Updates to Stopwatch"
git commit -a -m "Rest of the updates"

Will split out the changes you have made to NUMA and Stopwatch into two commits and then commit everything else. The only thing to watch is that if you modify a file after it is staged you have to do the add again as the snapshot of its changes has already been taken at the time of the add.

Tidying Up: After a development is complete one will need to tidy up and make sure that your changes have been merged back into your master and the local branch you have used for the development can usually be deleted. If you are doing a collaborative development using GitHub your changes are usually incorporated via a Pull Request from a branch on your GitHub repository (which is a fork of the repository you are submitting the changes to.). This means that you may need to not only delete your local branch but also the remote branch used to generate the Pull Request. Deleting the remote branch is not as trivial as one would expect and I have covered it in detail under Deleting a remote and local branch


I have already gone further than I intended when I started this page but I plan to add some basic ways to collaborate and contribute using Git and remote repositories. The following lighthearted breakdown shows the main methods:

What if you want to send me an enhancement

There are three obvious mechanisms you can use

  1. A Pull Request on the cinnamon-spices-applets repository.
  2. A patch or series of patches.
  3. A copy of the applet folder for your modifies version (zipped up into a single file)

I can obviously make use of any of these but ultimately I will have to change your input into a PR to make it available on the Spices Web Site.

If you are already familiar with git, and a GitHub user and have forked the cinnamon-spices-applets repository and are maybe already an applet writer then a PR is the obvious way.

If you are not familiar with git and do not want to get involved with git and GitHub I see no reason why patches should not work as I can just apply them to a local topic branch I open for testing your inputs which can be pushed to my fork if necessary to give you visibility. It actually has a lot of advantages if only a single person is involved as the whole process is still kept relatively private until it is ready to be incorporated rather than create a lot of noise on the main cinnamon-spices-applets repository.

There are no promises I will use Patches or Pull Requests but I will certainly try out anything sensible. I do not believe in 'Not Invented Here' but I must understand it if I am going to use it and maintain it, so keep it simple. The most important thing is to discuss it with me at an early stage, if a PR just turns up be warned I will reject out of hand unless it has been discussed first unless it is from a member of the Cinnamon team to fix a major security problem!

Using Git and GitHub with the latest Cinnamon Applet Development Repository

Much of what has been written above was originally directed at a different model for collaborative development of my applets to what one now has to use for Cinnamon Applets as there is now a single repository containing all the applets which are available through the Spices Web Site and the two are connected automatically.

The main way of adding changes is via Pull Requests (PRs) to the main repository, the preferred way of working with GitHub which is very well supported. As far as I can see no branches and no tags are used on the spices web site and every Pull Request is reviewed, approved and merged in directly to the master branch.

This section has some duplication to the earlier sections as I am am trying to make it largely stand-alone.

Note: This is equally applicable to Desklets - just change applet to desklet everywhere. You will need separate forks and clones for each.

Summary of the Workflow on GitHub

  1. Fork the original repository into your GitHub account.
  2. Clone your fork locally which automatically sets up a remote called origin.
  3. Set up a link to the repository you cloned so you can collect updates (by convention call the remote upstream)
  4. Make your changes in a branch of you local repository committing as often as neccesary.
  5. Squash the commits so there will be a clear workflow with a minimum number of commits, preferably just one, in the final Pull Request.
  6. Only when completely satisfied, push the local changes in the branch to your on-line repository (origin).
  7. The creation of a branch will visible to GitHub and you will be given the opportunity to create a Pull Request (PR) to send to the repository you initially cloned (your upstream).
  8. Pull Requests are then be reviewed and approved/rejected by the repository's administrators with the possibly of changes.
  9. They will be merged (into upstream)
  10. You will then fetch and merge the changes back to your master branch from the main repository (upstream).
  11. The branch is now redundant and can be deleted locally and online and the cycle restarts at 4

Note 1: You never merge your branch into your master as it goes via your online repository (origin), the PR and is then fetched and merged back from the main repository (upstream)

Note 2: The names are those used by convention for the remotes are origin and upstream.

Note 3: It is good practice to update your master branch from upstream and rebase it onto your topic-branch before pushing to origin

Stages in setting up to use GitHub for development.

On GitHub:

You'll now have your own copy of that repository in your GitHub account.

Note GitHub knows that the repository in you GitHub account is a fork and will monitor it for certain types of changes such as a new branch but you have to keep it up to date via your local copy.

That is the end on GitHub until you submit any Pull Requests (PRs)

On your local machine:

First we must install Git and set it up - the following is largely a duplicate of a section at the start of this article where there are far justification and details of what everything means .

sudo apt-get install git gitk leafpad meld

git config --global user.name "your_username_on_github"
git config --global user.email "yourname@your_git_email"

git config --global core.editor leafpad
git config --global diff.tool meld
git config --global merge.tool meld
git config --global color.ui auto
git config --global push.default simple

git config --global credential.helper 'cache --timeout=7200'

Clone the fork you have made so you have a local copy of the whole cinnamon-spices-applets repository. (By convention this will be in a folder ~/cinnamon-spices-applets and the remote will be called origin)

cd ~/
git clone https://github.com/your_username_on_github/cinnamon-spices-applets
cd ~/cinnamon-spices-applets

From now on you should always be in the folder containing your local repository when issuing git commands

Add a connection to the cinnamon-spices-applets repository (by convention call the remote upstream) so you can keep up to date locally and then keep your fork up to date.

git remote add upstream git://github.com/linuxmint/cinnamon-spices-applets

From now on you only need to be online when you are logged into GitHub online or are pushing and pulling from your remote connections. ie almost all your development does not require an internet connection.

Naming conventions.

Before you start work it is important to consider how you name and title everything even if you are working in topic-branches. The cinnamon repository is very large with over 200 subfolders, one for every applet. Tags and branches do not seemed to be used in the main repository (upstream) so it is important to be able to distinguish changes in your applet from every other in Commits, Pull Requests and Issues etc. One rule from Mint is is that Every PR and Issue must contain the full applet name preferably at the start.

Applet names are mostly of the form appletname@author. In the case of new applets the author name will be the same as the authors GitHub username.

Version numbers are not manditory in Cinnamon Applets but are supported and are then displayed within the About... if a line is added within metadata.json like this:

"version": "1.3.9",

I always use a version number and strongly encourage it.

The default title of a pull request is derived from message in last commit in the branch so it makes sense to make commits which are going to be visible of the form.

commit -a -m "batterymonitor@pdcurtis Update to version 1.2.3"

In some cases you may be able to include summary information about the commit as well as a version number which is better.

commit -a -m "batterymonitor@pdcurtis v 1.2.3 Add translation support"

In an ideal world you might want to make a more comprehensive commit message with full details of the changes rather than use the -m "short single line message" form but this does not seem common in the cinnamon-spices-applets repository.

You can edit the title of the Pull Request but I find it best to keep everything consistent.

I name branches in the same way so the matching branch I push would be bumblebee-1.2.3 or bumblebee@pdcurtis-1.2.3

You can easily rename a branch but should not do so once it has been pushed as then it appears as if you are changing history to anyone else looking at it.

git branch -m oldbranchname newbranchname

Format of Pull Request

The Pull Request will again need the title to start with the applet name as well a short message ie

batterymonitor@pdcurtis Update to Version 1.2.3

and the comment field should contain a bullet list of the changes - I use basically the same as in my changelog and the following is a real example:

* Add translation support to applet.js and identify strings
* Changes to remove leading and trailing spaces and replace with fixed spaces to ease translations
* Add po folder to applet
* Create batterymonitor.pot using cinnamon-json-makepot --js po/batterymonitor.pot
* Update version and note other changes in applet.js and changelog.txt

Note: Most text fields such as comment boxes allow use of Markdown formatting and the * will be changed to a nice bullet when displayed.

References in Conversations.

Issues and Pull Requests on GitHub can be referenced by their number in conversations as #123 etc. The number is unique even between Issues and Pull Requests ie there can not be an Issue and a Pull Request with number #123

Thus any git user can be referenced by his GitHub username as, for example @pdcurtis and any user referenced in a conversation will be emailed a copy by GitHub. This is the mechanism used to alert an author to an Issue

Hint: You can use this to alert translators that you have made changes which need to be translated within the PR.

Workflow when you are collaborating via Pull Requests.

Whenever you want to start working on a new branch you should first fetch and merge upstream/master before creating the branch.

git checkout master
git fetch upstream
git merge upstream/master
git checkout -b topic-branch

In general your commits should not have any merge commits of upstream/master in them. If you want to pull upstream changes into a branch you are working on you should first update your master branch.

git checkout master
git fetch upstream
git merge upstream/master

Once you do that you can checkout your working/topic branch and run git rebase master to bring it up to date

git checkout topic-branch
git rebase master

That will cleanly add all of the upstream changes to your working branch without all the merge commits.

Perform these steps frequently to ensure that you are working with updated code and definitely just before you push your topic-branch.

When your work is complete and has been committed it is time to make sure you do not need to do and tidying up such as squashing commits by an interactive rebase or other techniques I may come to.

When you are ready to generate a Pull Request then push your topic branch after a final update with upstream by:

git push origin topic-branch

The whole final stage is therefore

git checkout master
git fetch upstream
git merge upstream/master
git checkout topic-branch
git rebase master
git push origin topic-branch

As a reminder GitHub detects you pushing the new branch and assumes you will probably want to create a Pull Request from your fork to the upstream master so when you go to the GitHub web site you will find a button in your repository called Compare & pull request to start the process. You will have chance then to create a title and initial comment and also start a conversation with your collaborators. Also remember that you can alert them by email or their preferred mechanism by mentioning them in any message by their git username ie to alert me to look at a PR just include @pdcurtis anywhere in the conversation.

NOTE: The use of rebase versus merge to keep the topic branch up-to-date should avoid any additional merge commits appearing. I tried merge rather than rebase and it sometimes ended up with a series of merge commits which is undesirable if one is just keeping up-to-date (See https://www.atlassian.com/git/tutorials/merging-vs-rebasing#the-golden-rule-of-rebasing for some background) .

There is no need for use of rebase when keeping ones local master branch up-to-date provided you never commit anything into it locally and do all your work in topic branches. If you have not made any changes then the merge should be what is called a fast forwards merge which does not need to create an extra commit. You can therefore keep doing the fetch upstream then merge upstream/master as often as you like to see what has changed. Provided no other PRs have resulted in changes to the files you have changed in your topic branch you should be able to do a rebase master periodically to keep the topic branch tidy without risk of commits due to merges.

However everyone makes mistakes and it is worth checking occasionally that your local master and upstream/master are the same. This will be cover later but the incantation is:

git log --pretty=oneline upstream/master..HEAD

which will return any local commits.

Undoing a common mistake - merging upstream into a topic branch instead of your master.

The merge from upstream should always be into your master not a topic branch. If you forget to checkout master before the merge you will find that git is doing a merge commit and you will be asked to add a message in your editor. It is possible to abort the commit part by deleting the whole message, saving and closing the editor. You must then abandon the merge part by

git merge --abort

Checking if a merge will be a fast forward merge.

So how does one check if one is going to be able to have a final merge (PL) as a fast forwards merge without any conflicts and sort any out if you have them? I have been looking at options from the topic branch such as:

git merge --no-commit --no-ff master

This will allow you to try a merge with no risk of a commit and examine/undo the merge, even if it is a fast-forward merge. It can then be aborted by:

git merge --abort
Note 1: The above requires that you do not have any changes in your topic branch that have not yet been committed Above based on http://stackoverflow.com/questions/501407/is-there-a-git-merge-dry-run-option and also see http://stackoverflow.com/questions/5817579/how-can-i-preview-a-merge-in-git .

Note 2: In practice, if you keep to the correct workflow, you will not get any conflicts and if you did you will be told by GitHub when you initiate the pull request and you can go back to sort them out before forcing a correction back up so I have never actually used the above checks.

Merging a Pull Request from a collaborator into a Local Branch

There comes a time when other people generate PRs for your work and before you agree them you want to check what do and for errors. The best way is to open a new test branch and pull their work into it. The proceedure for a PR from another user with a clone of cinnamon-spices-applets is:

git checkout master
git fetch upstream
git merge upstream/master
git checkout -b test_topic-branch
git pull https://github.com/user/cinnamon-spices-applets users_branchname
git status

If you use:

gitk --all &

you should see that you have merged in all the commits in the persons PR to your topic branch so you can check it at your leisure before commenting or agreeing the chnages. You can also test modifications to feed back or utilise his code yourself. You can also push the changes to origin so he can get them back the same way.

Squashing Commits together before a Pull Request - Important

Whilst working in a topic branch (or in general) it is a good idea to commit frequently during development. It is also a good idea and makes life much easier if you commit before switch to another topic branch or back to the master. However when you make a Pull Request it is best that all the extra commits are squashed together to keep the upstream origin tidy for everybody else. Make sure that each commit you make has a meaningful commit message even if it just says that it was to switch branch - it will make life much easier when you tidy up.

The commit message for the first commit is important as that is the one that remains by default after you have squashed everything together.

There is a marvelous option to rebase which enable you to squash commits together, merge or reword messages etc. which makes this stage very easy. I am simple minded so I have a set format for the command like this:

git rebase -i HEAD~4

where the number combines that number of previous commits into one, in this case 4.

The command is self documenting and gives you a list of the commits you have specified with a default command at the start and a list of other possible commands for each commit in you default editor. When you have made the changes save and close and that is it. It looks like this.

pick 07c5abd bunblebee-
pick 3e7ee36 bunblebee-
pick de9b1eb bunblebee-
pick fa20af3 bunblebee-

# Rebase 8db7e8b..fa20af3 onto 8db7e8b
# Commands:
# p, pick = use commit
# r, reword = use commit, but edit the commit message
# e, edit = use commit, but stop for amending
# s, squash = use commit, but meld into previous commit
# f, fixup = like "squash", but discard this commit's log message
# x, exec = run command (the rest of the line) using shell
# These lines can be re-ordered; they are executed from top to bottom.
# If you remove a line here THAT COMMIT WILL BE LOST.
# However, if you remove everything, the rebase will be aborted.
# Note that empty commits are commented out

So if one changes it to;

reword 07c5abd
fixup 3e7ee36
fixup de9b1eb
fixup fa20af3

We will squash all of the commits into one and get a chance to reword the commit message in your editor. You are, of course, changing history but if you are in a local branch you have not shared with anybody else that is only your problem. You should take great care in changing history when anybody else has access. I try to make sure my first commit has the message I require for the final PR so I never need to do the reword.

What this also means is that one can make lots of commits during development in an unshared branch. I tend to use a series as above to keep track and do intermediate rebase -i to keep major changes rather than typo corrections.

How many commits are allowed in a Pull Request?

The preference seems to be one but two or three are not going to cause a problem as they can combine them very easily if they want. The Pull Request must never be for several applets, the only exception being for the team when solving a common problem and even then each applet will have a separate commit so it can be reverted if needed. If you do provide several commits in your PR my guidelines would be that they should be for independent areas and topics so each could be reverted separately and out of sequence.

Making changes to a Pull Request - important

You will quite often want to make some changes following discussion in the conversations which make GitHub so effective a collaboration tool. You can just push another commit up to your branch and GitHub will pick it up and it will appear in the PR so other can see it and collaboration can continue. In many cases this would leave an untidy and even misleading series of commits when they are merged into the upstream master. Under these circumstances it is permissible to 'tidy up' again using git rebase -i (--interactive) but in this case you will have to -f (--force) force the push to overwrite what is there so in your topic branch. You should explain exactly what you are doing and why in the convesation.

git rebase -i HEAD~4
git push -f origin topic-branch

Tidying up after Pull Requests have been accepted

My original philosophy was to not destroy information ie branches until absolutely necessary but I have realised that can be confusing and unnecessary the way the update cycle works with GitHub and it is better to delete them as soon as possible, especially if you use more than one machine.

When working on GitHub with Applets you never merge your branch into your master as it goes via your online repository (origin), the pull request PR is from origin and is then fetched back from the main repository (upstream). If you want to see what you have merged at a latter stage you look at the commit into the main repository or your local repository which is in step with it. The problem with this is that it can take many days before your PR is accepted is accepted and merged and you can fetch and merge the change - until then you need the branches. You will receive an email that the merge has been completed which will have links to GitHub which you can follow to confirm the pull request (PR) is complete and will often contain a statement that your remote branch is no longer required and a button to delete it.

Deleting both Local and Remote branches

After a development is complete one will need to tidy up and make sure that your changes have been merged back into your master and the local branch you have used for the development can usually be deleted. If you are doing a collaborative development using GitHub your changes are usually incorporated via a Pull request from a branch on your GitHub repository (which is a fork of the repository you are submitting the changes to.). This means that you may need to not only delete your local branch but also the remote branch used to generate the Pull Request.

Deleting the remote branch is not as trivial as one would expect and the 'definitive' answer on stackoverflow on how to delete a git branch both locally and remotely has been visited nearly 4 million times! This article is so popular as the syntax is even more obscure than the rest of git and has changed with version number. I will assume that everyone reading this has at least git version 1.7.0 . I have already covered deleting a local branch and forcing a delete if changes have not been merged above but will repeat here for completeness.

To delete a single local branch use:

git branch -d branch_name

Note: The -d option only deletes the branch if it has already been fully merged. You can also use -D, which deletes the branch "irrespective of its merged status."

Delete Multiple Local Branches

git branch | grep 'string' | xargs git branch -d

where string is common to all the branch names, mine all have a - (dash/minus sign) in them.

You may need to force the delete by:

git branch | grep 'string' | xargs git branch -D

Delete Remote Branch

As of Git v1.7.0, you can delete a remote branch using

git push origin --delete <branch_name>
This is all you need to do if you are just using a single machine for development but if you are using several you may need an additional stage on the other machines in order to locally remove stale branches that no longer exist in the remote. Run this on all other machines:

git fetch --all --prune

to propagate changes.

Pruning Stale Branches

Also if you have created a Pull Request on GitHub you will find that after it has been merged there is a tempting button to delete the branch. I often use it or sometimes use other mechanisms in GitHub to delete my remote branches. This is perfectly acceptable but the local machines do not know and have stale branches which you need to remove at some point on all your local machines, as above, by:

git fetch --all --prune

You can also use:

git remote update --prune origin
git remote update --prune upstream

Ongoing work - Unusual Situations and Recovery from Mistakes

This is a live document and I am continually revising and adding to it. I do much of the writing of new sections for this and other pages in my series of 'Diary' pages and the following section is currently being revised in System Development Diary Part 31- Git Tips where you will find the latest version.

This section is now primarily concerned with identifying and extracting oneself from unusual situations, mainly recovery from mistakes. Git is designed to make losing information very difficult and there is no command which is the equivalent of undo. It concentrates on the workflow used for applet and desklet development for Cinnamon using Github although much is applicable to ones own developments using Git and Github.

The development environment and workflow for applets is slightly unuual so I will lay it out again for completeness. All the applets are kept in a common Cinnamon repository in Github which is forked by every applet author who then has his own local repository. The local repository is updated from the main cinnamon repository which is called upstream and your remote repository (called origin) is updated by pushes from your local repository. The master branches of all three repositories should always be kept in step by this cycle of fetch and merge from upstream to local and subsequent push from local to origin.

Any development is done in a local topic-branch which, when complete, is pushed to origin (your remote) and a Pull Request generated which brings it back into upstream and then it is fetched and merged into your local master branch. This cycle brings the changes into your master without your having to merge the branch which contained them. Once one is sure the cycle is complete is is best to deletes the local and remote branch. So the 'steady state' has upstream/master, master and origin/master identical possibly with a few local topic-branches with ongoing work. Changes are never merged directly (locally) into your local master branch. It is important that the three repositories are always kept in step and one of the folowing sections deals with checking if they are in step and how to correct the situation with minimum impact. There is one final twist to the updating and that is to do with your topic branches - it is usual to also keep the branch updated so the branch is from a commit close to or at HEAD before pushing to origin. This means even recent erroneous commits may also end up in your branch as well as master.

There are several common ways to make mistakes and I am sure I will add others to the list which follows:

  1. The first mistake is in a commit and you want to undo the commit, change the message or split it up.
  2. The second is to forget to checkout master before doing a fetch and merge form upstream which introduces commits into your topic-branch so you can no longer push it to origin for a clean pull request.
  3. The third is to make and commit changes in the working directory to master rather than your topic-branch.
  4. or even to merge the topic branch into your master

They can all be retrieved but the sooner you find out the better. Git is designed to make losing information very difficult and there is no command which is the equivalent of undo. It is permissable to make changes and tidy up in a topic-branch which has not been pushed to origin but changes which have been pushed are accessible to others including colaborators who may have already based work on them are undesirable even if you notify your collaborators. However in the workflow used here origin is just a fork used as a mechanism to push changes to update your applet one can be a little more flexible in the ways one hides ones mistakes as it is unlikely anybody is actually using that fork directly!

Checking and re-synchronising the local master, origin/master and upstream/master

The most important thing is to always keep the cycle of upstream/master, your local master and origin/master in syncronisation although lags are inevitable.

There is fortunately a form of the log command which enables one to compare branches which can also be used for the purpose of checking. To understand this see progit book and search "double dot" It uses a .. range specification which resolves a range of commits that are reachable from one commit but aren't reachable from another.

You can, for example, ask Git to show you a log of just those commits with master..topic-branch – that means “all commits reachable by topic-branch that aren't reachable by master.” by

git log master..topic-branch

or in our case where we are looking for changes between upstream/master and master

git fetch upstream
git log --oneline HEAD..upstream/master

note the fetch upstream to get the latest changes and the option to log of --oneline to reduce the output. You can also use --pretty=oneline to give the full hash.

:~/cinnamon-spices-applets$ git log --oneline HEAD..upstream/master
18da10c8 (upstream/master) mem-monitor-text: issue 2476, fix RAM occupation calculation (#2548)
cb544d49 weather@mockturtl: New updated fi.po file in Finnish. (#2559)
be1ad798 hwmonitor@sylfurd - Major changes (#2557)
e8095840 [temperature@fevimu] Option added: Change color when the temperature becomes High or Critical (#2555)
81a7187b [temperature@fevimu] pot file updated - French translation: fr.po created (#2554)
c99ce37e Spices update fi (#2553)
897591e1 Make compatible with vertical panels (#2552)
2a096009 SpicesUpdate@claudiux v3.0.4 (#2547)
3c7b407f Fixes #2545 (Cinnamon 2.8 to 3.6) (#2546)

The commits shown will disappear when one does a merge upstream/master

What we are really interested in is the other way round as we want to check that we have no commits which are not from upstream/master

git fetch upstream
git log --oneline upstream/master..HEAD

which should be empty

:~/cinnamon-spices-applets$ git log --oneline upstream/master..HEAD

If you have commits the only simple way out is going to be to a hard reset reset to the previous commit and then do a fresh fetch and merge upstream/master which will lose all work since that point, both the accidental work in master and possibly the branches starting after that commit. See below for more details of reset and also ways to recover a deleted branch.

But before going into that lets look also at the next step of the circle which is from the local master to origin/master

~/cinnamon-spices-applets$ git log --oneline HEAD..origin/master
:~/cinnamon-spices-applets$ git log --oneline origin/master..HEAD

here again there should be no differences after you have done a push. If there are you will have to force a push to overwrite the existing origin/master branch by:

git push -f origin master:master

-f is the force flag. Normally, some checks are being applied before it's allowed to push to a branch. The -f flag turns off all checks. origin is the name of the remote where to push. master:master means: push my local branch master to the remote branch master. The general form is localbranch:remotebranch. This is another way to delete a branch on the remote: in that case, you push an empty local branch to the remote, thus deleting it:

git push origin :remote_branch_to_be_deleted

Types of reset and their uses

git reset is a powerful tool but one where of the few commands where one can destroy data. It has three main modes.

git reset --soft <ID>
git reset --mixed <ID> (the default)
git reset --hard <ID>

<ID> will often be relative to HEAD ie HEAD~ or HEAD~4 .

The three options differ in the way they handle the index and working directory.

The soft option leaves the working directory and staging area (index) unchanged. If we look at git reset --soft HEAD~ it moves HEAD back to point to the previous commit with working directory and staging area unchanged so you can change what you plan to stage and re commit.

The git reset --mixed (default) leaves the working directory unchanged but removes staged files from the index so you can make changes, restage and commit. This is as close as you can get to an undo of a commit and also allows you to squash several commits if you go back a number of stages in a branch (but note an interactive rebase has more flexibility and is the prefered option in a topic-branch).

git checkout topic-branch
git reset HEAD~4

Squashes the last 4 commits in topic-branch together.

A hard reset also clears the working directory so is one of the few ways you can destroy data as there is no record of uncommited data. It is however a useful way to correct your master if it gets out of step with upstream by

git reset --hard SHA1_prior_to_error
git fetch upstream
git merge upstream/master

This may leave recent deleted hanging and/or they may have extra commits to remove depending where the branch was located.

Recover a deleted branch

You should be able to do git reflog and find the SHA1 for the commit at the tip of your deleted branch, then just git checkout [SHA1]. And once you're at that commit, you can just git checkout -b [branchname] to recreate the branch from there. See https://stackoverflow.com/questions/3640764/can-i-recover-a-branch-after-its-deletion-in-git for more details. This can be compressed to:

git checkout -b <branch> <SHA1>

I have tried this and it is easiest to pipe into grep and the commit message to cut down the search for the latest SHA1. The following is a recovery of a branch I had routinely deleted 28 days before. See below for details:

:~/cinnamon-spices-applets$ git reflog | grep batterymonitor@pdcurtis-1.3.8
bad1c721 HEAD@{22}: checkout: moving from batterymonitor@pdcurtis-1.3.8 to master
66942db0 HEAD@{23}: rebase -i (finish): returning to refs/heads/batterymonitor@pdcurtis-1.3.8
bad1c721 HEAD@{28}: checkout: moving from master to batterymonitor@pdcurtis-1.3.8

:~/cinnamon-spices-applets$ git checkout 66942db0

Note: checking out '66942db0'.

You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by performing another checkout.

If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -b with the checkout command again. Example:

git checkout -b <new-branch-name>

HEAD is now at 66942db0 batterymonitor@pdcurtis v 1.3.8 Change location of temporary files to home folder

:~/cinnamon-spices-applets$ git checkout -b recovered
Switched to a new branch 'recovered'

:~/cinnamon-spices-applets$ git checkout master
Switched to branch 'master'
Your branch is up-to-date with 'origin/master'.
peter@helios:~/cinnamon-spices-applets$ git branch
* master

The recovered branch was back where it was previously 'located' not at HEAD.

This may help if you have ended up by deleting branches when doing a git reset --hard to get your local master in step with upstream master after making errors. But it is also possible that differences in local master may make it impossible to do a fast forwards merge for a PR from the recovered branch.

So it is always safer to check yout local master has not diverged from origin master before doing a rebase which moves a branch up to HEAD in anticipation of a push to origin ready for a PR.

Undo last Commit.

If you want to re-work the last commit - assuming it has not been already pushed to origin:

git reset HEAD~

This will undo the commit and restore the index to the state it was in before that commit and leave the working directory with the changes uncommitted, so one fix whatever you need to modify before committing again.

If you have pushed it or for more information see https://stackoverflow.com/questions/19859486/how-to-un-commit-last-un-pushed-git-commit-without-losing-the-changes

Use of interactive rebase: Another powerful tool for changing commits is an interactive rebase (git rebase --interactive ) which I have already covered for squashing commits before making a PR here. This enable you to squash commits together, reorder, merge and reword messages in a series of commits.

Help: I have merged upstream or master into a branch without meaning to

This is a very easy mistake to make when you intend to update your master from upstream and forget to checkout master. This usually causes a single commit in your topic branch which you want to get rid of. Most of the obvious ways lose the changes in your working folder as well as stepping back but the following leaves any non conflicting changes in your working directory. So what I do is to make a copy outside of git of the relevant part of the local repository which has the changes as a belt and braces fall back and then do:

git reset HEAD~1

The default option for reset is --mixed which does not make any non essential changes in your working directory so most times you just get rid of the commit and leave the working directory intact. If you find it has modified a file you have your backup to put the changes back.

There is an alternative way covered latter if you realise whilst the commit is taking place.

Modifications made in the wrong branch

We have already discussed how to remove modifications/commits from master or a topic-branch by git reset HEAD~ but not how to move them to a different branch. To a large extent it varies on the sort of modification you have been making. It is unlikely you have made many modifications on the same file in the wrong branch without noticing you were editing a file which had already been modified. I have usually made the mistake when finishing a job off by updating change logs, help files and version numbers.

I have not used it yet but it seems stash should be a way to save any un-committed modifications currently in the working directory (WD) which can then be reapplied in a different branch. The extra commits can be removed using git reset leaving the WD unmodificed before using git stash which saves the modifications on a stack and clears them from the WD. You can then change to the correct branch and reapply them, hopefully without conflicts with a git stash apply

I will add more after I use this proceedure

More about Stashing in General

Often, when you've been working on part of a project, you want to switch branches for a bit to work on something else butyou don't want to do a commit of half-done work just so you can get back to this point later. The answer is the git stash command. Stashing takes the dirty state of your working directory – that is, your modified tracked files and staged changes and saves it on a stack of unfinished changes that you can reapply at any time and cleans the Working Directory.

You can save a stash on one branch, switch to another branch later, and try to reapply the changes. You can also have modified and uncommitted files in your working directory when you apply a stash – Git gives you merge conflicts if anything no longer applies cleanly.

You can have multiple stashes. To see which stashes you've stored, you can use git stash list and then use git stash apply stash@{n} to select slash n.

You can find out much more and see examples in Pro Git by Scott Chacon by a search for "Stashing and Cleaning" . I keep a pdf of of Pro Git (which is licensed under the Creative Commons Attribution Non Commercial Share Alike 3.0 license) permanently on my desktop.

Reminder about difftool and meld

One can use difftool which is set up to call meld to check (and even correct) the unmerged set of changes by:

git difftool HEAD

And a good way of seeing what you have currently changed in a branch when everything is up-to-date is

git difftool master

Reminder about gitk

It is useful to be able to see all the branches by:

gitk --all &

I use gitk --all whenever using git to visualise what I am doing and gitk is part of git so is always up-to-date.



Appendix 1 - Planning for and working from several machines with Git

This is an extension of the sections above following some additional work on my second machine that has made me examine and revise my proceedures for handling branches once PRs have been generated and implemented and emphasise the use of a version number. My original philosophy was to not destroy information ie branches until absolutely necessary but I have realised that can be confusing and unnecessary the way the update cycle works with GitHub and it is better to delete them as soon as possible after a PR has been accepted and merged, especially if you use more than one machine. Most of the conclusions here have been fed back into the proceedures above as recommendations.

Firstly there are several initial states when one starts to work on an alternative machine:

  1. Git has never been used and needs to be installed and configured.
  2. Git is installed and configured but the particular repository has not been cloned onto the machine.
  3. The machine has been used with the repository at some time in the past or the whole home folder has been transfered and git has been installed

In the first and second cases the procedures have been covered already under starting using git with the difference that one may already have remote branches on origin rather than an unchanged fork. You will have no local branches and no way of telling what was on the other machine which means you need to have procedures which make sure you never end up working on the same applets on both machines and certainly not with the same branch names.

The third case is the most likely and is the most complicated from the point of view of overlaps as you will have an out of date local master which will probably have old local branches and you will also probably have stale remote branches - a stale branch is one where there has been a completed PR. You can find out the remote branches but not anything about PRs and their status by:

git fetch origin
git branch -r

But you still have to be certain that they have been pulled and merged. The easy way is to look on-line at GitHub at the list of your branches (ie at origin) and you can select active, stale etc. to view. Otherwise one has to look for a commit into upstream master.

All the information in stale branches has been transferred via the PR and subsequent merge into the upstream master back to ones own master branch. Once the PR has been merged the matching local branch holds no additional information as the PR and any comments and revisions are retained and accessible on GitHub.

At any time an excellent way to get an overview is to use gitk --all which shows all the local and remote branches and where they all link together in a 'graphic' way. You can search for strings so you can easily trace how an applet has been updated. Remember that not all changes will be yours as some will be translations and a team member may have also occasionally make minor coding changes to avoid problems with a new cinnamon version. It is a very good way to find your last update and version number.

Branch names, Commit messages and PR titles. There is a requirement from Mint that the applet name is used in the PR title and subsequent commits so they are easily tied to a particular applet. I add a version number as well so, for exmple, the branch will be batterymonitor@pdcurtis-1.3.9 and the first commit will have a message like batterymonitor@pdcurtis v 1.3.9 Enhance Audible Warnings. Any subsequent commits add an extra level but when they are finally squashed for the PR you get back to the higher level

Applets do not have to have a version number but again one should use them so everything is linked together. The version number goes in metadata.json as a extra line like:

"version": "1.3.9",

and is then displayed in About... along with the other applet details.

So what does all this mean for the best way to work to make it easy to keep track of the development and to switch machines?

The thoughts above are being or have been fed back into the relevant sections above but the philosophy behind them have not been brought together elsewhere.

Transferring an 'active' branch to a different machine

You should try to only work on a branch on a single machine. It is possible to continue work having transfered files from one machine to the other or via a remote branch but it should be the exception.

If you have to work on a remote branch started on a different machine

I have had to change machine and had an out of date (by 6 months) version of git and I needed to modify a branch I had already pushed and started a PR. So first update my master from upstream.

So first update my master branch from upstream by the usual

git checkout master
git fetch upstream
git merge upstream/master
git status

Now the important part is to get the remote branch and set it up so it is tracked (ie can be autiomatically pushed to and pulled from.. I looked at https://stackoverflow.com/questions/9537392/git-fetch-remote-branch. What seems to have worked for me is the simplest way namely

git fetch origin
git branch -r
git checkout remotebranchname

as below

peter@lafite:~/cinnamon-spices-desklets$ git branch -r
origin/HEAD -> origin/master
peter@lafite:~/cinnamon-spices-desklets$ git checkout netusage-1.0.4
Branch 'netusage-1.0.4' set up to track remote branch 'netusage-1.0.4' from 'origin'.
Switched to a new branch 'netusage-1.0.4'

Note: This recogises it is a remote branch and sets up the tracking. This was using a recent git version 2.17.1 which is still in the Mint 19.2 repositories o 10 Aug 2019 but there are rumours that this no long works with even more recent versions which take you to a Detached Head state instead. Use instead:

git checkout --track origin/remotebranchname

Which will end up a local branch set up to track the remote branch. I have tried that successfully with 2.17.1

Appendix 2 - Updating to the latest version of Git

There may be times when it is advantageous to have the latest version of GIT. For example there was a time when I was using Git 2.7.4 from the repositories under Mint 18 but I found under Mint 17.3 on another machine only had a much earlier version 1.9.1 available whilst there are major enhancements in the 2.x.x versions. You can obtain the version by:

git version

I therefore added the “Ubuntu Git Maintainers” team Git Stable PPA to obtain the most recent version. This is very simple to do with the three following terminal commands:

sudo add-apt-repository ppa:git-core/ppa
sudo apt-get update
sudo apt-get install git gitk

After this I had Git 2.9.3 available in Mint 17.3 on my old machine.

All the example on this page were initially tested with Git 2.7.4 and in July 2019 the version in the Mint Repositories is 2.17.1

Appendix 3 - Getting out of trouble if an Applet locks a machine by using an alternative login and Git

I unfortunately managed to have an Applet which broke Cinnamon preventing any display on my single user laptop whilst I was away from home. I however found I could do a remote login via SSH to my user giving me terminal access from my Android phone as the Wifi connection was working. I used an App called JuicySSH on the phone and the Wifi connection was the tethering one from another phone which was already set up. If I had been at home I could have used the normal network and a terminal on any of my other machines.

Subsequent Notes: It is often still possible to get to a Consol even when a graphic interface seems completely hung by Ctrl+Alt+F1 or Ctrl+Alt+F6 and login to your user from there instead of the remote login described here. It can also be done from the initial login screen. All these forms of login work with encrypted home folders. You also need to know how to Shut down a frozen machine safely, (or see the Linux Mint Forum post What to do with frozen desktop) especially with encrypted folders.

Once logged in I could run Git and commit the files in the faulty version so I could see latter what I had done wrong and then checkout the working master branch and login again using Cinnamon. Two commands and I was back in business. For advanced users - have a look at git stash and git stash pop to avoid a commit you really do not need but be able to get the Working Directory back where it was.

The applet problem turned out to be a faulty edit of the settings-schema.json file where a 200000 had lost the initial 2 giving an upper limit of zero. I would have expected that sort of error to be trapped in the Cinnamon Settings software - I will be be careful in future. I have had two more lockouts recently after editing a settings-schema.jason file and had to use a remote login over SSH again.

Lessons to Learn: The primary lesson is that you need to have contigency plans, with the emphasis on plural, on what to do if you hang the machine during applet development. I did not and the solution here using SSH was what I came up with at the time to login and at the time I did not know how to switch to a consol using Alt+Ctrl+F6 which would probably have worked. It is still useful to have either another user or SSH installed on any machine for applet development and the machine should be set up to automatically connect to a network. An automatic network connection is almost always the case at home but it is worth setting up for mobile hotspots and tethering on ones android phones or having a Mifi box. I always install SSH to allow remote logins and synchronisation via Unison on all my machines and this is just another good reason to have it installed. It also reinforces the advantages of working in topic branches and maintaining a stable master branch.

Before You Leave

I would be very pleased if visitors could spare a little time to give us some feedback - it is the only way we know who has visited the site, if it is useful and how we should develop it's content and the techniques used. I would be delighted if you could send comments or just let me know you have visited by sending a quick Message.

Link to W3C HTML5 Validator Copyright © Peter & Pauline Curtis
Fonts revised: 28th April, 2021