mirror of
https://github.com/thomiceli/opengist.git
synced 2024-12-23 04:52:40 +00:00
Set gist visibility via Git push options (#215)
This commit is contained in:
parent
7a75c5ecfa
commit
db6d6a5eba
15 changed files with 326 additions and 213 deletions
13
docs/usage/git-push-options.md
Normal file
13
docs/usage/git-push-options.md
Normal 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
|
||||||
|
```
|
|
@ -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")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
`
|
`
|
||||||
|
|
|
@ -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))
|
|
||||||
}
|
|
||||||
|
|
71
internal/git/test_funcs.go
Normal file
71
internal/git/test_funcs.go
Normal 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
24
internal/hooks/hook.go
Normal 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
|
||||||
|
}
|
|
@ -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()
|
|
||||||
}
|
|
77
internal/hooks/post_receive.go
Normal file
77
internal/hooks/post_receive.go
Normal 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()
|
||||||
|
}
|
|
@ -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
|
54
internal/hooks/pre_receive_test.go
Normal file
54
internal/hooks/pre_receive_test.go
Normal 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
|
||||||
|
}
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
Loading…
Reference in a new issue