Using Git
This guide is intended to document the first steps in using Git for Vassal development. If you do not intend to contribute and just want a copy of the code to play around with, follow this guide instead: Git clone guide
Prerequisites
- On Linux, install Git using your distributions package management tool, e.g.
apt install git
- On Windows, Git Bash is very good: https://gitforwindows.org/ (and once you have installed it you can also use git from a regular command line)
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
Setting up Git
If you are relatively new to git, we highly recommend "The Git Book" which you can read for free here -- https://git-scm.com/book/en/v2 -- It is a quick and easy read and will help you understand what is going on.
Fork the central Vassal repository
To create a fork, log into your GitHub account, navigate to github.com/vassalengine/vassal, look for a button on the top right labeled "Fork", click on it.
You can also follow one of these guides which explain the forking process in detail:
- https://help.github.com/en/github/getting-started-with-github/fork-a-repo
- https://guides.github.com/activities/forking/
The result will be a vassal repository in your GitHub account:
- 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
Clone the repository to a local computer
The official guide for this step is here: https://help.github.com/en/github/creating-cloning-and-archiving-repositories/cloning-a-repository
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.
Link Git on the local computer to 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
Link local repo to upstream
First 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
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)
Branches in Git
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
Workflow for keeping the master branches 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 we regularly sync the master branches by following the official guide here: https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/syncing-a-fork
Or by following these steps:
Update information about central/master
Make changes to central/master known to the local repo:
git fetch upstream
Switch to master branch
Make sure we have the master branch selected (make sure local changes are all committed before this, more about this later):
git checkout master
Bring local master up to date
Sync central/master with our local master:
git merge upstream/master
Bring github.com/contributor/vassal:master up to date
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.
Workflow for changing code and getting the changes into the central master branch
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.
Create new branch
Create a new branch in the local repo and switch to it. Let's call it bugfix-12345:
git checkout -b bugfix-12345
Change code and commit locally
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 commit -m 'changes to ClassA' (change ClassB.java and ClassC.java) git add ClassB.java ClassC.java git commit -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
If all files with changes should end up in the commit, there is a shorthand that adds all files and commits them:
git add * git commit -m 'changes to several files, and maybe even added some new ones'
One can also use -a switch as shorthand, and this will pick up changes to any [i]tracked[/i] files (files already in the repository), but will NOT notice brand new files previously untracked by the project:
git commit -a -m 'changes to several existing files'
Optional: Rewrite history
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. Complex commit reordering, squashing and fixup is best done with the help of a tool or a graphical git client.
Push commits to GitHub
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
Open PR
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.
Modify PR
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.
Delete branch on GitHub
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.
Delete branch locally
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.:
git fetch upstream
-> in the IntelliJ git client that's a button with two arrows circling each other with a popup info saying "fetch all remotes"git checkout -b bugfix-12345
-> right-click on the master branch, click on "create branch..", enter "bugfix-12345"git checkout bugfix-12345
-> right-click the branch bugfix-12345, click on "checkout branch"git push -u origin bugfix-12345
-> right-click the branch bugfix-12345, click on "push" or "push..."
For committing changes, the graphical git clients usually offer a convenient view where the files to be committed can be seen and each file can be marked to include it or exclude it from the commit.
The major Java IDEs all come with built-in graphical git clients.
A good generic graphical git client for Windows is Sourcetree, downloadable for free from here: https://www.sourcetreeapp.com/.
Additional tips
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, add these aliases as shorthands for complex commands:
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 use them in any git repo:
git lg git lg1 git lg2 git tree
Rewrite history
git rebase
is a very powerful feature of git, once comfortable with the basics explained above, learning to use rebase is a good next step.
Save changes temporary
git stash
is a good way to temporary keep changes without having to commit them, e.g. when switching branches between changes.
Use Git locally to track changes
When working on custom code for Vassal modules, other kinds of software projects, or any kind of simple plaintext documents that are not uploaded to a remote source code management system, why not create a local git repo underneath the files 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.)
Use Git to track changes to a Vassal Module
Since Vassal modules compress multiple image and data files into a "zipped" .vmod file, committing .vmod files directly into a git repository can be cumbersome. But you can also "unzip" the .vmod file into a directory and turn THAT directory into a git repository.
cd DirectoryYouExtractedModuleInto git init . (change files, commit, branch, look at commit history etc.)
You can then "build" a copy of the vmod file from the component parts using a utility like 7Zip:
del ModuleName.vmod # or `rm ModuleName.vmod` on Mac or Linux 7z a ModuleName.vmod *.xml -mx1 -tzip 7z a ModuleName.vmod *.html -mx1 -tzip 7z a ModuleName.vmod *.vsav -mx1 -tzip 7z a ModuleName.vmod moduledata -mx1 -tzip 7z a ModuleName.vmod help\ -r -mx1 -tzip 7z a ModuleName.vmod images\ -r -mx1 -tzip # any other lines needed to add custom code or other files into the module # a = add to archive # -mx1 = fastest compression # -tzip = zip archive format
When you use the VASSAL Editor on your module, you will then need to re-extract the new buildFile.xml and moduledata into your repo directory so that you can update them in git. Likewise any other files you have added to the module in the Editor (e.g. image files) should be likewise extracted and put in the correct place in the repo's directory structure -- then "git add" and "git commit" (and "git push" if you are working with your own github.com remote repository) to commit your changes.