Set gist visibility via Git push options (#215)

This commit is contained in:
Thomas Miceli 2024-01-30 00:07:57 +01:00
parent 7a75c5ecfa
commit db6d6a5eba
15 changed files with 326 additions and 213 deletions

View file

@ -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
```

View file

@ -1,9 +1,14 @@
package cli package cli
import ( 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/thomiceli/opengist/internal/hooks"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
"io"
"os" "os"
"path/filepath"
) )
var CmdHook = cli.Command{ var CmdHook = cli.Command{
@ -19,6 +24,7 @@ var CmdHookPreReceive = cli.Command{
Name: "pre-receive", Name: "pre-receive",
Usage: "Run Git server pre-receive hook for a repository", Usage: "Run Git server pre-receive hook for a repository",
Action: func(ctx *cli.Context) error { Action: func(ctx *cli.Context) error {
initialize(ctx)
if err := hooks.PreReceive(os.Stdin, os.Stdout, os.Stderr); err != nil { if err := hooks.PreReceive(os.Stdin, os.Stdout, os.Stderr); err != nil {
os.Exit(1) os.Exit(1)
} }
@ -30,9 +36,21 @@ var CmdHookPostReceive = cli.Command{
Name: "post-receive", Name: "post-receive",
Usage: "Run Git server post-receive hook for a repository", Usage: "Run Git server post-receive hook for a repository",
Action: func(ctx *cli.Context) error { Action: func(ctx *cli.Context) error {
initialize(ctx)
if err := hooks.PostReceive(os.Stdin, os.Stdout, os.Stderr); err != nil { if err := hooks.PostReceive(os.Stdin, os.Stdout, os.Stderr); err != nil {
os.Exit(1) os.Exit(1)
} }
return nil 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")
}
}

View file

@ -59,7 +59,7 @@ func App() error {
func Initialize(ctx *cli.Context) { func Initialize(ctx *cli.Context) {
fmt.Println("Opengist v" + config.OpengistVersion) 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) panic(err)
} }
if err := os.MkdirAll(filepath.Join(config.GetHomeDir()), 0755); err != nil { if err := os.MkdirAll(filepath.Join(config.GetHomeDir()), 0755); err != nil {
@ -83,8 +83,8 @@ func Initialize(ctx *cli.Context) {
homePath := config.GetHomeDir() homePath := config.GetHomeDir()
log.Info().Msg("Data directory: " + homePath) log.Info().Msg("Data directory: " + homePath)
if err := createSymlink(); err != nil { if err := createSymlink(homePath, ctx.String("config")); err != nil {
log.Fatal().Err(err).Send() log.Fatal().Err(err).Msg("Failed to create symlinks")
} }
if err := os.MkdirAll(filepath.Join(homePath, "repos"), 0755); err != nil { 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() exePath, err := os.Executable()
if err != nil { if err != nil {
return err return err
} }
symlinkPath := path.Join(config.GetHomeDir(), "opengist-bin") symlinkExePath := path.Join(config.GetHomeDir(), "symlinks", "opengist")
if _, err := os.Lstat(symlinkExePath); err == nil {
if _, err := os.Lstat(symlinkPath); err == nil { if err := os.Remove(symlinkExePath); err != nil {
if err := os.Remove(symlinkPath); err != nil {
return err 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
} }

View file

@ -91,18 +91,18 @@ func configWithDefaults() (*config, error) {
return c, nil return c, nil
} }
func InitConfig(configPath string) error { func InitConfig(configPath string, out io.Writer) error {
// Default values // Default values
c, err := configWithDefaults() c, err := configWithDefaults()
if err != nil { if err != nil {
return err return err
} }
if err = loadConfigFromYaml(c, configPath); err != nil { if err = loadConfigFromYaml(c, configPath, out); err != nil {
return err return err
} }
if err = loadConfigFromEnv(c); err != nil { if err = loadConfigFromEnv(c, out); err != nil {
return err return err
} }
@ -202,7 +202,7 @@ func GetHomeDir() string {
return filepath.Clean(absolutePath) return filepath.Clean(absolutePath)
} }
func loadConfigFromYaml(c *config, configPath string) error { func loadConfigFromYaml(c *config, configPath string, out io.Writer) error {
if configPath != "" { if configPath != "" {
absolutePath, _ := filepath.Abs(configPath) absolutePath, _ := filepath.Abs(configPath)
absolutePath = filepath.Clean(absolutePath) absolutePath = filepath.Clean(absolutePath)
@ -211,9 +211,9 @@ func loadConfigFromYaml(c *config, configPath string) error {
if !os.IsNotExist(err) { if !os.IsNotExist(err) {
return err return err
} }
fmt.Println("No YAML config file found at " + absolutePath) _, _ = fmt.Fprintln(out, "No YAML config file found at "+absolutePath)
} else { } else {
fmt.Println("Using YAML config file: " + absolutePath) _, _ = fmt.Fprintln(out, "Using YAML config file: "+absolutePath)
// Override default values with values from config.yml // Override default values with values from config.yml
d := yaml.NewDecoder(file) d := yaml.NewDecoder(file)
@ -223,13 +223,13 @@ func loadConfigFromYaml(c *config, configPath string) error {
defer file.Close() defer file.Close()
} }
} else { } else {
fmt.Println("No YAML config file specified.") _, _ = fmt.Fprintln(out, "No YAML config file specified.")
} }
return nil return nil
} }
func loadConfigFromEnv(c *config) error { func loadConfigFromEnv(c *config, out io.Writer) error {
v := reflect.ValueOf(c).Elem() v := reflect.ValueOf(c).Elem()
var envVars []string var envVars []string
@ -260,9 +260,9 @@ func loadConfigFromEnv(c *config) error {
} }
if len(envVars) > 0 { if len(envVars) > 0 {
fmt.Println("Using environment variables config: " + strings.Join(envVars, ", ")) _, _ = fmt.Fprintln(out, "Using environment variables config: "+strings.Join(envVars, ", "))
} else { } else {
fmt.Println("No environment variables config specified.") _, _ = fmt.Fprintln(out, "No environment variables config specified.")
} }
return nil return nil

View file

@ -37,11 +37,11 @@ func (v Visibility) Next() Visibility {
func ParseVisibility[T string | int](v T) (Visibility, error) { func ParseVisibility[T string | int](v T) (Visibility, error) {
switch s := fmt.Sprint(v); s { switch s := fmt.Sprint(v); s {
case "0": case "0", "public":
return PublicVisibility, nil return PublicVisibility, nil
case "1": case "1", "unlisted":
return UnlistedVisibility, nil return UnlistedVisibility, nil
case "2": case "2", "private":
return PrivateVisibility, nil return PrivateVisibility, nil
default: default:
return -1, fmt.Errorf("unknown visibility %q", s) return -1, fmt.Errorf("unknown visibility %q", s)

View file

@ -24,7 +24,6 @@ var (
) )
const truncateLimit = 2 << 18 const truncateLimit = 2 << 18
const BaseHash = "0000000000000000000000000000000000000000"
type RevisionNotFoundError struct{} type RevisionNotFoundError struct{}
@ -565,5 +564,5 @@ func removeFilesExceptGit(dir string) error {
} }
const hookTemplate = `#!/bin/sh 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
` `

View file

@ -1,44 +1,18 @@
package git package git
import ( import (
"bytes"
"fmt"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/thomiceli/opengist/internal/config" "github.com/thomiceli/opengist/internal/config"
"github.com/thomiceli/opengist/internal/hooks"
"os" "os"
"os/exec" "os/exec"
"path" "path"
"path/filepath"
"strings" "strings"
"testing" "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) { func TestInitDeleteRepository(t *testing.T) {
setup(t) SetupTest(t)
defer teardown(t) defer TeardownTest(t)
cmd := exec.Command("git", "rev-parse", "--is-bare-repository") cmd := exec.Command("git", "rev-parse", "--is-bare-repository")
cmd.Dir = RepositoryPath("thomas", "gist1") cmd.Dir = RepositoryPath("thomas", "gist1")
@ -55,14 +29,14 @@ func TestInitDeleteRepository(t *testing.T) {
} }
func TestCommits(t *testing.T) { func TestCommits(t *testing.T) {
setup(t) SetupTest(t)
defer teardown(t) defer TeardownTest(t)
hasNoCommits, err := HasNoCommits("thomas", "gist1") hasNoCommits, err := HasNoCommits("thomas", "gist1")
require.NoError(t, err, "Could not check if repository has no commits") require.NoError(t, err, "Could not check if repository has no commits")
require.True(t, hasNoCommits, "Repository should have 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") hasNoCommits, err = HasNoCommits("thomas", "gist1")
require.NoError(t, err, "Could not check if repository has no commits") 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.NoError(t, err, "Could not count commits")
require.Equal(t, "1", nbCommits, "Repository should have 1 commit") 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") nbCommits, err = CountCommits("thomas", "gist1")
require.NoError(t, err, "Could not count commits") require.NoError(t, err, "Could not count commits")
require.Equal(t, "2", nbCommits, "Repository should have 2 commits") require.Equal(t, "2", nbCommits, "Repository should have 2 commits")
} }
func TestContent(t *testing.T) { func TestContent(t *testing.T) {
setup(t) SetupTest(t)
defer teardown(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_file.txt": "I love Opengist\n",
"my_other_file.txt": `I really "my_other_file.txt": `I really
hate Opengist`, hate Opengist`,
@ -103,7 +77,7 @@ hate Opengist`,
require.False(t, truncated, "Content should not be truncated") require.False(t, truncated, "Content should not be truncated")
require.Equal(t, "I really\nhate Opengist", content, "Content is not correct") 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_renamed_file.txt": "I love Opengist\n",
"my_other_file.txt": `I really "my_other_file.txt": `I really
like Opengist actually`, like Opengist actually`,
@ -182,18 +156,18 @@ like Opengist actually`,
} }
func TestGitGc(t *testing.T) { func TestGitGc(t *testing.T) {
setup(t) SetupTest(t)
defer teardown(t) defer TeardownTest(t)
err := GcRepos() err := GcRepos()
require.NoError(t, err, "Could not run git gc") require.NoError(t, err, "Could not run git gc")
} }
func TestFork(t *testing.T) { func TestFork(t *testing.T) {
setup(t) SetupTest(t)
defer teardown(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_file.txt": "I love Opengist\n",
}) })
@ -210,10 +184,10 @@ func TestFork(t *testing.T) {
} }
func TestTruncate(t *testing.T) { func TestTruncate(t *testing.T) {
setup(t) SetupTest(t)
defer teardown(t) defer TeardownTest(t)
commitToBare(t, "thomas", "gist1", map[string]string{ CommitToBare(t, "thomas", "gist1", map[string]string{
"my_file.txt": "A", "my_file.txt": "A",
}) })
@ -227,7 +201,7 @@ func TestTruncate(t *testing.T) {
builder.WriteString("A") builder.WriteString("A")
} }
str := builder.String() str := builder.String()
commitToBare(t, "thomas", "gist1", map[string]string{ CommitToBare(t, "thomas", "gist1", map[string]string{
"my_file.txt": str, "my_file.txt": str,
}) })
@ -236,7 +210,7 @@ func TestTruncate(t *testing.T) {
require.True(t, truncated, "Content should be truncated") require.True(t, truncated, "Content should be truncated")
require.Equal(t, truncateLimit, len(content), "Content size should be at truncate limit") 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, "my_file.txt": "AA\n" + str,
}) })
@ -247,8 +221,8 @@ func TestTruncate(t *testing.T) {
} }
func TestGitInitBranchNames(t *testing.T) { func TestGitInitBranchNames(t *testing.T) {
setup(t) SetupTest(t)
defer teardown(t) defer TeardownTest(t)
cmd := exec.Command("git", "symbolic-ref", "HEAD") cmd := exec.Command("git", "symbolic-ref", "HEAD")
cmd.Dir = RepositoryPath("thomas", "gist1") cmd.Dir = RepositoryPath("thomas", "gist1")
@ -266,83 +240,3 @@ func TestGitInitBranchNames(t *testing.T) {
require.NoError(t, err, "Could not run git command") 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") 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))
}

View file

@ -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))
}

24
internal/hooks/hook.go Normal file
View file

@ -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
}

View file

@ -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()
}

View file

@ -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()
}

View file

@ -9,8 +9,6 @@ import (
"strings" "strings"
) )
const BaseHash = "0000000000000000000000000000000000000000"
func PreReceive(in io.Reader, out, er io.Writer) error { func PreReceive(in io.Reader, out, er io.Writer) error {
var err error var err error
var disallowedFiles []string var disallowedFiles []string

View file

@ -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
}

View file

@ -203,26 +203,12 @@ func pack(ctx echo.Context, serviceType string) error {
cmd.Stderr = &stderr cmd.Stderr = &stderr
cmd.Env = os.Environ() 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_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 { if err = cmd.Run(); err != nil {
return errorRes(500, "Cannot run git "+serviceType+" ; "+stderr.String(), err) 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 return nil
} }

View file

@ -127,7 +127,7 @@ func structToURLValues(s interface{}) url.Values {
func setup(t *testing.T) { func setup(t *testing.T) {
_ = os.Setenv("OPENGIST_SKIP_GIT_HOOKS", "1") _ = os.Setenv("OPENGIST_SKIP_GIT_HOOKS", "1")
err := config.InitConfig("") err := config.InitConfig("", io.Discard)
require.NoError(t, err, "Could not init config") require.NoError(t, err, "Could not init config")
err = os.MkdirAll(filepath.Join(config.GetHomeDir()), 0755) err = os.MkdirAll(filepath.Join(config.GetHomeDir()), 0755)