diff --git a/README.adoc b/README.adoc index a166062..7380a70 100644 --- a/README.adoc +++ b/README.adoc @@ -16,7 +16,7 @@ You can also list local repositories (+ghq list+). == SYNOPSIS [verse] -ghq get [-u] [-p] [--shallow] [--vcs ] [--look] [--silent] [--branch] [--no-recursive] [--bare] |//|/| +ghq get [-u] [-p] [--shallow] [--vcs ] [--look] [--silent] [--branch] [--no-recursive] [--bare] [--partial blobless|treeless] |//|/| ghq list [-p] [-e] [] ghq create [--vcs ] |//|/| ghq rm [--dry-run] |//|/| @@ -44,7 +44,10 @@ get:: The 'ghq' gets the git repository recursively by default. + We can prevent it with '--no-recursive' option. With '--bare' option, a "bare clone" will be performed (for Git - repositories only, 'git clone --bare ...' eg.). + repositories only, 'git clone --bare ...' eg.). + + With '--partial' option, a "partial clone" will be performed (for Git + repositories only, in 'blobless' mode, 'git clone --filter=blob:none ...', + in 'treeless' mode, 'git clone --filter=tree:0 ...' eg.). list:: List locally cloned repositories. If a query argument is given, only diff --git a/cmd_get.go b/cmd_get.go index bd3f6fd..4e44f88 100644 --- a/cmd_get.go +++ b/cmd_get.go @@ -35,6 +35,7 @@ func doGet(c *cli.Context) error { branch: c.String("branch"), recursive: !c.Bool("no-recursive"), bare: c.Bool("bare"), + partial: c.String("partial"), } if parallel { // force silent in parallel import diff --git a/cmd_get_test.go b/cmd_get_test.go index 4382d07..78111b2 100644 --- a/cmd_get_test.go +++ b/cmd_get_test.go @@ -248,6 +248,52 @@ func TestCommandGet(t *testing.T) { t.Errorf("silent mode should not output any logs, but got: %s", out) } }, + }, { + name: "[partial] blobless", + scenario: func(t *testing.T, tmpRoot string, cloneArgs *_cloneArgs, updateArgs *_updateArgs) { + localDir := filepath.Join(tmpRoot, "github.com", "motemen", "ghq-test-repo") + + app.Run([]string{"", "get", "--partial", "blobless", "motemen/ghq-test-repo"}) + + expect := "https://github.com/motemen/ghq-test-repo" + if cloneArgs.remote.String() != expect { + t.Errorf("got: %s, expect: %s", cloneArgs.remote, expect) + } + if filepath.ToSlash(cloneArgs.local) != filepath.ToSlash(localDir) { + t.Errorf("got: %s, expect: %s", filepath.ToSlash(cloneArgs.local), filepath.ToSlash(localDir)) + } + if cloneArgs.partial != "blobless" { + t.Errorf("cloneArgs.partial should be \"blobless\"") + } + }, + }, { + name: "[partial] treeless", + scenario: func(t *testing.T, tmpRoot string, cloneArgs *_cloneArgs, updateArgs *_updateArgs) { + localDir := filepath.Join(tmpRoot, "github.com", "motemen", "ghq-test-repo") + + app.Run([]string{"", "get", "--partial", "treeless", "motemen/ghq-test-repo"}) + + expect := "https://github.com/motemen/ghq-test-repo" + if cloneArgs.remote.String() != expect { + t.Errorf("got: %s, expect: %s", cloneArgs.remote, expect) + } + if filepath.ToSlash(cloneArgs.local) != filepath.ToSlash(localDir) { + t.Errorf("got: %s, expect: %s", filepath.ToSlash(cloneArgs.local), filepath.ToSlash(localDir)) + } + if cloneArgs.partial != "treeless" { + t.Errorf("cloneArgs.partial should be \"treeless\"") + } + }, + }, { + name: "[partial] unacceptable value", + scenario: func(t *testing.T, tmpRoot string, cloneArgs *_cloneArgs, updateArgs *_updateArgs) { + err := app.Run([]string{"", "get", "--partial", "unacceptable", "motemen/ghq-test-repo"}) + + expect := "flag partial value \"unacceptable\" is not allowed" + if err.Error() != expect { + t.Errorf("got: %s, expect: %s", err.Error(), expect) + } + }, }} for _, tc := range testCases { diff --git a/commands.go b/commands.go index 119a48b..ba5f58a 100644 --- a/commands.go +++ b/commands.go @@ -2,6 +2,7 @@ package main import ( "fmt" + "slices" "strings" "github.com/urfave/cli/v2" @@ -38,6 +39,16 @@ var commandGet = &cli.Command{ Usage: "Specify `branch` name. This flag implies --single-branch on Git"}, &cli.BoolFlag{Name: "parallel", Aliases: []string{"P"}, Usage: "Import parallelly"}, &cli.BoolFlag{Name: "bare", Usage: "Do a bare clone"}, + &cli.StringFlag{ + Name: "partial", + Usage: "Do a partial clone. Can specify either \"blobless\" or \"treeless\"", + Action: func(ctx *cli.Context, v string) error { + expected := []string{"blobless", "treeless"} + if !slices.Contains(expected, v) { + return fmt.Errorf("flag partial value \"%v\" is not allowed", v) + } + return nil + }}, }, } @@ -94,7 +105,7 @@ type commandDoc struct { } var commandDocs = map[string]commandDoc{ - "get": {"", "[-u] [-p] [--shallow] [--vcs ] [--look] [--silent] [--branch ] [--no-recursive] [--bare] ||/|//"}, + "get": {"", "[-u] [-p] [--shallow] [--vcs ] [--look] [--silent] [--branch ] [--no-recursive] [--bare] [--partial blobless|treeless] ||/|//"}, "list": {"", "[-p] [-e] []"}, "create": {"", "|/|//"}, "rm": {"", "|/|//"}, diff --git a/commands_test.go b/commands_test.go index 7c4315f..062a7ab 100644 --- a/commands_test.go +++ b/commands_test.go @@ -15,6 +15,7 @@ type _cloneArgs struct { recursive bool bare bool silent bool + partial string } type _updateArgs struct { @@ -41,6 +42,7 @@ func withFakeGitBackend(t *testing.T, block func(*testing.T, string, *_cloneArgs recursive: vg.recursive, bare: vg.bare, silent: vg.silent, + partial: vg.partial, } return nil }, diff --git a/getter.go b/getter.go index 2777fec..d6eb24f 100644 --- a/getter.go +++ b/getter.go @@ -25,7 +25,7 @@ type getInfo struct { type getter struct { update, shallow, silent, ssh, recursive, bare bool - vcs, branch string + vcs, branch, partial string } func (g *getter) get(argURL string) (getInfo, error) { @@ -113,6 +113,7 @@ func (g *getter) getRemoteRepository(remote RemoteRepository, branch string) (ge branch: branch, recursive: g.recursive, bare: g.bare, + partial: g.partial, }) } return info, nil diff --git a/misc/author/integration_test.sh b/misc/author/integration_test.sh index baf3084..0e17596 100755 --- a/misc/author/integration_test.sh +++ b/misc/author/integration_test.sh @@ -24,6 +24,8 @@ export GHQ_ROOT=$tmpdir ghq get https://svn.apache.org/repos/asf/subversion ghq get --shallow hub.darcs.net/byorgey/split ghq get --bare x-motemen/gore + ghq get --partial blobless x-motemen/blogsync + ghq get --partial treeless x-motemen/gobump test -d $tmpdir/github.com/x-motemen/ghq/.git test -d $tmpdir/www.mercurial-scm.org/repo/hello/.hg @@ -34,11 +36,15 @@ export GHQ_ROOT=$tmpdir test -d $tmpdir/svn.apache.org/repos/asf/subversion/.svn test -d $tmpdir/hub.darcs.net/byorgey/split/_darcs test -d $tmpdir/github.com/x-motemen/gore.git/refs + grep --quiet "partialclonefilter = blob:none" $tmpdir/github.com/x-motemen/blogsync/.git/config + grep --quiet "partialclonefilter = tree:0" $tmpdir/github.com/x-motemen/gobump/.git/config : testing 'ghq list' cat < $tmpdir/expect chiselapp.com/user/sti/repository/fossil-gui +github.com/x-motemen/blogsync github.com/x-motemen/ghq +github.com/x-motemen/gobump github.com/x-motemen/gore.git www.mercurial-scm.org/repo/hello launchpad.net/shutter diff --git a/misc/fish/ghq.fish b/misc/fish/ghq.fish index 172f131..6d82c29 100644 --- a/misc/fish/ghq.fish +++ b/misc/fish/ghq.fish @@ -36,6 +36,11 @@ complete -c ghq -n '__fish_seen_subcommand_from get' -l no-recursive -d 'Prevent complete -c ghq -n '__fish_seen_subcommand_from get' -s b -l branch -d 'Specify branch name. This flag implies --single-branch on Git' complete -c ghq -n '__fish_seen_subcommand_from get' -s P -l parallel -d 'Import parallelly' complete -c ghq -n '__fish_seen_subcommand_from get' -l bare -d 'Do a bare clone' +function __complete_get_partial + printf '%s\t%s\n' 'blobless' 'Do a blobless clone' + printf '%s\t%s\n' 'treeless' 'Do a treeless clone' +end +complete -c ghq -n '__fish_seen_subcommand_from get' -l partial -d 'Do a partial clone' -xa '(__complete_get_partial)' complete -c ghq -n '__fish_seen_subcommand_from list' -s e -l exact -d 'Perform an exact match' complete -c ghq -n '__fish_seen_subcommand_from list' -l vcs -d 'Specify vcs backend for matching' diff --git a/misc/zsh/_ghq b/misc/zsh/_ghq index e75b65f..34dab44 100644 --- a/misc/zsh/_ghq +++ b/misc/zsh/_ghq @@ -27,6 +27,7 @@ function _ghq () { '--bare[Do a bare clone]' \ '(-b --branch)'{-b,--branch}'[Specify branch name]' \ '(-P --parallel)'{-P,--parallel}'[Import parallelly]' \ + '--partial[Do a partial clone]: :(blobless treeless)' \ '(-)*:: :->null_state' \ && ret=0 ;; diff --git a/vcs.go b/vcs.go index c075c92..dd2a1a2 100644 --- a/vcs.go +++ b/vcs.go @@ -44,7 +44,7 @@ type vcsGetOption struct { url *url.URL dir string recursive, shallow, silent, bare bool - branch string + branch, partial string } // GitBackend is the VCSBackend of git @@ -70,6 +70,11 @@ var GitBackend = &VCSBackend{ if vg.bare { args = append(args, "--bare") } + if vg.partial == "blobless" { + args = append(args, "--filter=blob:none") + } else if vg.partial == "treeless" { + args = append(args, "--filter=tree:0") + } args = append(args, vg.url.String(), vg.dir) return run(vg.silent)("git", args...) diff --git a/vcs_test.go b/vcs_test.go index 72e1c1d..00080dc 100644 --- a/vcs_test.go +++ b/vcs_test.go @@ -136,6 +136,26 @@ func TestVCSBackend(t *testing.T) { }) }, expect: []string{"git", "clone", "--bare", remoteDummyURL.String(), localDir}, + }, { + name: "[git] (partial) blobless clone", + f: func() error { + return GitBackend.Clone(&vcsGetOption{ + url: remoteDummyURL, + dir: localDir, + partial: "blobless", + }) + }, + expect: []string{"git", "clone", "--filter=blob:none", remoteDummyURL.String(), localDir}, + }, { + name: "[git] (partial) treeless clone", + f: func() error { + return GitBackend.Clone(&vcsGetOption{ + url: remoteDummyURL, + dir: localDir, + partial: "treeless", + }) + }, + expect: []string{"git", "clone", "--filter=tree:0", remoteDummyURL.String(), localDir}, }, { name: "[git] switch git-svn on update", f: func() error {