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:
- A repo is a source of information regarding project history and current state of code
- A branch is a logically separate lineage of code which can be merged back in to the ‘master’ copy (also a branch).
- To get a new copy of a git repo, you
clonethe repository to your own working space. - You make any changes you’d like, then
committhem to the local repo. - At any time, you can
pushall your commits and code to another repo (called the remote repo) - You can also
pullany changes made to the remote repo and merge them in to your local copy.
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:
- blob: file data
- commit: a snapshot of the whole file system tree rooted at
$REPO_ROOT - tag: specific named pointer to a specific commit
- tree: representation of a directory
Each commit is labeled by its SHA1 hash. A commit includes:
- The hash of the root tree
- The hash of the parent commit(s)
- Commit message
- Author
- Committer
- Date/time of the commit
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 soglabcan manage your account, and an SSH key so everydaygit pushprompts 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:
- Sign in to your instance, e.g.
https://gitlab.cecs.pdx.edu. - Top-right avatar → Edit profile → Access tokens (direct URL:
https://gitlab.cecs.pdx.edu/-/user_settings/personal_access_tokens). - Add new token: give it a name (e.g.
laptop-glab), an expiration date, and select scopes. - 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
apiscope 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:
-e, --expires-at YYYY-MM-DDTHH:MM:SSZ— set an expiry (ISO 8601).-u, --usage-type—auth(a plain push key),signing(commit signing), orauth_and_signing(the default).
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 addis authenticated by your PAT (theapiscope), 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
User git— every SSH-based git host logs in as the literal usergit; the repository path carries your identity.IdentityFile— the private key (no.pub).IdentitiesOnly yes— offer only this key. Without it, SSH presents every key your agent holds, and a busy agent can trip the server’s “Too many authentication failures” limit before it ever reaches the right one.
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
Hosta short alias: a block withHost pdxandHostName gitlab.cecs.pdx.edulets yougit clone git@pdx:group/project.git. The alias only has to match what you type in the URL.
Key takeaways
- git is distributed: every clone is a complete repository with full history. Most teams still designate one central “blessed” remote (GitHub/GitLab) by convention, not because git requires it.
- git tracks content, not files: identical content is stored once as a single blob, empty directories can’t be tracked, and the object store holds four types — blob (file data), tree (directory), commit (a snapshot + parent + metadata), and tag — each named by its hash.
- The core loop is clone/init → edit →
add(stage to the index) →commit(to the local repo) →push(to a remote) /pull(from a remote). The index (staging area) sitting between your working tree and the repo is the part newcomers miss. - Configure identity once with
git config --global user.name/user.email/core.editor; every commit records author, committer, parent(s), tree, and message. - A repo can have multiple remotes; with one, convention names it
origin— but you can addproduction,codeberg, etc., and choose where topush. - On a self-hosted GitLab, authenticate from the CLI with a PAT (API/HTTPS git; needs the
apiscope to manage keys) and/or an SSH key uploaded withglab ssh-key add. A non-default key name must be wired into~/.ssh/configwithIdentityFile+IdentitiesOnly yes, or SSH will never try it.
References
- The Pro Git book (free, comprehensive — the canonical reference). https://git-scm.com/book
- Official
gitreference manual. https://git-scm.com/docs - Roger Dudler, git - the simple guide. https://rogerdudler.github.io/git-guide/
- Learn Git Branching — interactive challenges. https://learngitbranching.js.org/
- GitLab CLI (
glab) — installation and command reference. https://docs.gitlab.com/cli/ - GitLab personal access tokens — creating, scoping, and revoking. https://docs.gitlab.com/user/profile/personal_access_tokens/
- Using SSH keys with GitLab. https://docs.gitlab.com/user/ssh/
- OpenSSH
ssh_configmanual —IdentityFile,IdentitiesOnly, and host matching. https://man.openbsd.org/ssh_config
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
mastertomain(set yours withgit 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, theglabinstall commands, and theglab auth login/glab ssh-key addflags all drift between releases — verify them against the linked GitLab CLI docs if they don’t match what you see.