From db6d6a5ebab9c456c5c5026e19eb9f537f8ac75d Mon Sep 17 00:00:00 2001 From: Thomas Miceli <27960254+thomiceli@users.noreply.github.com> Date: Tue, 30 Jan 2024 00:07:57 +0100 Subject: [PATCH] Set gist visibility via Git push options (#215) --- docs/usage/git-push-options.md | 13 ++ internal/cli/hook.go | 18 +++ internal/cli/main.go | 40 +++-- internal/config/config.go | 20 +-- internal/db/gist.go | 6 +- internal/git/commands.go | 3 +- internal/git/commands_test.go | 150 +++--------------- internal/git/test_funcs.go | 71 +++++++++ internal/hooks/hook.go | 24 +++ internal/hooks/post-receive.go | 43 ----- internal/hooks/post_receive.go | 77 +++++++++ .../hooks/{pre-receive.go => pre_receive.go} | 2 - internal/hooks/pre_receive_test.go | 54 +++++++ internal/web/git_http.go | 16 +- internal/web/test/server.go | 2 +- 15 files changed, 326 insertions(+), 213 deletions(-) create mode 100644 docs/usage/git-push-options.md create mode 100644 internal/git/test_funcs.go create mode 100644 internal/hooks/hook.go delete mode 100644 internal/hooks/post-receive.go create mode 100644 internal/hooks/post_receive.go rename internal/hooks/{pre-receive.go => pre_receive.go} (96%) create mode 100644 internal/hooks/pre_receive_test.go diff --git a/docs/usage/git-push-options.md b/docs/usage/git-push-options.md new file mode 100644 index 0000000..3bc17fc --- /dev/null +++ b/docs/usage/git-push-options.md @@ -0,0 +1,13 @@ +# Push Options + +Opengist has support for a few [Git push options](https://git-scm.com/docs/git-push#Documentation/git-push.txt--oltoptiongt). + +These options are passed to `git push` command and can be used to change the metadata of a gist. + +## Change visibility + +```shell +git push -o visibility=public +git push -o visibility=unlisted +git push -o visibility=private +``` diff --git a/internal/cli/hook.go b/internal/cli/hook.go index a05ff36..94b3f9b 100644 --- a/internal/cli/hook.go +++ b/internal/cli/hook.go @@ -1,9 +1,14 @@ package cli import ( + "github.com/rs/zerolog/log" + "github.com/thomiceli/opengist/internal/config" + "github.com/thomiceli/opengist/internal/db" "github.com/thomiceli/opengist/internal/hooks" "github.com/urfave/cli/v2" + "io" "os" + "path/filepath" ) var CmdHook = cli.Command{ @@ -19,6 +24,7 @@ var CmdHookPreReceive = cli.Command{ Name: "pre-receive", Usage: "Run Git server pre-receive hook for a repository", Action: func(ctx *cli.Context) error { + initialize(ctx) if err := hooks.PreReceive(os.Stdin, os.Stdout, os.Stderr); err != nil { os.Exit(1) } @@ -30,9 +36,21 @@ var CmdHookPostReceive = cli.Command{ Name: "post-receive", Usage: "Run Git server post-receive hook for a repository", Action: func(ctx *cli.Context) error { + initialize(ctx) if err := hooks.PostReceive(os.Stdin, os.Stdout, os.Stderr); err != nil { os.Exit(1) } return nil }, } + +func initialize(ctx *cli.Context) { + if err := config.InitConfig(ctx.String("config"), io.Discard); err != nil { + panic(err) + } + config.InitLog() + + if err := db.Setup(filepath.Join(config.GetHomeDir(), config.C.DBFilename), false); err != nil { + log.Fatal().Err(err).Msg("Failed to initialize database in hooks") + } +} diff --git a/internal/cli/main.go b/internal/cli/main.go index 702211d..3d8341c 100644 --- a/internal/cli/main.go +++ b/internal/cli/main.go @@ -59,7 +59,7 @@ func App() error { func Initialize(ctx *cli.Context) { fmt.Println("Opengist v" + config.OpengistVersion) - if err := config.InitConfig(ctx.String("config")); err != nil { + if err := config.InitConfig(ctx.String("config"), os.Stdout); err != nil { panic(err) } if err := os.MkdirAll(filepath.Join(config.GetHomeDir()), 0755); err != nil { @@ -83,8 +83,8 @@ func Initialize(ctx *cli.Context) { homePath := config.GetHomeDir() log.Info().Msg("Data directory: " + homePath) - if err := createSymlink(); err != nil { - log.Fatal().Err(err).Send() + if err := createSymlink(homePath, ctx.String("config")); err != nil { + log.Fatal().Err(err).Msg("Failed to create symlinks") } if err := os.MkdirAll(filepath.Join(homePath, "repos"), 0755); err != nil { @@ -113,19 +113,41 @@ func Initialize(ctx *cli.Context) { } } -func createSymlink() error { +func createSymlink(homePath string, configPath string) error { + if err := os.MkdirAll(filepath.Join(homePath, "symlinks"), 0755); err != nil { + return err + } + exePath, err := os.Executable() if err != nil { return err } - symlinkPath := path.Join(config.GetHomeDir(), "opengist-bin") - - if _, err := os.Lstat(symlinkPath); err == nil { - if err := os.Remove(symlinkPath); err != nil { + symlinkExePath := path.Join(config.GetHomeDir(), "symlinks", "opengist") + if _, err := os.Lstat(symlinkExePath); err == nil { + if err := os.Remove(symlinkExePath); err != nil { return err } } + if err = os.Symlink(exePath, symlinkExePath); err != nil { + return err + } - return os.Symlink(exePath, symlinkPath) + if configPath == "" { + return nil + } + + configPath, _ = filepath.Abs(configPath) + configPath = filepath.Clean(configPath) + symlinkConfigPath := path.Join(config.GetHomeDir(), "symlinks", "config.yml") + if _, err := os.Lstat(symlinkConfigPath); err == nil { + if err := os.Remove(symlinkConfigPath); err != nil { + return err + } + } + if err = os.Symlink(configPath, symlinkConfigPath); err != nil { + return err + } + + return nil } diff --git a/internal/config/config.go b/internal/config/config.go index 5f3e77f..b755c3d 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -91,18 +91,18 @@ func configWithDefaults() (*config, error) { return c, nil } -func InitConfig(configPath string) error { +func InitConfig(configPath string, out io.Writer) error { // Default values c, err := configWithDefaults() if err != nil { return err } - if err = loadConfigFromYaml(c, configPath); err != nil { + if err = loadConfigFromYaml(c, configPath, out); err != nil { return err } - if err = loadConfigFromEnv(c); err != nil { + if err = loadConfigFromEnv(c, out); err != nil { return err } @@ -202,7 +202,7 @@ func GetHomeDir() string { return filepath.Clean(absolutePath) } -func loadConfigFromYaml(c *config, configPath string) error { +func loadConfigFromYaml(c *config, configPath string, out io.Writer) error { if configPath != "" { absolutePath, _ := filepath.Abs(configPath) absolutePath = filepath.Clean(absolutePath) @@ -211,9 +211,9 @@ func loadConfigFromYaml(c *config, configPath string) error { if !os.IsNotExist(err) { return err } - fmt.Println("No YAML config file found at " + absolutePath) + _, _ = fmt.Fprintln(out, "No YAML config file found at "+absolutePath) } else { - fmt.Println("Using YAML config file: " + absolutePath) + _, _ = fmt.Fprintln(out, "Using YAML config file: "+absolutePath) // Override default values with values from config.yml d := yaml.NewDecoder(file) @@ -223,13 +223,13 @@ func loadConfigFromYaml(c *config, configPath string) error { defer file.Close() } } else { - fmt.Println("No YAML config file specified.") + _, _ = fmt.Fprintln(out, "No YAML config file specified.") } return nil } -func loadConfigFromEnv(c *config) error { +func loadConfigFromEnv(c *config, out io.Writer) error { v := reflect.ValueOf(c).Elem() var envVars []string @@ -260,9 +260,9 @@ func loadConfigFromEnv(c *config) error { } if len(envVars) > 0 { - fmt.Println("Using environment variables config: " + strings.Join(envVars, ", ")) + _, _ = fmt.Fprintln(out, "Using environment variables config: "+strings.Join(envVars, ", ")) } else { - fmt.Println("No environment variables config specified.") + _, _ = fmt.Fprintln(out, "No environment variables config specified.") } return nil diff --git a/internal/db/gist.go b/internal/db/gist.go index f975926..b9e6675 100644 --- a/internal/db/gist.go +++ b/internal/db/gist.go @@ -37,11 +37,11 @@ func (v Visibility) Next() Visibility { func ParseVisibility[T string | int](v T) (Visibility, error) { switch s := fmt.Sprint(v); s { - case "0": + case "0", "public": return PublicVisibility, nil - case "1": + case "1", "unlisted": return UnlistedVisibility, nil - case "2": + case "2", "private": return PrivateVisibility, nil default: return -1, fmt.Errorf("unknown visibility %q", s) diff --git a/internal/git/commands.go b/internal/git/commands.go index 294d500..6850d8f 100644 --- a/internal/git/commands.go +++ b/internal/git/commands.go @@ -24,7 +24,6 @@ var ( ) const truncateLimit = 2 << 18 -const BaseHash = "0000000000000000000000000000000000000000" type RevisionNotFoundError struct{} @@ -565,5 +564,5 @@ func removeFilesExceptGit(dir string) error { } const hookTemplate = `#!/bin/sh -"$OG_OPENGIST_HOME_INTERNAL/opengist-bin" hook %s +"$OG_OPENGIST_HOME_INTERNAL/symlinks/opengist" --config=$OG_OPENGIST_HOME_INTERNAL/symlinks/config.yml hook %s ` diff --git a/internal/git/commands_test.go b/internal/git/commands_test.go index 32e3723..efa761d 100644 --- a/internal/git/commands_test.go +++ b/internal/git/commands_test.go @@ -1,44 +1,18 @@ package git import ( - "bytes" - "fmt" "github.com/stretchr/testify/require" "github.com/thomiceli/opengist/internal/config" - "github.com/thomiceli/opengist/internal/hooks" "os" "os/exec" "path" - "path/filepath" "strings" "testing" ) -func setup(t *testing.T) { - _ = os.Setenv("OPENGIST_SKIP_GIT_HOOKS", "1") - - err := config.InitConfig("") - require.NoError(t, err, "Could not init config") - - err = os.MkdirAll(path.Join(config.GetHomeDir(), "tests"), 0755) - ReposDirectory = path.Join("tests") - require.NoError(t, err) - - err = os.MkdirAll(filepath.Join(config.GetHomeDir(), "tmp", "repos"), 0755) - require.NoError(t, err) - - err = InitRepository("thomas", "gist1") - require.NoError(t, err) -} - -func teardown(t *testing.T) { - err := os.RemoveAll(path.Join(config.GetHomeDir(), "tests")) - require.NoError(t, err, "Could not remove repos directory") -} - func TestInitDeleteRepository(t *testing.T) { - setup(t) - defer teardown(t) + SetupTest(t) + defer TeardownTest(t) cmd := exec.Command("git", "rev-parse", "--is-bare-repository") cmd.Dir = RepositoryPath("thomas", "gist1") @@ -55,14 +29,14 @@ func TestInitDeleteRepository(t *testing.T) { } func TestCommits(t *testing.T) { - setup(t) - defer teardown(t) + SetupTest(t) + defer TeardownTest(t) hasNoCommits, err := HasNoCommits("thomas", "gist1") require.NoError(t, err, "Could not check if repository has no commits") require.True(t, hasNoCommits, "Repository should have no commits") - commitToBare(t, "thomas", "gist1", nil) + CommitToBare(t, "thomas", "gist1", nil) hasNoCommits, err = HasNoCommits("thomas", "gist1") require.NoError(t, err, "Could not check if repository has no commits") @@ -72,17 +46,17 @@ func TestCommits(t *testing.T) { require.NoError(t, err, "Could not count commits") require.Equal(t, "1", nbCommits, "Repository should have 1 commit") - commitToBare(t, "thomas", "gist1", nil) + CommitToBare(t, "thomas", "gist1", nil) nbCommits, err = CountCommits("thomas", "gist1") require.NoError(t, err, "Could not count commits") require.Equal(t, "2", nbCommits, "Repository should have 2 commits") } func TestContent(t *testing.T) { - setup(t) - defer teardown(t) + SetupTest(t) + defer TeardownTest(t) - commitToBare(t, "thomas", "gist1", map[string]string{ + CommitToBare(t, "thomas", "gist1", map[string]string{ "my_file.txt": "I love Opengist\n", "my_other_file.txt": `I really hate Opengist`, @@ -103,7 +77,7 @@ hate Opengist`, require.False(t, truncated, "Content should not be truncated") require.Equal(t, "I really\nhate Opengist", content, "Content is not correct") - commitToBare(t, "thomas", "gist1", map[string]string{ + CommitToBare(t, "thomas", "gist1", map[string]string{ "my_renamed_file.txt": "I love Opengist\n", "my_other_file.txt": `I really like Opengist actually`, @@ -182,18 +156,18 @@ like Opengist actually`, } func TestGitGc(t *testing.T) { - setup(t) - defer teardown(t) + SetupTest(t) + defer TeardownTest(t) err := GcRepos() require.NoError(t, err, "Could not run git gc") } func TestFork(t *testing.T) { - setup(t) - defer teardown(t) + SetupTest(t) + defer TeardownTest(t) - commitToBare(t, "thomas", "gist1", map[string]string{ + CommitToBare(t, "thomas", "gist1", map[string]string{ "my_file.txt": "I love Opengist\n", }) @@ -210,10 +184,10 @@ func TestFork(t *testing.T) { } func TestTruncate(t *testing.T) { - setup(t) - defer teardown(t) + SetupTest(t) + defer TeardownTest(t) - commitToBare(t, "thomas", "gist1", map[string]string{ + CommitToBare(t, "thomas", "gist1", map[string]string{ "my_file.txt": "A", }) @@ -227,7 +201,7 @@ func TestTruncate(t *testing.T) { builder.WriteString("A") } str := builder.String() - commitToBare(t, "thomas", "gist1", map[string]string{ + CommitToBare(t, "thomas", "gist1", map[string]string{ "my_file.txt": str, }) @@ -236,7 +210,7 @@ func TestTruncate(t *testing.T) { require.True(t, truncated, "Content should be truncated") require.Equal(t, truncateLimit, len(content), "Content size should be at truncate limit") - commitToBare(t, "thomas", "gist1", map[string]string{ + CommitToBare(t, "thomas", "gist1", map[string]string{ "my_file.txt": "AA\n" + str, }) @@ -247,8 +221,8 @@ func TestTruncate(t *testing.T) { } func TestGitInitBranchNames(t *testing.T) { - setup(t) - defer teardown(t) + SetupTest(t) + defer TeardownTest(t) cmd := exec.Command("git", "symbolic-ref", "HEAD") cmd.Dir = RepositoryPath("thomas", "gist1") @@ -266,83 +240,3 @@ func TestGitInitBranchNames(t *testing.T) { require.NoError(t, err, "Could not run git command") require.Equal(t, "refs/heads/main", strings.TrimSpace(string(out)), "Repository should have main branch as default") } - -func TestPreReceiveHook(t *testing.T) { - setup(t) - defer teardown(t) - var lastCommitHash string - err := os.Chdir(RepositoryPath("thomas", "gist1")) - require.NoError(t, err, "Could not change directory") - - commitToBare(t, "thomas", "gist1", map[string]string{ - "my_file.txt": "some allowed file", - "my_file2.txt": "some allowed file\nagain", - }) - lastCommitHash = lastHashOfCommit(t, "thomas", "gist1") - err = hooks.PreReceive(bytes.NewBufferString(fmt.Sprintf("%s %s %s", BaseHash, lastCommitHash, "refs/heads/master")), os.Stdout, os.Stderr) - require.NoError(t, err, "Should not have an error on pre-receive hook for commit+push 1") - - commitToBare(t, "thomas", "gist1", map[string]string{ - "my_file.txt": "some allowed file", - "dir/my_file.txt": "some disallowed file suddenly", - }) - lastCommitHash = lastHashOfCommit(t, "thomas", "gist1") - err = hooks.PreReceive(bytes.NewBufferString(fmt.Sprintf("%s %s %s", BaseHash, lastCommitHash, "refs/heads/master")), os.Stdout, os.Stderr) - require.Error(t, err, "Should have an error on pre-receive hook for commit+push 2") - require.Equal(t, "pushing files in directories is not allowed: [dir/my_file.txt]", err.Error(), "Error message is not correct") - - commitToBare(t, "thomas", "gist1", map[string]string{ - "my_file.txt": "some allowed file", - "dir/ok/afileagain.txt": "some disallowed file\nagain", - }) - lastCommitHash = lastHashOfCommit(t, "thomas", "gist1") - err = hooks.PreReceive(bytes.NewBufferString(fmt.Sprintf("%s %s %s", BaseHash, lastCommitHash, "refs/heads/master")), os.Stdout, os.Stderr) - require.Error(t, err, "Should have an error on pre-receive hook for commit+push 3") - require.Equal(t, "pushing files in directories is not allowed: [dir/ok/afileagain.txt dir/my_file.txt]", err.Error(), "Error message is not correct") - - commitToBare(t, "thomas", "gist1", map[string]string{ - "allowedfile.txt": "some allowed file only", - }) - lastCommitHash = lastHashOfCommit(t, "thomas", "gist1") - err = hooks.PreReceive(bytes.NewBufferString(fmt.Sprintf("%s %s %s", BaseHash, lastCommitHash, "refs/heads/master")), os.Stdout, os.Stderr) - require.Error(t, err, "Should have an error on pre-receive hook for commit+push 4") - require.Equal(t, "pushing files in directories is not allowed: [dir/ok/afileagain.txt dir/my_file.txt]", err.Error(), "Error message is not correct") - - _ = os.Chdir(os.TempDir()) // Leave the current dir to avoid errors on teardown -} - -func commitToBare(t *testing.T, user string, gist string, files map[string]string) { - err := CloneTmp(user, gist, gist, "thomas@mail.com", true) - require.NoError(t, err, "Could not clone repository") - - if len(files) > 0 { - for filename, content := range files { - if strings.Contains(filename, "/") { - dir := filepath.Dir(filename) - err := os.MkdirAll(filepath.Join(TmpRepositoryPath(gist), dir), os.ModePerm) - require.NoError(t, err, "Could not create directory") - } - _ = os.WriteFile(filepath.Join(TmpRepositoryPath(gist), filename), []byte(content), 0644) - - if err := AddAll(gist); err != nil { - require.NoError(t, err, "Could not add all to repository") - } - } - } - - if err := CommitRepository(gist, user, "thomas@mail.com"); err != nil { - require.NoError(t, err, "Could not commit to repository") - } - - if err := Push(gist); err != nil { - require.NoError(t, err, "Could not push to repository") - } -} - -func lastHashOfCommit(t *testing.T, user string, gist string) string { - cmd := exec.Command("git", "rev-parse", "HEAD") - cmd.Dir = RepositoryPath(user, gist) - out, err := cmd.Output() - require.NoError(t, err, "Could not run git command") - return strings.TrimSpace(string(out)) -} diff --git a/internal/git/test_funcs.go b/internal/git/test_funcs.go new file mode 100644 index 0000000..5cfb4c9 --- /dev/null +++ b/internal/git/test_funcs.go @@ -0,0 +1,71 @@ +package git + +import ( + "github.com/stretchr/testify/require" + "github.com/thomiceli/opengist/internal/config" + "io" + "os" + "os/exec" + "path" + "path/filepath" + "strings" + "testing" +) + +func SetupTest(t *testing.T) { + _ = os.Setenv("OPENGIST_SKIP_GIT_HOOKS", "1") + + err := config.InitConfig("", io.Discard) + require.NoError(t, err, "Could not init config") + + err = os.MkdirAll(path.Join(config.GetHomeDir(), "tests"), 0755) + ReposDirectory = path.Join("tests") + require.NoError(t, err) + + err = os.MkdirAll(filepath.Join(config.GetHomeDir(), "tmp", "repos"), 0755) + require.NoError(t, err) + + err = InitRepository("thomas", "gist1") + require.NoError(t, err) +} + +func TeardownTest(t *testing.T) { + err := os.RemoveAll(path.Join(config.GetHomeDir(), "tests")) + require.NoError(t, err, "Could not remove repos directory") +} + +func CommitToBare(t *testing.T, user string, gist string, files map[string]string) { + err := CloneTmp(user, gist, gist, "thomas@mail.com", true) + require.NoError(t, err, "Could not clone repository") + + if len(files) > 0 { + for filename, content := range files { + if strings.Contains(filename, "/") { + dir := filepath.Dir(filename) + err := os.MkdirAll(filepath.Join(TmpRepositoryPath(gist), dir), os.ModePerm) + require.NoError(t, err, "Could not create directory") + } + _ = os.WriteFile(filepath.Join(TmpRepositoryPath(gist), filename), []byte(content), 0644) + + if err := AddAll(gist); err != nil { + require.NoError(t, err, "Could not add all to repository") + } + } + } + + if err := CommitRepository(gist, user, "thomas@mail.com"); err != nil { + require.NoError(t, err, "Could not commit to repository") + } + + if err := Push(gist); err != nil { + require.NoError(t, err, "Could not push to repository") + } +} + +func LastHashOfCommit(t *testing.T, user string, gist string) string { + cmd := exec.Command("git", "rev-parse", "HEAD") + cmd.Dir = RepositoryPath(user, gist) + out, err := cmd.Output() + require.NoError(t, err, "Could not run git command") + return strings.TrimSpace(string(out)) +} diff --git a/internal/hooks/hook.go b/internal/hooks/hook.go new file mode 100644 index 0000000..aa1fcae --- /dev/null +++ b/internal/hooks/hook.go @@ -0,0 +1,24 @@ +package hooks + +import ( + "fmt" + "os" + "strconv" + "strings" +) + +const BaseHash = "0000000000000000000000000000000000000000" + +func pushOptions() map[string]string { + opts := make(map[string]string) + if pushCount, err := strconv.Atoi(os.Getenv("GIT_PUSH_OPTION_COUNT")); err == nil { + for i := 0; i < pushCount; i++ { + opt := os.Getenv(fmt.Sprintf("GIT_PUSH_OPTION_%d", i)) + kv := strings.SplitN(opt, "=", 2) + if len(kv) == 2 { + opts[kv[0]] = kv[1] + } + } + } + return opts +} diff --git a/internal/hooks/post-receive.go b/internal/hooks/post-receive.go deleted file mode 100644 index 58f15e4..0000000 --- a/internal/hooks/post-receive.go +++ /dev/null @@ -1,43 +0,0 @@ -package hooks - -import ( - "bufio" - "fmt" - "io" - "os" - "os/exec" - "strings" -) - -func PostReceive(in io.Reader, out, er io.Writer) error { - scanner := bufio.NewScanner(in) - for scanner.Scan() { - line := scanner.Text() - parts := strings.Fields(line) - if len(parts) != 3 { - _, _ = fmt.Fprintln(er, "Invalid input") - return fmt.Errorf("invalid input") - } - oldrev, _, refname := parts[0], parts[1], parts[2] - - if err := verifyHEAD(); err != nil { - setSymbolicRef(refname) - } - - if oldrev == BaseHash { - _, _ = fmt.Fprintf(out, "\nYour new repository has been created here: %s\n\n", os.Getenv("OPENGIST_REPOSITORY_URL_INTERNAL")) - _, _ = fmt.Fprintln(out, "If you want to keep working with your gist, you could set the remote URL via:") - _, _ = fmt.Fprintf(out, "git remote set-url origin %s\n\n", os.Getenv("OPENGIST_REPOSITORY_URL_INTERNAL")) - } - } - - return nil -} - -func verifyHEAD() error { - return exec.Command("git", "rev-parse", "--verify", "--quiet", "HEAD").Run() -} - -func setSymbolicRef(refname string) { - _ = exec.Command("git", "symbolic-ref", "HEAD", refname).Run() -} diff --git a/internal/hooks/post_receive.go b/internal/hooks/post_receive.go new file mode 100644 index 0000000..5e7948c --- /dev/null +++ b/internal/hooks/post_receive.go @@ -0,0 +1,77 @@ +package hooks + +import ( + "bufio" + "fmt" + "github.com/thomiceli/opengist/internal/db" + "github.com/thomiceli/opengist/internal/git" + "io" + "os" + "os/exec" + "slices" + "strings" +) + +func PostReceive(in io.Reader, out, er io.Writer) error { + opts := pushOptions() + scanner := bufio.NewScanner(in) + for scanner.Scan() { + line := scanner.Text() + parts := strings.Fields(line) + if len(parts) != 3 { + _, _ = fmt.Fprintln(er, "Invalid input") + return fmt.Errorf("invalid input") + } + oldrev, _, refname := parts[0], parts[1], parts[2] + + if err := verifyHEAD(); err != nil { + setSymbolicRef(refname) + } + + if oldrev == BaseHash { + _, _ = fmt.Fprintf(out, "\nYour new repository has been created here: %s\n\n", os.Getenv("OPENGIST_REPOSITORY_URL_INTERNAL")) + _, _ = fmt.Fprintln(out, "If you want to keep working with your gist, you could set the remote URL via:") + _, _ = fmt.Fprintf(out, "git remote set-url origin %s\n\n", os.Getenv("OPENGIST_REPOSITORY_URL_INTERNAL")) + } + } + + gist, err := db.GetGistByID(os.Getenv("OPENGIST_REPOSITORY_ID")) + if err != nil { + _, _ = fmt.Fprintln(er, "Failed to get gist") + return fmt.Errorf("failed to get gist: %w", err) + } + + if slices.Contains([]string{"public", "unlisted", "private"}, opts["visibility"]) { + gist.Private, _ = db.ParseVisibility(opts["visibility"]) + _, _ = fmt.Fprintf(out, "\nGist visibility set to %s\n\n", opts["visibility"]) + } + + if hasNoCommits, err := git.HasNoCommits(gist.User.Username, gist.Uuid); err != nil { + _, _ = fmt.Fprintln(er, "Failed to check if gist has no commits") + return fmt.Errorf("failed to check if gist has no commits: %w", err) + } else if hasNoCommits { + if err = gist.Delete(); err != nil { + _, _ = fmt.Fprintln(er, "Failed to delete gist") + return fmt.Errorf("failed to delete gist: %w", err) + } + } + + _ = gist.SetLastActiveNow() + err = gist.UpdatePreviewAndCount(true) + if err != nil { + _, _ = fmt.Fprintln(er, "Failed to update gist") + return fmt.Errorf("failed to update gist: %w", err) + } + + gist.AddInIndex() + + return nil +} + +func verifyHEAD() error { + return exec.Command("git", "rev-parse", "--verify", "--quiet", "HEAD").Run() +} + +func setSymbolicRef(refname string) { + _ = exec.Command("git", "symbolic-ref", "HEAD", refname).Run() +} diff --git a/internal/hooks/pre-receive.go b/internal/hooks/pre_receive.go similarity index 96% rename from internal/hooks/pre-receive.go rename to internal/hooks/pre_receive.go index 343f795..899263c 100644 --- a/internal/hooks/pre-receive.go +++ b/internal/hooks/pre_receive.go @@ -9,8 +9,6 @@ import ( "strings" ) -const BaseHash = "0000000000000000000000000000000000000000" - func PreReceive(in io.Reader, out, er io.Writer) error { var err error var disallowedFiles []string diff --git a/internal/hooks/pre_receive_test.go b/internal/hooks/pre_receive_test.go new file mode 100644 index 0000000..118e3d6 --- /dev/null +++ b/internal/hooks/pre_receive_test.go @@ -0,0 +1,54 @@ +package hooks + +import ( + "bytes" + "fmt" + "github.com/stretchr/testify/require" + "github.com/thomiceli/opengist/internal/git" + "os" + "testing" +) + +func TestPreReceiveHook(t *testing.T) { + git.SetupTest(t) + defer git.TeardownTest(t) + var lastCommitHash string + err := os.Chdir(git.RepositoryPath("thomas", "gist1")) + require.NoError(t, err, "Could not change directory") + + git.CommitToBare(t, "thomas", "gist1", map[string]string{ + "my_file.txt": "some allowed file", + "my_file2.txt": "some allowed file\nagain", + }) + lastCommitHash = git.LastHashOfCommit(t, "thomas", "gist1") + err = PreReceive(bytes.NewBufferString(fmt.Sprintf("%s %s %s", BaseHash, lastCommitHash, "refs/heads/master")), os.Stdout, os.Stderr) + require.NoError(t, err, "Should not have an error on pre-receive hook for commit+push 1") + + git.CommitToBare(t, "thomas", "gist1", map[string]string{ + "my_file.txt": "some allowed file", + "dir/my_file.txt": "some disallowed file suddenly", + }) + lastCommitHash = git.LastHashOfCommit(t, "thomas", "gist1") + err = PreReceive(bytes.NewBufferString(fmt.Sprintf("%s %s %s", BaseHash, lastCommitHash, "refs/heads/master")), os.Stdout, os.Stderr) + require.Error(t, err, "Should have an error on pre-receive hook for commit+push 2") + require.Equal(t, "pushing files in directories is not allowed: [dir/my_file.txt]", err.Error(), "Error message is not correct") + + git.CommitToBare(t, "thomas", "gist1", map[string]string{ + "my_file.txt": "some allowed file", + "dir/ok/afileagain.txt": "some disallowed file\nagain", + }) + lastCommitHash = git.LastHashOfCommit(t, "thomas", "gist1") + err = PreReceive(bytes.NewBufferString(fmt.Sprintf("%s %s %s", BaseHash, lastCommitHash, "refs/heads/master")), os.Stdout, os.Stderr) + require.Error(t, err, "Should have an error on pre-receive hook for commit+push 3") + require.Equal(t, "pushing files in directories is not allowed: [dir/ok/afileagain.txt dir/my_file.txt]", err.Error(), "Error message is not correct") + + git.CommitToBare(t, "thomas", "gist1", map[string]string{ + "allowedfile.txt": "some allowed file only", + }) + lastCommitHash = git.LastHashOfCommit(t, "thomas", "gist1") + err = PreReceive(bytes.NewBufferString(fmt.Sprintf("%s %s %s", BaseHash, lastCommitHash, "refs/heads/master")), os.Stdout, os.Stderr) + require.Error(t, err, "Should have an error on pre-receive hook for commit+push 4") + require.Equal(t, "pushing files in directories is not allowed: [dir/ok/afileagain.txt dir/my_file.txt]", err.Error(), "Error message is not correct") + + _ = os.Chdir(os.TempDir()) // Leave the current dir to avoid errors on teardown +} diff --git a/internal/web/git_http.go b/internal/web/git_http.go index 1f7974c..bd8d808 100644 --- a/internal/web/git_http.go +++ b/internal/web/git_http.go @@ -203,26 +203,12 @@ func pack(ctx echo.Context, serviceType string) error { cmd.Stderr = &stderr cmd.Env = os.Environ() cmd.Env = append(cmd.Env, "OPENGIST_REPOSITORY_URL_INTERNAL="+git.RepositoryUrl(ctx, gist.User.Username, gist.Identifier())) + cmd.Env = append(cmd.Env, "OPENGIST_REPOSITORY_ID="+strconv.Itoa(int(gist.ID))) if err = cmd.Run(); err != nil { return errorRes(500, "Cannot run git "+serviceType+" ; "+stderr.String(), err) } - // updatedAt is updated only if serviceType is receive-pack - if serviceType == "receive-pack" { - - if hasNoCommits, err := git.HasNoCommits(gist.User.Username, gist.Uuid); err != nil { - return err - } else if hasNoCommits { - if err = gist.Delete(); err != nil { - return err - } - } - - _ = gist.SetLastActiveNow() - _ = gist.UpdatePreviewAndCount(false) - gist.AddInIndex() - } return nil } diff --git a/internal/web/test/server.go b/internal/web/test/server.go index 1f3d709..25aaaed 100644 --- a/internal/web/test/server.go +++ b/internal/web/test/server.go @@ -127,7 +127,7 @@ func structToURLValues(s interface{}) url.Values { func setup(t *testing.T) { _ = os.Setenv("OPENGIST_SKIP_GIT_HOOKS", "1") - err := config.InitConfig("") + err := config.InitConfig("", io.Discard) require.NoError(t, err, "Could not init config") err = os.MkdirAll(filepath.Join(config.GetHomeDir()), 0755)