Sparse Checkout without command
Tags: git, tutorial
Published: 2023-02-27 19:42:55

Although not well known, there is a way to achieve git sparse-checkout on git versions before 2.26.0.

## Acknowledgments
This, slightly more in-depth article was made possible thanks to the one written by Brian Coyner on [his blog](https://briancoyner.github.io/articles/2013-06-05-git-sparse-checkout/).

## Sparse checkout
Sparse checkout is git's functionality that lets users pull remote repositories partially, saving time and space. It works by changing the working tree from having all files tracked to only those specified. 

## Availability
Nowadays the functionality is available thru it's own command [`git sparse-checkout`](https://git-scm.com/docs/git-sparse-checkout) **since version 2.26.0** and, although experimental, it is the recommended way.  
Unfortunately there are times and situations, especially in large corporations where "newest" version of git are not available.  
Fortunately there are ways of achieving sparse checkout in older git versions.

## Creating sparse checkout manually step by step
1. Create new repository and add remote
   ```bash
   git init
   ```

2. Enable sparse checkout
   ```bash
   git config core.sparsecheckout true
   ```

   This command enables the necessary sparse-checkout [config settings](https://git-scm.com/docs/git-sparse-checkout#_commands)

3. Specify directories
   To specify directories, edit file "sparse-checkout" inside directory `.git/info` by adding directory path per line, like so:
   ```bash
   dir-in-root
   another-dir-in-root/sub-dir
   ```

   The "sparse-checkout" file uses the same syntax as ".gitignore" file, therefor it is possible to specify the unwonted directories with `!`, for example:
   ```bash
   !unwonted-dir-name
   ```

4. Add the remote
   To add remote, run:
   ```bash
   git remote add -f <name>
   ```

   The -f flag will cause git to run `git fetch <name>` after remote information is set.

5. Pull files
   To pull files run:
   ```bash
   git pull <name> <branch>
   ```

   The `<name>` must be the same name as the one provided in point 4.

## Example
The example will use [GitHub's git mirror](https://github.com/git/git) to fetch directory "git/contrib/buildsystems" without it's subfolder "git/contrib/buildsystems/Generators".
```bash
git init
git config core.sparsecheckout true
echo contrib/buildsystems/* >> .git/info/sparse-checkout
echo !Generators >> .git/info/sparse-checkout
git remote add -f origin https://github.com/git/git.git
git pull origin master
```

After running each line separately and running them as a single script, the result of command `ls -R example` was:
```bash
./example:
contrib

./example/contrib:
buildsystems

./example/contrib/buildsystems:
CMakeLists.txt  Generators.pm  engine.pl  generate  parse.pl
```

## Limitations
Git will not download actual files but **will fetch information about the whole repo structure and tags**.  
There are ways of limiting what git will fetch:  
* `--no-tags` can be used to not download tags
* `--depth=<n>` can be used to limit downloaded changes to to last "n" commits.

## Adding or removing directory
Updating directories in "sparse-checkout" file will not automatically update local files. To update, a command `git reset` is required to be run like:
```bash
git reset --hard <remote-name>/<branch>
```

[`git reset`](https://git-scm.com/docs/git-reset) resets the branch to specified state.  
[`--hard`](https://git-scm.com/docs/git-reset#Documentation/git-reset.txt---hard) changes all the files in working tree to match the files in specified state.  

Running:
```bash
echo contrib/emacs/* >> .git/info/sparse-checkout
git reset --hard <remote-name>/<branch>
```

Resulted in:
```bash
example:
contrib

example/contrib:
buildsystems  emacs

example/contrib/buildsystems:
CMakeLists.txt  Generators.pm  engine.pl  generate  parse.pl

example/contrib/emacs:
README  git-blame.el  git.el
```

While manually removing the first line, end leaving "sparse-checkout" like:
```bash
!Generators
contrib/emacs/*
```

And running:
```bash
git reset --hard <remote-name>/<branch>
```

Resulted in:
```bash
example:
contrib

example/contrib:
emacs

example/contrib/emacs:
README  git-blame.el  git.el
```

> Note!  
> I am not aware of any problems `git reset` may cause but it is a functionality I haven't fully tested for a meaningful amount of time.