Git

From Leo's Notes
Last edited on 6 February 2024, at 17:02.

Git is a version control system.

Introduction

There are a few basic commands that you should be familiar with. The following table covers 99% of all the commands I use with Git in the command line.

Family Task Command
Configuration Set name and email git config --global user.name "Leo Leung"

git config --global user.email "leo@somewhere"

Branches Creates a new branch and switch to it immediately git checkout -b [branch-name]
Switches to the branch git switch -c [branch-name]
Combines the specified branch to the current branch git merge [branch]
Deletes the specified branch git branch -d [branch]
Create a new branch from a specific commit git branch [branch] [commit-id]
Tags List tags. (-n for description) git tag -n
Fetch tags git fetch --all --tags
Checkout a specific tag git checkout tags/[tag] -b [branch]
Synchronizing Changes Downloads all history from the remote repo git fetch
Combines remote branches to local branch git merge
Uploads all local branches to the remote repo git push
Update the local branch with all new commits on the remote repo git pull (git fetch + git merge)
Changes Show the git version history on the current branch git log
Show the difference since the last commit git diff
Show the difference between two branches git diff [branchA] [branchB]
Add a file to be committed git add [file]
Create a new commit git commit
Amend the last commit git commit --amend
Amend the last commit with different author git commit --amend --author="Leo Leung <leo@somewhere>"
Mistakes Resets all changes after the specified commit git reset [commit]
Discards all history after the specified comit git reset --hard [commit]
Restore a deleted file (staged, before it's committed) git checkout -f [file]

Committing Files

A commit is like a snapshot of the code at a particular time in the repository. It is possible to recover code to a previous commit if something were to happen to the code (such as accidental bugs being introduced, or accidentally deleting or modifying some file).

Similar to SVN where files are included/excluded to a commit, git allows you to stage/unstage files to commit. A file that is staged will be added to the next commit that is made.

Typically, you will want to stage all files for commit which can be done by running:

$ git add -A .

Run git status to see all files that are staged as well as any changed but unstaged files.

$ git status

To commit all the staged files with a message:

$ git commit -m 'message'

If you need to commit your changes to a separate branch, such as if you started working on a new feature but it isn't quite ready yet, create a new branch and then commit.

$ git checkout -b wip_feature
$ git commit -a

Reverting Changes

To revert all changes in the current repository, use:

## Fetch latest from remote
$ git fetch --all
## Reset all changes fetched previously.
$ git reset --hard HEAD

To revert a specific file from a specific revision, use git checkout.

$ git checkout <revision> <filename>

Remote repositories

Git allows you to push/pull changes from other remote git repositories. Remote repositories can be in another path or hosted somewhere on the internet.

To see your repository's remotes:

$ git remote -v

To add a new remote:

$ git remote add origin git@gitlab-blah:user/repo.git

To remove a remote:

$ git remote rm origin

If your remote uses SSH, you will most likely need to configure SSH in order to make use of SSH key based authentication. You can read more about this at SSH Configuration File, but the basic idea is to create a SSH configuration file at ~/.ssh/config defining the remote's host.

Host gitlab-remote
    User git
    HostName git.example.com
    IdentityFile ~/.ssh/gitlab-remote

Branches

Create a new branch

To create a new branch from the master branch

$ git checkout -b new-branch-name

To create a branch from an existing branch

$ git checkout -b  new-branch-name  existing-branch

Delete a branch

To delete a branch

$ git branch -d  existing-branch

Pushing changes upstream

After making a new change in your repository, you may want to push your changes out to other repositories:

$ git push

If you need to specify the remote origin and branch (Eg. remote origin and master branch):

$ git push -u origin master

Tags

List tags

Local tags can be listed with git tag. Some additional options that are helpful are:

  • -n option to show tag descriptions and
  • -l to limit by pattern.
  • --sort=refname to sort by name or --sort=version:refname to sort by version numbers

Remote tags can be listed with git ls-remote --tags [remote]. Eg: git ls-remote --tags origin

The latest tag available can be found with git describe --tags `git rev-list --tags --max-count=1`

Fetching tags

Remote tags can be fetched into your local repo using git fetch --all --tags. This will pull all remote tags and make them appear as local tags.

Checkout tags

You can 'switch' into a tag on a particular branch using git checkout tags/[tag] -b [branch].

Pulling & Merging

When implementing a feature, it's ideal to separate each feature into its own branch. To test multiple features together, you will need to merge these branches together. Do this using the git pull command.

## Work on a feature
$ git checkout -b  feature-A  master
## Do your work and commit
$ git commit -a
$ git push origin feature-A

## Work on another feature
$ git checkout -b  feature-B  master
$ git commit -a
$ git push origin feature-B

## Then, to test both feature-A and feature-B together:
$ git checkout -b  testing  master
## Pull one, or multiple branches at once with the pull command.
## You will be asked to merge these commits to your branch.
$ git pull origin feature-A
$ git pull origin feature-B

## At this point, your code now should have the changes from both branches.

Note that git pull does a git fetch and git merge as one command. If you do not want to merge, run git fetch instead.

Merging

After creating a new branch development and making changes to this branch, you may want to merge the changes on the development branch back into master.

development$ git merge master
## Resolve any conflicts
development$ git checkout master
master$ git merge --no-ff development

The --no-ff flag prevents the merge from executing a fast-forward. This ensures that a new node will be constructed that records the merge event instead of a fast-forwarding on the branch.

Restore a deleted file

If you notice files are missing somewhere and aren't sure which commit triggered its deletion, run the following to determine the last commit that altered this path.

## Missing 'scripts' directory. Find the commit that deleted it.
$ git rev-list -n 1 HEAD -- scripts
2709b067bfc836c471ffa02ba569537e459e5e01

## To restore 'scripts', checkout that path from the previous (^) commit.
$ git checkout 2709b067bfc836c471ffa02ba569537e459e5e01^ -- scripts

See also: https://stackoverflow.com/questions/953481/find-and-restore-a-deleted-file-in-a-git-repository

Rebasing

If for some reason your master has diverged from the upstream repository (such as if changes were made to master simultaneously while you were working on your local copy), you will notice that the history between both repositories are different. A git pull will result in either a message about a diverged branch, or conflicts between the local and remote changes.

The fix is to either:

  1. Merge your work upstream (git merge origin/master), thereby integrating your changes with the upstream repo and then commit your changes, or
  2. Rebase your work on the upstream master using the git rebase origin/master command. Alternatively, you can also git pull with the rebase option (git pull --rebase origin/master) to rebase with the local work as part of the pull.
  3. If you don't care about your changes, git reset --hard origin/master will bring your local repo up to speed with the upstream, discarding any changes you've already made.

Identities

Users typically use the global identity when using Git. This is great if you use the same identity across all repos.

In order to use different identities, unset the global identity and require that identities are specified from each repository's configuration. Without specifying any identities, git commits will use the system's hostname and username which is not desired.

# git config --global --unset user.name
# git config --global --unset user.email
# git config --global --unset user.signingkey
# git config --global user.useConfigOnly true

Create each new identity.

See also: www.micah.soy/posts/setting-up-git-identities/

Tasks

Removing a large file from commit history

If someone accidentally committed a large file into git, you can remove it using the BFG Repo-Cleaner (https://rtyley.github.io/bfg-repo-cleaner/) java utility. This tool will look for any deleted files and remove it from the history. In doing so, it will also rewrite all the affected commits.

$ java -jar bfg.jar --strip-blobs-bigger-than 10M my-repo
$ cd my-repo
$ git reflog expire --expire=now --all 
$ git gc --prune=now --aggressive
## If you have the repo hosted somewhere, do a force push to overwrite the history there.
$ git push --force

In GitLab, ensure that all affected branches are deleted and then re-pushed. Otherwise, the file might still be lingering even after doing a house cleaning.

Correcting multiple commits' author

If you accidentally committed something with the wrong name and email address, you can correct it with:

$ git commit --amend --author="Leo Leung <leo@example.com>"

If you've made multiple commits with the wrong name and email address, then you'll have to use git filter-branch --commit-filter to rewrite the history like so:

## Change the author for the past 2 commits
$ FILTER_BRANCH_SQUELCH_WARNING=1 git filter-branch -f --commit-filter '
GIT_AUTHOR_NAME="Leo Leung";
GIT_AUTHOR_EMAIL="leo@example.com";
git commit-tree "$@";
' HEAD~2..HEAD

See Also

Self hosted applications:

  • Gogs
  • GitLab
  • Gitea, a fork of Gogs but has feature set that rivals GitLab.

Tools:

Resources