Okay Flintb, Let's Talk "Git Command Line"

Hi Flint,

The other part of the thread (on getting Vassal to build) got pretty long, so now I’m interested in drilling down on getting my Git stuff together for forking and PR’s and stuff.

What I’ve accomplished so far:
(1) Account on Github (and whaddaya know, it still links to stuff from when my company was getting started)
(2) I have made a fork of vassalengine/vassal into (apparently) Cattlesquat/vassal
(3) I have installed Git (and Git Bash) into my system, and it “seems to work” but I don’t have much (any) git command line knowledge so I’m afraid to “do anything”

So when I got to reading your second section (about keeping a “master” branch of my github and my local clone in sync with vassalengine/vassal) is when I start to get confused. Since I guess that means that “Cattlesquat/vassal” needs to be my “master” branch? So does that mean I need to be forking THAT to make the branch in which I will make my changes? Or should I be making a second fork of vassalengine/vassal, to be the one that changes?

I’m not sure if I should have “Cattlesquat/vassal” is master branch and “Cattlesquat/vassal/newbugfix” is the working branch? Or should I have “Cattlesquat/vassal/master” is master branch and “Cattlesquat/vassal/newbugfix” is working branch? Or something else entirely?

I am, in truth, probably far enough along that I could probably begin “successfully causing harm” by e.g. making a PR from my “Cattlesquat/vassal” branch. But I’d much rather get things set up in some modicum of “correctly” so that I can e.g. do several small bugfixes, put PR’s, etc, etc.

And I will admit that I couldn’t quite follow what you said in the second section (again about how to keep a “master” branch in sync with vassalengine/vassal).

At that point you contemplated writing a step-by-step guide. I would definitely be interested in such a guide! I’m making gradual progress, but it’s definitely an unfamiliar tool for me.

Thanks so much for your help & patience!

Brian

Ok, I’ll try it here and if it’s any good we can make this into Vassal’s official Git guide later.

On Windows, Git Bash is very good.

Some 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, step 1: Create a fork of github.com/vassalengine/vassal, the result is:

Setting up, 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.

Setting up, step 3: Configure Git on the local computer to know the GitHub account
Set username according to: help.github.com/en/github/using … ame-in-git
Set commit email address according to: help.github.com/en/github/setti … il-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.

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

Setting up, step 4: Tell the local repo where the central Vassal repo is located.
Follow this: help.github.com/en/github/colla … 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 / repos. 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:

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.

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:

Staying in sync, step 1: Make changes to central/master known to the local repo:

git fetch upstream

Staying in sync, 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

Staying in sync, step 3: Sync central/master with our local master:

git merge upstream/master

Staying in sync, 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.

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.

Changing code, 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

Changing code, 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

Pro tip, 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.

Changing code, 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

Changing code, 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 don’t write) and confirm.

Changing code, 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.

Changing code, 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.
Changing code, step 7: Delete the branch locally:

git branch -d bugfix-12345

Additional info:

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 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. Example, here I have changed 3 files, but only want to add 2 of them to the commit, the 3rd file will keep my changes but will not be added to this particular commit, I want to commit it later for some reason:
[attachment=0]commit.png[/attachment]

This concludes the instructions for using Git/GitHub.

Now some additional tips.

Pro tip 1: 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)

Pro tip 2: For 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

Pro tip 3: 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.)

Things worth noting:

  • ‘git fetch --all’ will fetch everything new from all remotes.

  • ‘git pull’ is the same thing as ‘git fetch’ and ‘git merge’ run in
    sequence. So if you’re on master and you intend to merge remote’s master
    into yours, ‘git pull’ will do the fetch and merge in one shot.


J.

Thank you SO much for all that information. I was able to use that to successfully create a PR for Bug 12980. I mean, it may not be accepted or anything, but at least I created one! :slight_smile:

So my NEXT question for you on this subject goes like this:

  • So now I’ve got my local “vassal” directory filled with the “Bug12980” fix, which meanwhile is in a PR and waiting accept/reject/etc, and could be for a while.
  • But now I’d like to work on a different bug, e.g. Bug13117 or something.
  • It doesn’t touch any of the same files as the other bugfix.

So I imagine what I’d like is a second vassal directory (e.g. instead of …/vassal, perhaps …/vassalbug2), into which I could now pull down the whole kit-and-kaboodle, make & test a set of change for eventually another PR, etc, etc.

So I guess I need to “clone my master” again into that directory, and just treat that as a whole separate block-o-crud? But of course it talks to my github master, that presumably means I need to make a second fork of vassalengine/vassal there?

Now if any of that is wrong or unworkable or a terrible idea, please let me know.

One thing in the your original description that I don’t fully understand yet is the idea of “switching branches” (you refer to switching back to my bugfix branch if I had switched away). So that makes me curious about (a) how would I go about switching away? and (b) WHY would I switch away? Is the idea that I can swap my original “…/vassal” local repository that we created above into other branches, and e.g. use my same directory tree to manage several different sets of fixes/commits? Perhaps that is at least in some cases better than my “multi-fork-multi-directory-tree” plan above? If so then obviously what I’m searching for are the relevant ways to deal with that via useful commands – because presumably when I switch to a branch I need it to pull me down a clean set of files for that branch, etc.

Many thanks,

Brian

Thus spake Cattlesquat:

So I imagine what I’d like is a second vassal directory (e.g. instead
of …/vassal, perhaps …/vassalbug2), into which I could now pull down
the whole kit-and-kaboodle, make & test a set of change for eventually
another PR, etc, etc.

No. What you should do is check out master again, and then make a new
branch off that.

The important thing here is that your local git repo stores all the
branches you have. You can switch between them just by checking out
a different one.

commands – because presumably when I switch to a branch I need it to
pull me down a clean set of files for that branch, etc.

Nope. You already have them. Git stores everything under the .git
directory. When you check out a branch, it recreates the state of that
branch from what it has in .git.


J.

Yep switching branches is what you need to do if you want to work on another bugfix, and that is the exact reason why you would want to switch branches.

Speaking in steps:

  1. Done with creating PR for bugfix-1
  2. Switch back to master
git checkout master
  1. Local master possibly got old in the meanwhile, there are new commits in the central master branch, do the “keep master in sync” workflow described above
  2. For bugfix-2, follow the “change code” workflow described above, i.e. create new branch, do commits and so on.

To see which branch is currently selected as the active branch, and whether there are any uncommitted files, do this:

git status

Try to avoid uncommitted files when switching branches, either commit them or revert them.

Thus spake Flint1b:

Try to avoid uncommitted files when switching branches, either commit
them or revert them.

Or stash them, using ‘git stash’.


J.

Ooh, git stash might be a way for me to avoid my project build settings being repeatedly overwritten. (see https://forum.vassalengine.org/t/anyone-know-what-changed-in-vassal-master-since-yesterday/10763/1)

Do you guys presumably make little “batch files” of some of these commands?

p.s. I am SO all about this becoming a wiki entry. Would mean I could also correct the little typos in a few of the commands :slight_smile:

I have created an initial version of the wiki page, it is awaiting moderation now. It’s mostly a copy of my initial guide in this thread with wikimedia formatting, but it’s a start.

Would be good if later on someone whose native language is english could read it and correct my mistakes, I might have learned proper Oxford English when I was young but decades on the internet with it’s various englishes and people writing “could of” and “there there there” instead of “their there they’re” have spoiled me.

Cool I’ll keep an eye out for it and give a pass through. I think your english is fine (you “could have passed” to me!). By edits I was referring to a couple syntax errors like ’ instead of " around a string, and a missing “commit” keyword, that kind of stuff.

Now a question from me, I’m not such a git master after all:

In a situation where a master has one remote master, yes, git pull.

But what if my local master branch has a ‘remote’ and an ‘upstream’, the ‘remote’ is in sync but the ‘upstream’ has new commits? Will ‘git pull’ fetch from both, see that ‘upstream’ has the more recent commits, then merge from ‘upstream’?

Thus spake Flint1b:

“uckelman” wrote:

  • ‘git pull’ is the same thing as ‘git fetch’ and ‘git merge’ run in
    sequence. So if you’re on master and you intend to merge remote’s
    master
    into yours, ‘git pull’ will do the fetch and merge in one shot.

Now a question from me, I’m not such a git master after all:

In a situation where a master has one remote master, yes, git pull.

But what if my local master branch has a ‘remote’ and an ‘upstream’, the
‘remote’ is in sync but the ‘upstream’ has new commits? Will ‘git pull’
fetch from both, see that ‘upstream’ has the more recent commits, then
merge from ‘upstream’?

When you first push a branch, you may have seen a message reminding you
to set an upstream for the branch (because you forgot to specify the
-u or --set-upstream argument). The argumnent -u wants is the name of
a remote. When you push with ‘-u somewhere’, this will cause there to
be a line which looks like ‘remote = somewhere’ to be written to your
.git/config in the section corresponding to your branch. Thereafter, if
you do a ‘git push’ (or ‘git pull’) on that branch without specifying
a remote, the one you set with -u is used as the default.

That is, ‘git pull’ will pull from the remote you set with -u. If you
want to pull from any other remote, you need to tell ‘git pull’ which
one, so e.g. ‘git pull somewhere_else master’.


J.

So, say I got my push all done and discovered it accidentally caught an extra file I didn’t want (in my case, usually “.classpath”).

Github was offering me a “delete” button and I thought that was going to purge that file out of my PR, but no apparently it was trying to delete it from the repo.

So in that case, is there a way in git to… reach into my existing PR and say “please remove from this PR all the changes to this one particular file”?

Tried to google some git stuff and I seemed to get as far as at least getting rid of the “delete” command from one of my PR’s, but it still wants to be touching the file.

I’m getting better at keeping .classpath from getting picked up in my commit in the first place, but I might as well learn how to knock unwanted things back out. If anybody knows how.

Brian

I had a similar problem where a commit I didn’t want got sucked into a PR. I just had to un-make the change and commit again. This adds an extra commit to your PR, but the PR process works by diff between the current master and the sum of your PR commits, so the unwanted commit ‘disappears’ from the PR.

There is now a wiki page for this: vassalengine.org/wiki/Using_Git

A link to that page from the wiki’s main page / development section is awaiting moderation.

Looks great – I just saved (to moderation of course) a few minor changes. There was one of the early examples where the keyword “commit” was actually missing from a couple of the git command lines. And I made a few minor “englishy” tweaks.

One thought I have – at one point you offer this command line:
git commit -a -m ‘applied requested changes’

And I think the “-a” is the part that was slurping up my .classpath into some of my early commits? I eventually learned to just do “git add” where needed and commit with a regular “git commit -m” and no -a and that seemed safer, since I that way I am always explicitly naming the things I want staged for commit and nothing gets put in there “automatically”.

So possibly you might want to teach that workflow instead? Anyway just an idea.

The git introduction is absolutely wonderful for someone like me!

Brian

Uh oh - so did I, I guess someone will have fun merging them :unamused:

Haha!

So meanwhile “Today in WTF Have I Done” – could you please take a peek at the weird relationship between PR34 and PR35?

What “I thought happened” yesterday was:
(1) I made an HTMLChatter branch and did a bunch of chatter-y stuff. Pushed. Made a PR.
(2) You reviewed the code, I made the changes in two batches. Pushed them. They showed up in PR. Yay, all is well.
(3) Meanwhile I got started on fixing the drag-the-whole-contents-of-deck problem, creating a Bugfix13036 (or whatever number it is) branch. Made the fixes. Pushed. Made a PR.
(4) But today when I went to LOOK at the Bugfix13036 PR (35), it seems to have all the Chatter commits in that PR too?!?!
(5) So then I thought “uh oh I committed them to the wrong place that means they’re not in the Chatter PR”… but I’d remembered them showing up when I was making the changes, and sure enough when I went back to PR 34 “there they are”.

So I’m not really sure what happened or even what on earth is going on, I’m afraid. I thought I was changing two completely different files in two completely different branches, but Git seems to (maybe?) think things are somehow conflated together.

Anything I can do to repair? Or am I misunderstanding something and everything is actually okay?

Brian

I cloned your github repo and here is what I see:

  • your origin/master (head i.e. latest commit in your master branch) is at commit b6ed17
  • your branch origin/HTMLChatter branches off of commit b6ed17 and adds 3 commits on top of that (f88ba4, d53096, 4b5327)
  • then your branch origin/Bugfix13036 branches off of commit 4b5327 of the HTMLChatter branch (!) and adds commit e04a12 on top of that

A PR is the difference between the master and the PR’s branches head.

The difference between master and your branches (the path from master to the branch head) is:

  • from master to HTMLChatter: commits f88ba4, d53096, 4b5327
  • from master to Bugfix12036: commits f88ba4, d53096, 4b5327, e04a12

This is because you branched Bugfix12036 off of HTMLChatter, if you had branched Bugfix12036 off of master it would look like this:

  • from master to HTMLChatter: commits f88ba4, d53096, 4b5327
  • from master to Bugfix12036: commit e04a12

Now this may be a mistake or may be correct, if the changes of Bugfix12036 have nothing to do with HTMLChatter then it’s probably a mistake, but sometimes the changes of one bugfix depend on the changes of another.

Now there’s two options, you either leave it as it is if the changes of Bugfix12036 depend on HTMLChatter, or you correct this.

If you leave it as it is, you can leave a comment under the Bugfix12036 PR and say that it should be merged AFTER HTMLChatter got merged, then the following will happen:

  • HTMLChatter will be merged, git will add commits f88ba4, d53096, 4b5327 to master
  • then the difference between master and Bugfix12036 will be commit e04a12 (since the other 3 commits already are in master)
  • the Bugfix12036 PR will change to only contain that one commit

If you want to fix it, you use my favorite part of git, the “rewrite history” part:

  • close the PR (or will it close automatically when we do the next step… it probably will?)
  • delete the Bugfix12036 branch on github (not the local one): git push origin --delete Bugfix12036
  • locally, run the following: git rebase --onto master HTMLChatter Bugfix12036
    This basically says, “Take the Bugfix12036 branch, figure out the patches since it diverged from the HTMLChatter branch, and replay these patches in the Bugfix12036 branch as if it was based directly off the master branch instead.”
  • then proceed as usual, push the Bugfix12036 branch to github and create a new PR

I admit I don’t know all of git by heart, I had to look up here git-scm.com/book/en/v2/Git-Branching-Rebasing :smiley:

Beautiful! Worked perfectly, thanks! (Yes I rebased it, they were completely unrelated and independent fixes).

My small followup is… making sure I understand how I managed to do that in the first place. Does this mean I was switched to HTMLChatter branch at the time I did my “git checkout -b Bugfix13036”? And the way to prevent that in the future (when I don’t WANT to create a dependent fix branch, of course) is to make sure I do the “git checkout master” drill to switch back, before the next “git checkout -b …”?

Thanks SO much! At least by the time Joel sees them they will be nice clean PR’s :slight_smile: Hopefully there will be enough value to the project to justify the time spent getting me git-literate…

Brian