Category Archives: Git

Git worktree

Git worktree allows us to maintain multiple working directories for a single repository, the benefits of this are that we can work on multiple branches at the same time.

Ofcourse your first question might be, well I can checkout a repo multiple times in different folders/directories already, so what’s the point?

Let’s compare multiple checkouts against git worktree

  • Disk usage
    • Multiple checkouts: Each clone has a full .git directory, incl. all refs etc.
    • Git worktree: All work trees share a single .git folder and hence lightweight
  • Performance
    • Multiple checkouts: Cloning is slower, esp on large repos
    • Git worktree: As no clone takes place and reuses .git this is very fast
  • Consistency
    • Multiple checkouts: Clones can drift, i.e. different removes, configs etc.
    • Git worktree: As worktree’s share .git they share remotes, configs etc.
  • Branch Isolation
    • Multiple checkouts: With multiple checkouts, can become confusing
    • Git worktree: Only allows one instance of a branch to be checked out, avoid divergence
  • Maintenance Overhead
    • Multiple checkouts: Multiple clones means multiple .git and folders to maintain
    • Git worktree: A central .git folder with lightweight trees
  • Use Cases
    • Multiple checkouts: Great if using multiple remotes and sandboxing
    • Git worktree: Great for parallel branch work

Usage

I find it easiest if you have a folder, for example if your application was called MyApp then the folder is MyApp, but then clone your repo into MyApp and name it main (or whatever your root branch is named). The reason I find this best is because when we start using git worktree I want the branches etc. to be next to the main branch, as opposed to mingling with other cloned applications – just makes it simpler to see pretty quickly what worktree’s you have.

If we there assume I have something like c:\repos\myapp\main now cd in the main folder and run

git worktree list

This will show any worktree’s you have and their file location.

Let’s create a new worktree

git worktree add ../ka-hotfix -b hotfix

If you already have a branch named hotfix then you can ignore the -b.

This will have created a new worktree named hotfix in the folder ../ka-hotfix – at this point you’re see if you run git worktree list again that the branch was created and the worktree path is a sibling of our main folder.

If you cd into your branches folder you’ll notice that if you had changes in main, the branch is clean (i.e. no changes). You therefore do not need to stash changes if you’re switching between branches in a single folder (i.e. not using worktree’s).

From either main or you new branch worktree you’ll find git branch list all the branches because it’s using the same .git folder etc.

When we have a branch checked out we cannot check it out again, i.e. the root .git shows it’s already checked out into a different worktree.

We can delete a worktree, but if we run git worktree list again it still shows in the list even though it’s been deleted – it should be displayed as prunable. We can now run

git worktree prune

to remove anything no longer in a worktree.

The thing to remember is that the branch still exists, it’s just the worktree removed.

I’m not going to go into every worktree command but the following is a list of some of the options/subcommands for you the investigate

git worktree add
git worktree list
git worktree move
git worktree remove
git worktree repair
git worktree lock
git worktree unlock

Changing the origin of your local git repo.

Git, being a distributed source control system, allows us to switch the remote/origin for the push or fetch of our local repository. Or to put it another way…

We’ve just moved from one remote git repository to another, how do we update our local code base to “retarget” to the new location?

When you’re using GitHub, GitLab, bitbucket etc. you might come to a point where you migrate from one server to another. Ofcourse you can simply clone your repo. again from the new server OR you can just target you fetch/push origin to the new location like this…

To check your current remote/origin, just run

git remote -v

Now if you wish to change the remote/origin, simply use

git remote set-url remote_name remote_url

Where remote_name might be origin and the remote_url is the new .git URL of your server.

Migrating a folder from one git repo to another

I had a situation where I had a git repo. consisting of a Java project and a C# project (a small monorepo), we decided that permissions for each project needed to differ (i.e. the admin of those projects) and maybe more importantly in a way, changes to one were causing “Pending” changes to the other within CI/CD, in this case TeamCity.

So we need to split the project. Ofcourse it’s easy to create a new project and copy the code, but we wanted to keep the commit history etc.

What I’m going to list below are the steps that worked for me, but I owe a lot to this post Move files from one repository to another, preserving git history.

Use case

To reiterate, we have a Java library and C# library sitting in the same git code base and we want to move the C# library into it’s own repository whilst keeping the commit history.

Steps

  • Clone the repository (i.e. the code we’re wanting to move)
  • CD into it
  • From a command line, run
    git remote rm origin
    

    This will remove the remote url and means we’re not going to accidently commit to the original/source repository.

  • Now we want to filter our anything that’s not part of the code we want to keep. It’s hoped that the C# code, like ours, was in it’s own folder (otherwise things will be much more complicated). So run
    git filter-branch --subdirectory-filter <directory> -- --all
    

    Replace with the relative folder, i.e. subfolder1/subfolder2/FOLDER_TO_KEEP

  • Run the following commands

    git reset --hard
    git gc --aggressive 
    git prune
    git clean -fd
    
  • Now, if you haven’t already create a remote repository, do so and then run
     
    git remote add origin <YOUR REMOTE REPO>
    
  • // this should have been handled by step 6 git remote set-url origin https://youreposerver/yourepo.git

  • git push -u origin --all
    git push origin --tags
    

Using .mailmap

When we create a commit in Git we get the author field listing the author’s name and email address. There are occasions where maybe an author is using different email addresses or names and we’d like to “fix” the commits to show the current or correct information for a commit.

Just add a file named .mailmap into the root folder of your project which contains the name of an author along with any email addresses that are to link to that author, for example

Bruce Dickinson  <bruce@im.com> <bruce@samson.net>

See mailmap for more information.

Git tags using the CLI

Creating tags from the command line…

Tags allows us to store a pointer to the repository at a point in time, these are often used for “tagging” a release, but can be used for other purposes.

Note: I will use the tag name v1.0.2 here, obviously this should be replaced with the tagname you’ve used/assigned.

Listing your tags

To see what tags you currently have on a repository run

git tag

this will list all your tags.

Creating your tags

There’s a couple of ways to create your tags, the first is the annotated tag

git tag -a v1.0.2 -m "Version 1.0.2"

Here we use -a (annotate) to create a new tag and -m (add a message). When creating an annotated tag, a message is expected and so you will be prompted to enter a message if you omit -m. Annotated tags store the message (although you could specify an empty message) along with the author of the tag.

Lightweight tags are another way to tag, these tags store no data with the tag (so do not supply -a or -m), for example

git tag v1.0.2

These tags store the commit checksum and whereas you’d tend to use the annotated tag for releases (for example) the lightweight tag might be used simply to label certain commits.

We can also tag by using the commit checksum (the 6a0b83 in the example below), this example uses a lightweight tag

git tag v1.0.2 6a0b83

Pushing your tags

Ofcourse, if we’re working with others we’ll want to share our tags, so if we have a remote to push to then we can push the tag using

git push origin v1.0.2

A simple git push does not push our tags, we need to be explicit or we can use the –tags switch to push all tags.

git push origin --tags

Viewing your tag

We’ve seen that git tag will list the tags but what about showing us the annotation tag data or what the tag actually refers to, this is where we use

git show v1.0.2

If it’s an annotated tag you see the author of the tag and any message along with the last commit within that tag, for the lightweight we simply see the last commit details.

If you’ve signed your tag (see Signing tags below) you’ll also see the GPG signature attached to the tag.

Deleting your tag

To delete a tag just use the following

git tag -d v1.0.2

If you’ve pushed your tag to a remote then use the following after the local delete

git push origin :refs/tags/v1.0.2

Or use

git push origin --delete v1.0.2

Checking out a tag

We can checkout the tag just like any branch using

git checkout v1.0.2

This will put your repository into a “detached HEAD” state. You cannot commit to this tag, or to put it another way, if you commit to this tag your commits will not appear within the tag, instead they’ll only be reachab;e by the commit hash.

If you intention it to make changes to a tag then instead, you need to branch from the tag, i.e.

git checkout -b v1.0.2-branch v1.0.2

Signing tags

Tags can be signed using GPG. The purpose of this functionality is to simply ensure that the tag was created by the person we expected. I’ve not so far needed to sign a tag (or commits for that matter, which can also be signed). However there may be a time when this is useful, so let’s go through it.

To check if you have a key installed type

gpg --list-keys

If no keys exist it’ll create the folders needed for creating keys.

To generate a key key run

gpg --gen-key

This will ask for your “real name” and “email address”, followed by a pass phrase and then this will create your key. Now running gpg –list-keys should list the newly created key.

Next we need to tell git to use our key using the following

git config --global user.signingkey 123456789

where 123456789 is replaced by the pub string associated with your key (see output from gpg –list-keys).

Now we can sign our tags using

git tag -s v1.0.2 -m "Version 1.0.2"

We replace -a with -s, this is now a signed, annotated tag.

We’ll probably want to verify our tag, and we do this using

git tag -v v1.0.2

If you have the public key installed you’ll see information about the key, if not then you’ll get a verification error.

Svn to git migration

I’ve been moving some repositories from SVN to GIT, so for reference, here’s the basic steps…

Note: These steps to not handle changing the author names etc. For a good explanation of this, checkout Migrate to Git from SVN.

Step are, as follows (these steps also assume you’ve create a repository within GIT for you code to migrate to)

  • git svn clone your_svn_repo
  • cd into your newly created folder
  • git remote add origin your_git_repo
  • git push -u origin master

Renaming a git branch

Occasionally you need to rename a branch you’re working on in git. The following shows how to rename the branch and push commits (assuming the branch had already been pushed) to a remote location

Note: if the branch has not been pushed to a remote location you only need to use the first two commands

  • git checkout <old_branch_name>
  • git branch -m <new_branch_name>
  • git push origin –delete <old_branch_name>
  • git push origin -u <new_branch_name>

gitconfig, the where and how’s

The git configuration files are stored at three different levels.

Local are stored within the cloned repository’s .git folder and the file is named config.

Global is stored in a file with no name and with the extension .gitconfig. It’s stored in your home directory. On Windows this can be confusing especially if the home directory is in a roaming profile. For example, normally we’d find the it in c:\users\your-user-name, however if you have a roaming profile then you’ll need to check

HOME="$HOMEDRIVE$HOMEPATH"

So for example this might end up as H:\

System is stored as gitconfig (filename but no extension). In the case of a Windows OS, this will be in C:\Program Files\Git\mingw64\etc, further configuration data may be found in the config file (filename but no extension) within C:\ProgramData\Git.

Scope

The scope of these files is as follows, local overrides global options and global overrides system.

List the configurations

If you execute the command below, you’ll see a list of all the configuration along with the location of the files used.

git config --list --show-origin

Note: beware if passwords are stored in the configuration then these will be displayed when you run this command.

The command does not show you what’s “actually” being used for configuration from the current repo. so much as “here’s all the configuration values”, so you’ll need to look at the scope of each file to determine what values are currently being used by git.

We can also use the same command along with the switch –local, –system and –global to list the files used along with the configuration used.

Further reading

git-config
git config (Atlassian)

Git aliases

In a previous post on git (Using GIT from the CLI), I listed a fair few commands which are very useful. For the most part they’re fairly simple commands/options, but occasionally they become less easy to remember.

For example to check for merge conflicts on a branch that you may wish to merge master to, you can type

git merge master --no-ff --no-commit

Luckily I have this blog post allowing me to copy and paste this command, an alternate might be to create an alias to this command.

Adding an alias

Creating the alias is simply a way to store a command name within git’s global config which runs another command. Whether we want to simply shorten a name, like using co and ci in place of checkout and commit or shorten a command such as the merge above with check-conflicts.

To create an alias we use the following

git config --global alias.check-conflicts 'merge master --no-ff --no-commit'

In this example we’re telling git to add configuration using –global option (so the configuration is across all repositories or –local for just this repository). We create the alias using the alias option followed by a period/full stop and then the name we want for our alias. This is then followed by the git command and options (within single or double quotes if there are multiple options).

Now we can execute

git check-conflicts

Deleting an Alias

To delete an alias we can either remove it from the config file (for example .gitconfig) or run the following command line

git config --global --unset alias.check-conflicts

Listing Aliases

We can look in the .gitconfig or run the following command line to list the available aliases

git config --get-regexp '^alias\.'

Creating an Alias to list Aliases

To round this up nicely, let’s now create an alias to list aliases, for example

git config --global alias.alias "config --get-regexp ^alias\."

Creating a pre-commit hook for TortoiseGit

Note: This is specific to using TortoiseGit to add your hooks.

I’ve covered Creating a pre-commit hook for TortoiseSvn previously for SVN. We can create similar hooks for GIT also.

Either create a script or other form of executable (this one’s a C# console application). This example is going to do nothing of real interest. It’ll simply stop commits to a repo. but is useful if we place a Debugger.Break before the Console line, for debugging purposes

static int Main(string[] args)
{
   Console.Error.WriteLine("No commits allowed");
   return 1;
}

A non-zero return value indicates a failure and hence this code will effectively stop any commits to the repository it’s applied to.

The arguments sent to the method will be as follows

* 1st arg is the file name of a file which contains a list of files that have changed
* 2nd arg is the file name of a file which has the commit message
* 3rd arg is the folder being committed

Now we compile our application and place it in the .git\hooks\ folder and we need to name it pre-commit.exe.

That’s all there is to it.