Using Git

Prerequisites

 * On Linux, install Git using your distributions package management tool, e.g.
 * On Windows, Git Bash is very good: https://gitforwindows.org/

Definitions

 * Central repository - The central, the main repository, the one where development of Vassal happens, the one where we sync from to get our local code up to date, the one that is kind of like the SVN server in SVN, the one where we ultimately want our pull requests (PRs) to end up
 * github.com/vassalengine/vassal - The location of the central Vassal repository
 * Contributor - Person that partakes in Vassal development without having write access to the central repo, to github.com/vassalengine/vassal
 * github.com/contributor - The GitHub account of the contributor

Step 1
Create a fork of github.com/vassalengine/vassal, the result is:


 * github.com/contributor/vassal - Contributor's fork of github.com/vassalengine/vassal, this is a fully featured repository on its own, like another SVN server with its own branches, only it points to the central repo and knows that it's a kind of "child" of it

Step 2
The contributor creates a clone of his fork on his local computer by running git clone:

(on posix systems) cd /home/contributor/developmentProjects (on Windows) cd C:\Users\contributor\developmentProjects git clone https://github.com/contributor/vassal.git

This creates a clone of the contributor's repo in /home/contributor/developmentProjects/vassal or C:\Users\contributor\developmentProjects\vassal. This clone is yet another fully featured repository, like another SVN server, a third one after central and the contributor's GitHub repo, but it points to the contributor's repo on GitHub and knows that it is a kind of "child" of the contributor's GitHub repo.

Step 3
Configure Git on the local computer to know the GitHub account:


 * Set username according to: https://help.github.com/en/github/using-git/setting-your-username-in-git
 * Set commit email address according to: https://help.github.com/en/github/setting-up-and-managing-your-github-user-account/setting-your-commit-email-address

Now some more definitions. The links between repositories have names, we want the local repository to link to both the contributor's GitHub repo and to the central Vassal repo.


 * remote - For the local repo, the name of the link to github.com/contributor/vassal, i.e. where it was cloned from
 * upstream - For the local repo, the name of the link to github.com/vassalengine/vassal, i.e. where is the central Vassal repo

The remote is already known to the local repo since we cloned it from there, we can see it like this:

(on posix systems) cd /home/contributor/developmentProjects/vassal (on Windows) cd C:\Users\contributor\developmentProjects\vassal git remote -v

Step 4
Tell the local repo where the central Vassal repo is located. Follow this guide: https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/configuring-a-remote-for-a-fork

Or do this:

(on posix systems) cd /home/contributor/developmentProjects/vassal (on Windows) cd C:\Users\contributor\developmentProjects\vassal git remote add upstream https://github.com/vassalengine/vassal.git

Check if it worked:

git remote -v

This should show 2 lines with origin and another 2 lines with upstream now, e.g.:

origin  https://github.com/contributor/vassal.git (fetch) origin  https://github.com/contributor/vassal.git (push) upstream  https://github.com/vassalengine/vassal.git (fetch) upstream  https://github.com/vassalengine/vassal.git (push)

So much for the repositories. Now the branches. Git branches are per repository. The central repo has a branch called master. This is the trunk of the SVN server, in SVN terms. This is the branch where Vassal development happens, the branch where all our commits have to end up. The contributor's repo on GitHub also has a branch called master. And the contributor's repo on the local computer also has a branch called master. Three repositories, one master branch in each:


 * https://github.com/vassalengine/vassal/tree/master (or central/master for short) - the master branch of the central repo
 * https://github.com/contributor/vassal/tree/master - the master branch of the contributor's repo
 * local master - the master branch of the contributor's repo on the local computer

We now have two different workflows. One is for keeping the master branches in sync, the other is for doing code changes and getting them into the central master branch.

Staying in sync
First let's make sure we are in sync, we do this regularly, usually after we see that there are new commits in central master and our other 2 master branches fell behind. Since we (the contributor) do not have write access to the central repo, we can only read from the master branch. We never do any commits into this branch, instead what we do regularly is this:

Step 1
Make changes to central/master known to the local repo:

git fetch upstream

Step 2
Make sure we have the master branch selected (make sure local changes are all committed before this, more about this later):

git checkout master

Step 3
Sync central/master with our local master:

git merge upstream/master

Step 4
At this point our local master and central/master are in sync, now let's update our GitHub master as well by pushing our local master to origin:

git push origin refs/heads/master:master

Now our own two master branches are in sync with central/master.

Changing code
Next, the workflow for actually changing the code. This is best done right after syncing the master branches, to make sure we build upon the latest commits in master.

Step 1
Create a new branch in the local repo and switch to it. Let's call it bugfix-12345:

git checkout -b bugfix-12345

Step 2
Change code. Commit it to the local branch. Change some more, commit again. As often as we want. At this point we're only working in our local branch, the outside world does not know anything about our local branch and the commits in it.

(change ClassA.java) git add ClassA.java git -m 'changes to ClassA' (change ClassB.java and ClassC.java) git add ClassB.java ClassC.java git -m 'changed ClassB and ClassC' (change ClassA.java ClassB.java and ClassC.java again) git add ClassA.java ClassB.java ClassC.java git commit -m 'a third round of changes'

Each commit consists of 2 steps, the files that go into the commit have to be added first, then comes the actual commit. Files that were not added do NOT end up in the commit. To see which files are added, which are not, and which files are not tracked by git at all:

git status

Optional step 2b
Now is a good time for a "git rebase" if we want to change history i.e. reword commit messages, smash several commits into one, change the order of the commits.

Step 3
Now we think we are ready with the changes we wanted to do and we want to get them into central/master by doing a pull request (PR). First we push our local branch, which so far is not known outside of our local computer, to our GitHub repo:

git push -u origin bugfix-12345

Step 4
Now we log into our GitHub account on the GitHub website, open up our repository, and see how GitHub noticed that we just pushed a new branch and since it knows that our GitHub repo is a fork of another repo, it is smart enough to offer us to create a PR from this branch. We do this, we push the green button that creates a new PR, verify that it shows something like this:

contributor wants to merge N commits into vassalengine:master from contributor:bugfix-12345

We write a few words into the description (or we don't) and confirm.

Step 5
As usual, we have forgotten some detail, or someone else has a good idea how to make our code change even better, we log into our GitHub account a while later, check our PR and see that some changes were requested. We change to our local branch again, in case we switched away from it in the meanwhile:

git checkout bugfix-12345

We apply the requested changes, commit them and push them to GitHub:

(apply changes) git commit -a -m 'applied requested changes' git push -u origin bugfix-12345

We look at our PR on GitHub again and see that GitHub is smart enough to add our new commits to the existing PR.

This can go back and forth several times, more changes can be requested, we repeat step 5, and so on. At some point the PR will be accepted and closed. GitHub will then offer us to delete the branch, this is the branch on GitHub not the local one.

Step 6
Accept GitHub's proposal and delete the branch "bugfix-12345" on GitHub. There will be a button "Delete branch" at the bottom of the PR on GitHub.

Step 7
Delete the branch locally:

git branch -d bugfix-12345

Using graphical git clients
When using graphical git clients, each of the above commands maps to an action in the graphical client, e.g.:


 * -> in the IntelliJ git client that's a button with two arrows circling each other with a popup info saying "fetch all remotes"
 * -> right-click on the master branch, click on "create branch..", enter "bugfix-12345"
 * -> right-click the branch bugfix-12345, click on "checkout branch"
 * -> right-click the branch bugfix-12345, click on "push" or "push..."

For commiting changes, the graphical git clients usually offer a convenient view where the files to be commited can be seen and each file can be marked to include it or exclude it from the commit.

Practice
You can practice interactions between git repos locally by creating 2 or more git repos on the local filesystem, then create branches, commit, push/pull branches etc:

cd /home/user/tmp mkdir gitrepo-main cd gitrepo-main git init. echo "first file" > first.txt git add first.txt git commit -m "first commit" cd .. git clone gitrepo-main/ ./gitrepo-clone1 cd gitrepo-clone1 git checkout -b bugfix-1 echo "\na second line of text" >> first.txt git commit -a -m "added second line to first.txt" git push -u origin bugfix-1 (.... etc)

View repository state on the command line
For a neat ascii-art view of the branches and commits, do this:

git config --global alias.lg1 log --graph --abbrev-commit --decorate --format=format:'%C(bold blue)%h%C(reset) - %C(bold green)(%ar)%C(reset) %C(white)%s%C(reset) %C(dim white)- %an%C(reset)%C(bold yellow)%d%C(reset)' --all

git config --global alias.lg2 log --graph --abbrev-commit --decorate --format=format:'%C(bold blue)%h%C(reset) - %C(bold cyan)%aD%C(reset) %C(bold green)(%ar)%C(reset)%C(bold yellow)%d%C(reset)%n''         %C(white)%s%C(reset) %C(dim white)- %an%C(reset)' --all

git config --global alias.tree log --graph --decorate --pretty=oneline --abbrev-commit

git config --global alias.lg !git lg1

Then in any git repo:

git lg   git lg1 git lg2 git tree

Use Git locally to track changes
When working on Vassal modules, even when they are not uploaded anywhere, why not create a local git repo underneath the module and use git to track all changes, be able to go back to previous versions, find out what exactly changed and when, maybe even branch and try out different things while keeping the other variations?

cd anyDirectoryWithFilesToTrack git init. (change files, commit, branch, look at commit history etc.)