Git is probably the most used developer tool (aside from operating systems, shells and browsers/editors) out there. It can be a lifesaver but if used wrong cause terrible headache. Here are some commands and snippets I’ve encountered and found useful.

Prerequisites

I’m assuming that the default remote in these examples is origin and a foreign remote is called remote. As example remote URL I will either use pseudo local paths, random urls or linux because it has infinite collaborators.

The default branch is master and any other branch is mostly called branch. The example tag in these examples is in most cases v1.0.0.

Git Subcommands

git lfs

Git-LFS (Large File Storage) is a git plugin/subcommand that lets you save binary files outside of the git object storage. This saves some space when cloning and works faster. To install it, follow the instructions on the website.

Repository setup

Assuming you want to track .bin files with Git-LFS:

  1. Change into project directory
  2. Initialize Git-LFS:
    git lfs install
    
  3. Tell Git-LFS which files to track:
    git lfs track "*.bin" 
    
  4. Add the .gitattributes file and commit:
    git add .gitattributes && git commit 
    

Alternatively, you can skip/speed up step three by putting the following in the .gitattributes file (create it if it’s not there):

*.bin filter=lfs diff=lfs merge=lfs -text

git log

When run with no arguments, git log shows a vi/less style commit history output. But it can also be used for more specific stuff.

Show log for specific remote/branch/tag

The following command can be used to show the history of a specific tag or remote and/or branch:

git log remote/master

Show log for specific subdirectories

You can use the following command if you’re in a subdirectory of a repository and only want to see the local history:

git log -- ./

The -- delimiter is used to make a clear separation between refs and paths. This can also be seen in some git error messages.

git tag

A tag is like a bookmark on a specific commit. Mostly it’s a good practice to tag the version bump commits (if those are made).

Listing tags

While git tags can be seen as an appendage next to the commit id in git log, it’s also possible to show all available tags.

Local tags

I think the command below is self-explanatory:

git tag -l
Remote tags

Listing remote tags can either be done for the default remote:

git ls-remote --tags

Or a specific remote:

git ls-remote --tags remote

Creating tags

A tag can be created like this:

git tag v1.0.0

The tag will now show up in the git history as v1.0.0 which is an often used scheme. Btw have you heard of semver?

Pushing tags

Since git push doesn’t submit patches by default, a flag is needed:

git push --tags

Deleting tags

Deleting tags can be necessary when doing debug stuff (like CI that should build on every tag but there’s a bug in a script of a dependency that only runs on CI so you end up with something like v1.0.0.a.a.a.a.a.a.a.a.a.a.h.e.l.p).

Local tags

Local tags can be deleted quite easily:

git tag -d v1.0.0

Remote tags

When removing remote tags, it’s required to to a push:

git push remote :v1.0.0

You can see more info on this syntax in the section about git push below.

Update: Since git 1.8.0, you can also use the following which is a bit more readable:

git push --delete remote v1.0.0

git push

The push command allows to submit changes to a remote. When used without arguments in a properly set up repository, it will push all new (for the remote) commits on the current branch (or it’s remote counterpart) to the default remote.

git remote

A remote is in most cases a collaboration server like GitLab, GitHub or Gitea, but it can in general be any local (or local-ish mounted) or remotely (ssh/http(s)) available .git folder.

Showing available remotes

All available remotes can be printed out by running:

git remote -v

Setting a default remote

A default (upstream) remote can be set by running the following:

git push -u remote branch

This sets the foreign remote called remote as upstream that will be used by default when running push or pull. The -u flag can be extended to --set-upstream.

Adding a remote

Adding a remote can be done by running the following:

git remote add remote /path/to/repo/.git

When adding a remote remote (haha), a protocol and/or authentication might be required:

git remote add remote ssh://git@example.com:foo/bar.git

Deleting a remote

Deleting a remote can be done like this:

git remote remove remote

Changing a remote’s URL

When a repository was moved or there is some other stuff going on, a remote url can be changed by running the following:

git remote set-url remote /path/to/new/repo/.git

Note that by default, only the fetch url is updated by this command. It’s also default for git to use only the fetch url when a push url is not explicitly set.
In case of local mirrors (see section about git clone below) it might be required to set the push url to the original remote. This can be done by appending the --push flag after set-url (and before the url).

git clone

Cloning means downloading/synchronizing a remote repository to/with your machine. It’s used to get an initial copy of the repository (updating is done with git pull instead).

Cloning in general

Most Git platforms mentioned above support cloning by passing the URL of the repository’s web frontend like the following:

git clone https://github.com/torvalds/linux

This will result in the repository being cloned into the current folder as linux. On some platforms, the URL might look more like https://github.com/torvalds/linux.git. In this case, git will omit the .git extension for the local folder.

Changing the output directory

To clone a remote repository into a folder other than the repository name, you can simply append it. If it does not exist, git will create it automatically. Example:

git clone /path/to/repo/.git ../otherName

The command above will result in the repository being cloned into the parent of the current folder as otherName. You can also specify an absolute path.

Cloning as mirror

In some cases it might be wise to store an exact copy of a remote repository on another place. For instance when the connection to a remote repository is pretty slow or the network has a limited transmission rate.

It can be a good idea to clone a very remote repository onto a more local server and let local clients pull from there.
The remote repository can be cloned as mirror by appending the --mirror flag:

git clone --mirror ssh://user@host:repo.git

The mirror can be kept up to date by running git remote update. This can be intervalled by using something like the following crontab which will update the mirror every fifteen minutes:

*/15 * * * * cd /path/to/mirror && git remote update >/dev/null 2>&1

Advanced Stuff

Below is more advanced stuff. Some of it requires additional software.

Cleaning up messy contributor stats

We all probably had to deal with this at some point: The committer you use in your git hoster is not the same you use locally and suddenly you appear as three different people because you made commits on your local machine, the web ui and maybe some dev system.

You can clean this up by rewriting the git history and changing the duplicated committers to use your desired data. Note that this requires a force push and will open a whole new can of worms when lots of people use the repo!

First install git-filter-repo, and then modify the following command to suit your setup (the callback is python code):

The bytearray("".encode()) at the new_name variable is needed in case the committer name you want to use contains special characters. You could also supply a plain string if your name doesn’t contain special chars.

git filter-repo --commit-callback '                                                                       ⬡ 16.15.0
old_email = b"duplicated_committer@example.com"
new_email = b"real_committer@example.com"
new_name =  bytearray("New Committer Name".encode())

if commit.author_email == old_email:
    commit.author_email = new_email
    commit.author_name = new_name

if commit.committer_email == old_email:
    commit.committer_email = new_email
    commit.committer_name = new_name
'

Note: git-filter-repo will complain when not run on a fresh clone, and will delete your remotes, so you don’t accidentally do weird stuff. You’ll need to re-add them in order to force push after the correction.