Git and GitHub

From CUSF Wiki
Jump to navigation Jump to search

Git is a distributed version control system used for most software projects worldwide. Here at CUSF, we plan to use Git for CAD as well through Charon

GitHub is one of many providers of Git remotes, i.e. a centralized place to store Git repositories. It is used by CUSF, you can find our organisation on GitHub here

Installation

Linux

If you’re using Linux you probably know what you’re doing. If not, just… use your package manager…?

MacOS

via Command Line Developer Tools

Typing git into a terminal on macOS will (by default) create a pop-up window asking you to install the command line developer tools. This will install "Apple Git", which is an out-of-date version of Git, compiled by apple for some reason. It should work fine.

via Homebrew

Adapted from docs written by Weixuan Zhang

Many macOS users enjoy the package manager Homebrew, which you can install from a terminal with

/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"

Then you can install git (and any other packages you want) in a similar way to Linux, with:

brew install git

If you encounter a permission error, be cautious when you copy and paste solutions from StackOverflow. Make sure you don’t mistakenly give root privileges to all files in /usr/local/.

Windows

You may also be interested in downloading the Windows Terminal from the Microsoft Store for a slightly nicer experience.

via Git Bash

The simplest solution is to use Git Bash, which you can get from the official download page. The default options are fine. The main drawback is that Git bash does not give you access to the Git man pages (manuals).

via MSYS2

A more extensible solution is to use MSYS2, which gives you access to a native UNIX-like environment. You can then install Git (and the man pages, which is highly recommended) from an MSYS2 terminal via:

pacman -S git man-db man-pages-posix

via Windows Subsystem for Linux

This is not recommended right now, as it is not yet compatible with Charon

Usage

This is not comprehensive, only a quick reference for basic usage. Your first port of call if you want to know more should be the man pages (manuals), which are accessible via:

man git

you can also access a glossary of terms though man:

man gitglossary

If in doubt, type git status into the terminal. It will give you some informative output of what is currently going on.

UNIX-like terminals

BEWARE: files deleted on a terminal will not usually go to the recycle bin

Refer to these docs from the SRCF for a good intro to UNIX-like terminals (i.e. the kind of terminal you should be using Git from, see Installation). Some understanding of these terminals is required to use Git. There are graphical clients for Git, but usually they should be avoided, as they obscure what's going on (and won't work with Charon).

Repositories

Repositories are usually self contained projects. They correspond to a folder on your computer, and are usually synced to a remote (e.g. GitHub).

To 'clone' an existing repository, i.e. copy the latest version from GitHub:

git clone https://github.com/username/project

This will create a local directory project containing the repository.

If you want to create a repository to use with GitHub, it's probably easiest to click the plus button on the GitHub website in the top right and let it tell you what to do.

If you are creating a repository just locally, the following command will turn the current directory into a repository:

git init

Commits

To 'commit' is to make a record of your changes. Except in some special circumstances, commits will always exist in the history of the repository, so that changes in the project can be tracked and rolled back if necessary.

In order to make a commit, you first need to 'add' changes to Git, which means making Git aware of the changes. Having to do this can be useful if you only want to commit some of the changes that you have made.

Note that you can use git status to see what changes have been added

To add just the file file:

git add file

To add all files in the current directory:

git add .

To commit the changes that have been added:

git commit -m "Commit message"

"Commit message" should be a short description of what you have changed. (Include the quotes)

Sometimes you realise you need to change something right after commiting. For this, the following command is useful. It will overwrite the previous commit instead of making a new one.

git commit --amend -m "Commit message"

Note that you should only do this before pushing, or conflicts will occur.

Pushing and Pulling

In order to sync your local changes with the remote (e.g. GitHub), you must push and pull: pushing uploads your changes to the remote, and pulling gets the changes from the remote.

To pull:

git pull

To push:

git push

The first time that you push, Git does not know which branch on the remote to push to, so you instead need to use

git push --set-upstream origin master

you can substitute master with any other branch that you want to push to.

Branches

N.B: Would be nice to add a `git branch --oneline --graph` from something complex.

Branches are chains of commits. In a repository, any two branches will have a common ancestor, which makes them akin to the branches of a tree.

There is one branch which is the canonical state of the project, usually called 'master', although alternatives are often used. In this page and most of the cuspaceflight repositories, 'master' is the name used, but GitHub now prefers 'main'

Typically different people will add changes to different branches, and then they will be merged together. See Pull Requests

To create a new branch based on the current branch:

git checkout -b branch-name

To switch between branches:

Try to keep the working tree clean when doing this or it will get complicated. (i.e. don't have changes which aren't yet part of a commit)

git checkout branch-name

To push a branch:

git push origin branch-name

Navigating between commits

git checkout can also be used to navigate between any commits in the repository. There are many ways to specify a commit, but here are some useful ones:

HEAD the commit that is currently checked out
foo~ the commit before foo, for example HEAD~ is the commit before the current one
foo~N the commit N commits before foo, for example other-branch~N is N commits behind

the latest commit on the "other-branch" branch

Confusion regarding checkout, switch, and restore

A source of confusion is that the old git checkout command has been somewhat replaced in recent versions of Git (with backwards-compatibility) by git switch and git restore. This page uses the older git checkout, but note that the newer commands are used in some places too. If in doubt, have a look at the man pages for each command.

Pull Requests

Pull Requests are a request to include the changes that you have made to your branch in the master branch. They are specific to GitHub, alternatives have their own way of doing things (which is often very similar).

The easiest way to create a pull request is to go to github.com, navigate to the page for your repository, open the branch you want to merge, and click the icon to open a pull request.

Merging

When multiple people are working on the same thing at the same time, conflicts can occur. Merging is the way to fix this. The best way to explain is with examples.

Note that in the following, master and origin/master can be replaced by any two branches

Consider the following scenario (no merge required):

           E--F--G    master (the master branch on your computer)
          /
A--B--C--D            origin/master (the master branch on GitHub)

when you do a git push, your changes can be applied directly on top of origin/master exists, so it will go smoothly, resulting in this:

A--B--C--D--E--F--G    master and origin/master

Now consider the following scenario (merge required):

     E--F--G    master (the master branch on your computer)
    /
A--B--C--D      origin/master (the master branch on GitHub)

Here your local changes can't be applied right on top of the remote, and GitHub will reject any attempt to push.

The solution is "merging", where the changes from one branch are incorporated into another. To merge origin/master into the current branch, you can do

git merge origin/master

Note that git pull will attempt a merge by default if it is required

In the example above, git will compare commits G and D to B and try to resolve any conflicts. If this goes smoothly, it will result in the following

     E--F--G--H   master (the master branch on your computer)
    /        /
A--B--C--D---     origin/master (the master branch on GitHub)

Now both H is a direct successor of D (as well as G), so it can be pushed to origin/master.

If the merge fails, it gets more tricky. Git will output something like this:

Auto-merging some.file
CONFLICT (content): Merge conflict in some.file
Automatic merge failed; fix conflicts and then commit the result.

If you are using Charon, merge resolution is a bit different, and the rest of this section is not relevant

You have a few options at this point:

  1. use git mergetool, which by default will open vim. See Exiting vim if you get stuck
  2. edit some.file manually. The conflicts can be seen in plain text, and you can just replace them with the right code
  3. attempt to use a plugin for your IDE
  4. cry and try to pawn off the conflict resolution to someone else
  5. rewrite all your local changes on top of origin/master and hope nobody else pushes
  6. give up and work on another project

Once conflict resolution is complete, add the files which conflicted, and then continue the merge with:

git merge --continue

Rebasing

This is an advanced topic, only read this when you're already comfortable with everything else

An alternative to merging is "rebasing". Consider the following scenario:

     E--F--G    feat-1
    /
A--B--C--D      master

If feat-1 is "rebased" on master, the history will now look like this:

           E'--F'--G'  feat-1
          /
A--B--C--D             master

In this case git will first look at the changes from B to E and try to apply them to D, then the the changes from E to F are applied to E', etc. The conflicts are thus resolved, and feat-1 can be applied directly on top of master.

This can be done with the following command (assuming feat-1 is checked out)

git rebase master

Git will then reject a push to feat-1 because you are rewriting history, so you will have to force it through

Warning: this is a very dangerous command. Use with *extreme* caution

git push -f

There are some advantages and disadvantages of this.

Not a comprehensive list yet

Advantages of rebasing Disadvantages of rebasing
Conflicts can be resolved early, so if feat-1 continues to be worked on, the

merge conflicts are not looming over the branch (and conflicts tend to get worse over time)

feat-1 has to be forcefully overwritten (as the original commits

are being removed). This can be dangerous if done improperly

The git history ends up being much cleaner and more understandable Everyone who uses the feat-1 branch will need to forcefully

overwrite their local history, which is annoying if rebasing is being done too often.

Each conflict resolution must be done individually, as opposed to merging

where only the lastest commit on each branch, and the common ancestor, is involved.

Typically rebasing is useful when a branch is not close to being ready to be merged, but has significant conflicts with master which are likely to get worse. It's a bit of a grey area though. Make sure you liase with anyone else depending on the branch before rebasing it.

Submodules

Submodules are Git repositories inside Git repositories. They can be useful for a few reasons:

  • Reusing a project in multiple places
  • Using someone else's project as a dependency
  • Segmenting projects into sub-projects.

When a submodule exists within a project, it's just a link to another repository. Cloning a repository which has submodules will not (by default) clone the project's submodules.

To initialise submodules (after cloning the repo):

git submodule update --init

To add a submodule (location is optional, it will go to a new directory repo-name under the directory of the repo by default).

git submodule add https://github.com/org-name/repo-name <location>

To update all submodules to the latest commit in their repository

git submodule update --remote

To completely remove a submodule (yes it is this annoying)

git submodule deinit repo-name
# now delete the relevant lines in .gitmodules
rm -rf .git/modules/repo-name

Help and Troubleshooting

As the old adage goes… RTFM. man git should be your best friend. Run it on any terminal where you use Git. It’s much better than any online tutorial you will ever find (including this one). There are also man pages for each individual Git commands, try man git-add, man git-commit, and so on.

For common problems, you can also refer to Oh Shit, Git?!, but do make sure you understand what you are doing (using the man pages), or you'll have a bad time.

If you're still stuck, do ask someone for help, but make sure you give it a good go on your own first.

Exiting vim

Occasionally Git commands will open vim. Tim's advice is to learn vim, it's worth the effort. If you really want to just exit, the following will work every time. (there are simpler ways when you know what you’re doing).

Type <ESC>:qa!<Enter>, where <ESC> and <Enter> are the escape and enter keys respectively. (You'd be surprised how much clarification this usually needs)