Skip to content
mnaoumov.dev
Go back

git checkout on bare repo

Hi folks

In this blogpost I tell you how to checkout specific files and folders from a bare repo. If you cannot imagine a scenario when it may be required see my next blogpost :)

For non-bare repos, task is simple

git checkout mybranch folder/file.txt

it gives us a way to get file even if we are on a different branch

But for bare repos, it is more difficult

Let’s try the same command from bare repo, and get the following result

fatal: This operation must be run in a work tree

After googling the issue we can find several approaches

Cloning

git clone . __temp
Push-Location -Path __temp
git checkout origin/mybranch folder/file.txt
Copy-Item -Path folder\file.txt -Destination path\to\destination
Pop-Location
Delete-Item -Path __temp -Recurse -Force

This approach is very inefficient, you have to clone the whole repo, and this can take time and space.

Git Show or Git Cat-file

git show master:folder/file.txt > path\to\destination\file.txt

or

git cat-file blob master:folder/file.txt > path\to\destination\file.txt

These approaches would work fine only for text files

And have many problems

git show master:folder/file.txt | Out-File -FilePath path\to\destination\file.txt -Encoding Ascii


    This is lame. We had to know desired encoding before hand.

- **Line endings**. Result file always has CRLF ending, no matter what ending file actually has.
- **Binary files**. Binary file is corrupted.
- **Folders**. There is no way to extract whole folder with all files inside.

So, none of these approaches worked.

And then I found one which works perfectly

## git archive

It is probably a dirty hack, but it works

```powershell
git archive mybranch folder/file.txt --output result.tar

It creates a tar archive with desired content, exactly the file that sits in the source control.

It works fine with folders as well.

So we can write a helper

function Git-CheckoutFile
{
    param
    (
        [string] $Branch,
        [string] $RelativeFilePath,
        [string] $DestinationFolder
    )

    if ($RelativeFilePath -eq ".")
    {
        $RelativeFilePath = "*"
    }

    $RelativeFilePath = $RelativeFilePath -replace "\\", "/"

    $tempDir = [Guid]::NewGuid()
    New-Item -Path $tempDir -ItemType Directory | Out-Null

    git archive $Branch $RelativeFilePath --output "$tempDir\__temp.tar"
    tar -xf "$tempDir/__temp.tar" -C $tempDir
    Remove-Item "$tempDir/__temp.tar"

    if (-not (Test-Path $DestinationFolder))
    {
        New-Item -Path $DestinationFolder -ItemType Directory | Out-Null
    }

    Copy-Item -Path "$tempDir/$RelativeFilePath" -Destination $DestinationFolder -Recurse

    Remove-Item -Path $tempDir -Recurse -Force
}

This works as a charm

Git-CheckoutFile -Branch mybranch -RelativeFilePath folder\file.txt -DestinationFolder path\to\destination

Explanation of how it works is pretty simple

We create a tar archive with required files in a temp folder. Then we unpack it. Then we copy desired files into destination folder.

We cannot unpack straight into destination folder, because archive preserved repo’s tree structure, which we don’t need.

Line #10-13 wrote to support checkout of whole branch using syntax

Git-CheckoutFile -Branch mybranch -RelativeFilePath . -DestinationFolder path\to\destination

We need to change . (dot) into * (asterisk) because otherwise line #29 will produce an incorrect result.

Hope you’ve been as excited as I was :)

P.S. According to stackoverflow I should be able to combine lines #20-21 and extract tar file on a fly without creating it.

git archive $Branch $RelativeFilePath | tar -xf -

But this did not work for me

"C:/Program Files (x86)/Git/bin/tar.exe": Malformed extended header: missing newline
"C:/Program Files (x86)/Git/bin/tar.exe": Substituting `.' for empty member name
"C:/Program Files (x86)/Git/bin/tar.exe": .: Cannot open: File exists
"C:/Program Files (x86)/Git/bin/tar.exe": Substituting `.' for empty member name
"C:/Program Files (x86)/Git/bin/tar.exe": .: Cannot open: File exists
"C:/Program Files (x86)/Git/bin/tar.exe": Exiting with failure status due to previous errors

I suspect that this is because of the Windows line endings


Share this post on:

Previous Post
git undo
Next Post
git server-side hooks - maintenance