courses

Using git

Version control, and why git’s model matters

Every assignment in this course is turned in by committing and pushing to a git repository, so fluency here is not optional — and git rewards understanding its model far more than memorizing commands. Git is a distributed version control system, originally created by Linus Torvalds in the spirit of BitKeeper (which was proprietary at the time). It is used to track changes to a project’s source code, manage the project’s history, collaborate with others, and manage releases. As Torvalds once said, he really likes naming things after himself, hence the name git.

At its core, the way most people use it, is as a variation on svn. I mention this to highlight that while git is a distributed version control system, meaning in theory there is no canonical repository, most folks do have a central repository, usually hosted on GitHub or GitLab. There’s nothing wrong with this approach at all, but git was developed to handle the distributed nature of the Linux kernel development model – everyone had their own repo that handled their development, then they merged with the ‘blessed’ repo owned by Torvalds.

These mergers are handled by what are known as pull requests, where a developer notifies another developer that a changed or new feature is available for them to merge into their own repo.

Some terminology:

One common complaint you may hear about git is that “you need to be a plumber in order to go to the bathroom.” One of the primary goals of software development is abstraction. As a tool, I shouldn’t need to know how it works internally in order to use it. Tools which require that have what is known as a leaky abstraction. git doesn’t have a leaky abstraction – that would require that it had any abstraction at all. It does not. You must be aware of, to an absurd degree, exactly how git works and does what it does in order to use it effectively.

At the network level, git supports HTTPs, HTTP, ssh, and its own protocol (the git:// URI scheme). You do need to know which protocol a given repo is using, as you may need to deal with certificates or SSH keys.

All that said, there’s some important things you’ll need to know. git makes use of significant metadata which define a directory as a repository. Residing in the $REPO_ROOT/.git/ directory, this metadata is:

.git
├── .git/branches/          # List of branches that exist for the current repository
├── .git/COMMIT_EDITMSG     # Contains the commit message of any uncommitted, but staged content
├── .git/config             # This is the main `git` configuration file. It keeps specific `git` options for your project
├── .git/description        # It will show when you have viewed your repository or the list of all versioned repositories
├── .git/HEAD               # This file holds a reference to the branch (or commit) you currently have checked out 
├── .git/hooks/             # Directory contains shell scripts that are invoked after the corresponding `git` command
├── .git/index              # The `git` index is used as a staging area between your working directory and your repository
├── .git/info/              # Contains additional information about the repository.
├── .git/logs/              # Keeps records of changes made to refs
├── .git/objects/           # In this directory the data of your `git` objects are stored – all the contents of the files you have ever checked i
└── .git/refs/              # This directory normally contains the tips of branches and tags in  three subfolders – heads, remotes and tags

It is important to emphasize that git tracks content, not files. This means that if two files have the same content, only a single blob will exist in the .git/objects tree. This is also true of any identical file trees – only a single copy of the tree will exist, with the appropriate references to recreate the tree as necessary in the cloned repository. As a corollary, empty directories can’t be tracked by git.

Only the root of the repository will have a .git/ folder. Any subdirectories will simply be file trees in the .git/objects database. The .git/objects database contains 4 distinct types of objects:

Each commit is labeled by its SHA1 hash. A commit includes:

You can have many commits to your local repo, with only a single push. All of the commits will be pushed to the remote repo as distinct commits, each with their own copy of the above information.

Depending on your work flow, there may be a single “blessed” repository that is managed by an individual or group, or there may be parallel internal and public repositories (with privileged information redacted from the public repo). There may also be a code review process, using something like Gerrit (a code review system which integrates with git).

If you have not already done so, you will need to configure your name, your email, and (at minimum) your preferred editor (this will be used for commit messages).

$ git config --global user.name “Alice” 
$ git config --global user.email “alice@collab.net” 
$ git config --global core.editor code

Global settings apply to all repos on this host – unless or until you define repo-specific values.

An example workflow for configuring a new repository:

$ git init my-project           # initialize an empty repository
$ cp $EXTERNAL_PATH/file1 .     # add a file (with content) to file tree
$ git add file1                 # stage the new file to the index
$ git status                    # show the state of the repository, based on the index
$ git commit -m  “Add file1”    # commit all staged files, with the message "Add file1"
$ git remote add origin $URI    # add the remote repo URI to this repo
$ git push                      # push all committed changes to the remote URI

You can have multiple “remotes” for a given repository. If there is only one, tradition calls it origin. You may also have a remote called testing or production with different URIs; in this case, you need to specify the destination of git push.

While the above shows how to handle a newly defined repository on your local machine, you may also create it in other ways. A common example is to create a repository on the GitHub website, then clone it locally. You can make any necessary changes, then push the staged commits back to GitHub. As such:

$ git clone $URI                # clone the repo found at URI
$ cp $EXTERNAL_PATH/file1 .     # add a file (with content) to file tree
$ git add file1                 # stage the new file to the index
$ git status                    # show the state of the repository, based on the index
$ git commit -m  “Add file1”    # commit all staged files, with the message "Add file1"
$ git push                      # push all committed changes to the remote URI

git is a tool with a fairly shallow learning curve when everything works as expected. Then it’s a sheer cliff in the night, with an evil soul randomly throwing lubricant all over all the hand- and foot-holds.

I won’t be getting into branches, rebasing, stashing, or other more complex uses of git often seen in a more distributed setting. See the references below for more on those topics, or take a software engineering class which covers git in much more nitty gritty detail as a software development lifecycle tool.

Authenticating to a self-hosted GitLab from the command line

GitLab gates writes behind authentication. On a self-hosted instance (this course uses gitlab.cecs.pdx.edu) you can’t lean on the defaults the CLI tools bake in for the public services — you authenticate with either a personal access token (PAT) or an SSH key that you register on the instance. This section does both, entirely from the terminal.

ℹ️ Two credentials, two jobs. A PAT authenticates you to GitLab’s API and HTTPS git, and is what CLI tools use to talk to the instance. An SSH key authenticates git@… SSH clones and pushes. You usually want both: a PAT so glab can manage your account, and an SSH key so everyday git push prompts for nothing.

Creating a personal access token (PAT)

A PAT is a revocable, scoped stand-in for your password. You create the first one in the web UI — there is no way to mint a credential from nothing:

  1. Sign in to your instance, e.g. https://gitlab.cecs.pdx.edu.
  2. Top-right avatar → Edit profileAccess tokens (direct URL: https://gitlab.cecs.pdx.edu/-/user_settings/personal_access_tokens).
  3. Add new token: give it a name (e.g. laptop-glab), an expiration date, and select scopes.
  4. Click Create personal access token and copy it immediately — GitLab shows the glpat-… value exactly once.

Which scopes:

Scope Grants
api Full read/write API — required to manage SSH keys, open MRs, etc. with glab
write_repository git clone/pull/push over HTTPS (no API access)
read_repository git clone/pull over HTTPS only

⚠️ The api scope is broad — it can do anything you can. Give it a real expiration date, store it like a password (a secrets manager or an un-committed ~/.env), and revoke it from the same page if a machine is lost.

For HTTPS pushes you can paste the PAT into git’s password prompt, but it’s far more convenient to hand it to glab, below.

Installing glab

glab is GitLab’s official CLI. Install it for your platform:

# Windows (pick one)
winget install glab.glab
scoop install glab

# macOS
brew install glab

# Linux
brew install glab            # Homebrew — the officially supported path, newest version
sudo apt install glab        # Debian / Kali / Ubuntu (may lag a release)
sudo snap install glab       # any snap-enabled distro

Confirm with glab --version.

Logging in with your PAT

Point glab at your instance and authenticate. Interactively:

glab auth login --hostname gitlab.cecs.pdx.edu
# choose: Token  →  paste your glpat-… value

Non-interactively (handy for lab VMs and the container) read the token from stdin so it never lands in shell history or the process list:

glab auth login --hostname gitlab.cecs.pdx.edu --stdin <<< "$GITLAB_TOKEN"

glab also honors the GITLAB_TOKEN environment variable directly, and it takes precedence over stored credentials — useful for one-off scripted runs.

Uploading an SSH key with glab

If you don’t have a key yet, generate one with a distinct name so it won’t collide with existing keys:

ssh-keygen -t ed25519 -C "you@pdx.edu" -f ~/.ssh/pdx_gitlab

Then upload the public half (the .pub file). The --title flag is required:

glab ssh-key add ~/.ssh/pdx_gitlab.pub --title "$(hostname)"

Useful flags:

With no file argument it reads the key from stdin, so glab ssh-key add -t laptop < ~/.ssh/pdx_gitlab.pub works too.

ℹ️ Notice the bootstrap order: glab ssh-key add is authenticated by your PAT (the api scope), because the key you’re uploading isn’t usable for anything until it’s registered. The PAT is what breaks the chicken-and-egg.

Telling SSH to use a non-default key

SSH only tries the default key names automatically (~/.ssh/id_ed25519, id_rsa, id_ecdsa). If you named your key something else — like ~/.ssh/pdx_gitlab above — you must point SSH at it explicitly in ~/.ssh/config:

# ~/.ssh/config
Host gitlab.cecs.pdx.edu
    HostName gitlab.cecs.pdx.edu
    User git
    IdentityFile ~/.ssh/pdx_gitlab
    IdentitiesOnly yes

Test, then clone over SSH:

ssh -T git@gitlab.cecs.pdx.edu          # → "Welcome to GitLab, @you!"
git clone git@gitlab.cecs.pdx.edu:group/project.git

The Host block is matched by the hostname in that URL, so the right key is selected automatically.

ℹ️ You can also give Host a short alias: a block with Host pdx and HostName gitlab.cecs.pdx.edu lets you git clone git@pdx:group/project.git. The alias only has to match what you type in the URL.

Key takeaways

References


Related course pages: Technical Writing · Software Configuration

🛠️ Maintenance note: git’s model is decades-stable. Two slow-moving changes to watch: the default initial branch name shifted from master to main (set yours with git config --global init.defaultBranch main), and git is gradually adding a SHA-256 object format to replace SHA-1 — still opt-in, but worth knowing the hash in “each commit is labeled by its SHA-1 hash” will not always read SHA-1. The GitLab-specific section moves much faster than git itself: the token-settings UI path, the glab install commands, and the glab auth login / glab ssh-key add flags all drift between releases — verify them against the linked GitLab CLI docs if they don’t match what you see.