4

Is there a way to download only a given subdirectory from a git repository? Say I want to fetch only the nyancat directory from https://github.com/djdeath/toys.git .

I could either clone the whole repository and ignore the files I don't want, or go to https://github.com/djdeath/toys/tree/master/nyancat and download the relevant files one by one. I think there must be an easier way.

Note I'm not asking if it is possible to clone the directory, this was asked before and is apparently not possible. I'm just interested in quickly getting the files, and don't need to commit back, or use git on them again.

Community
  • 1
  • 1
jdm
  • 9,470
  • 12
  • 58
  • 110

3 Answers3

2

The git-archive command will do pretty much what you want, but it needs to operate on an already cloned repo, so if you've got SSH access to the host, you can do:

cd /path/to/destination
ssh user@host "cd /path/to/repo && git archive HEAD dir/you/want" | tar xf -

Or, with compression for the network transport:

cd /path/to/destination
ssh user@host "cd /path/to/repo && git archive HEAD dir/you/want | gzip" | tar xzf -
Alex Howansky
  • 50,515
  • 8
  • 78
  • 98
1

A full paramateric bash function (no-dependancy)

#!/bin/bash

gitsub() {

  usage() {
        cat <<- EOF
        ------------------------------------------------------------------------
        GNU gitsub 0.0.1, a non-interactive github filtered subrepo retriever.
        Usage: gitsub [-h] [[-d[dir] -s[strip] -e[ext]] -o owner -r repo -b sub]
        ------------------------------------------------------------------------
        Mandatory arguments to long options are mandatory for short options too.
        ------------------------------------------------------------------------
        MANDATORY:
          -o, --owner           repo's owner
          -r, --repo            repo's name
          -b, --subrepo         directory(s) to be cloned
        OPTIONS:
          -s, --strip           number of dirs (/) to be skipped, default 0
          -d, --dir             output directory default current directory
          -e, --extension       filter by ext, if missing clone all (including subdirs)
        COMMANDS:
          -h, --help            display this help and exit
        ------------------------------------------------------------------------
        Mail bug reports and suggestions to makramjandar@gmail.com
        ------------------------------------------------------------------------
        EOF
  }

  error() { echo -e "\033[1;31mError: $1\033[0m" ;}

  # check supplied args
  is_arg() { 
    [[ -n "$2" && ${2:0:1} != "-" ]] \
    || { error "Argument for $1 is missing..." >&2 \
    && usage \
    && exit 1 ;}
  }

  POSITIONAL=()
  while (( "$#" )); do
    case "$1" in
      # commands
      -h|--help)      usage && exit 0                                    ;;
      # mandatory flags with arguments
      -o|--owner)     is_arg $1 $2 && OWNER=$2                 ; shift 2 ;;
      -r|--repo)      is_arg $1 $2 && REPO=$2                  ; shift 2 ;;
      -b|--subrepo)   is_arg $1 $2 && SUBREPO=$2               ; shift 2 ;;
      # optional flags with arguments
      -d|--dir)       is_arg $1 $2 && DIR=$2 && mkdir -p $DIR  ; shift 2 ;;
      -s|--strip)     is_arg $1 $2 && STRIP=$2                 ; shift 2 ;;
      -e|--extension) is_arg $1 $2 && EXTENSION=$2             ; shift 2 ;;
      # unsupported flags 
      -*|--*=) error "Unsupported flag $1" >&2 && usage        ; exit 1  ;;
      # preserve positional arguments
      *) POSITIONAL+=("$1")                                    ; shift   ;;
    esac
  done
  # set positional arguments in their proper place
  set -- "${POSITIONAL[@]}"

  # check mandatory arguments
  [[ -z "$OWNER" || -z "$REPO" || -z "$SUBREPO" ]] \
  && { error "Missing mandatory arguments..." >&2 \
  && usage \
  && exit 1 ;}

  # get github filtered (optional) subrepository
  {
    curl -L "https://api.github.com/repos/$OWNER/$REPO/tarball" \
    | 
      tar \
      --verbose \
      --extract \
      --gzip \
      --directory=${DIR:-$PWD} \
      --strip=${STRIP:-0} \
      --wildcards */${SUBREPO}/*.${EXTENSION}*
  } 2>/dev/null \
    && echo "Done" \
    || \
  { 
    error "Invalid args..." \
    && usage \
    && exit 1
  }
}

gitsub "$@"

For a given repo: https://github.com/jenskutilek/free-fonts

To download the entire contents of the sub-folder Fira including dirs and files

$ bash gitsub.sh -o "jenskutilek" -r "free-fonts" -b "Fira" -d "FiraUnfiltered" -s 2

$ tree -d FiraUnfiltered/
FiraUnfiltered/
├── Fira Mono
│   ├── OTF
│   ├── TTF
│   ├── VFB
│   └── WOFF
└── Fira Sans
    ├── OTF
    ├── TTF
    ├── VFB
    └── WOFF

To downlod the same subfolder but filtered with the font TTF

$ bash gitsub.sh -o "jenskutilek" -r "free-fonts" -b "Fira" -d "FiraFiltered" -s 2 -e "ttf"

$ tree -d FiraFiltered/
FiraFiltered/
├── Fira Mono
│   └── TTF
└── Fira Sans
    └── TTF

downlod only the filtered files to the outdir by setting -s|--strip to 4

bash gitsub.sh -o "jenskutilek" -r "free-fonts" -b "Fira" -d "ttfFilesOnly" -s 4 -e "ttf"

$ tree ttfFilesOnly/
ttfFilesOnly/
├── FiraMono-Bold.ttf
├── FiraMono-Regular.ttf
├── FiraSans-Bold.ttf
├── FiraSans-BoldItalic.ttf
├── FiraSans-Light.ttf
├── FiraSans-LightItalic.ttf
├── FiraSans-Medium.ttf
├── FiraSans-MediumItalic.ttf
├── FiraSans-Regular.ttf
└── FiraSans-RegularItalic.ttf
AIe
  • 11
  • 4
  • You should explain your script a bit. – U. Windl Dec 19 '21 at 16:39
  • Your answer could be improved with additional supporting information. Please [edit] to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Dec 20 '21 at 06:33
  • My function is provided with an embeded help... shall I print it or must I rewrite a second help for the help itself !! A bit of consistency, please Sir – AIe Dec 20 '21 at 12:00
  • @Ale I had mean to edit the answer, not to add a comment adding details for the answer. – U. Windl Dec 21 '21 at 10:59
0

Hey I just wrote a script

usage

      python get_git_sub_dir.py path/to/sub/dir <RECURSIVE>
david_adler
  • 9,690
  • 6
  • 57
  • 97