all repos

mugit @ d85c7a0

๐Ÿฎ git server that your cow will love
5 files changed, 0 insertions(+), 166 deletions(-)
remove current implementation of hooks
Author: Oleksandr Smirnov olexsmir@gmail.com
Committed at: 2026-06-10 11:50:20 +0300
Authored at: 2026-05-31 13:11:11 +0300
Change ID: pksurstsyoursrqlwqkzrmklssnrluko
Parent: f492c42
M README.md
ยทยทยท
        182
        182
         mugit repo sync myproject

      
        183
        183
         ```

      
        184
        184
         

      
        185
        
        -## Per-repo hooks

      
        186
        
        -

      
        187
        
        -mugit creates these server-side hooks per repository: `pre-receive`, `update`, `post-receive`, `post-update`.

      
        188
        
        -

      
        189
        
        -Each hook delegates to executable scripts in: `<repo>.git/hooks/<hook>.d/`

      
        190
        
        -

      
        191
        
        -<details>

      
        192
        
        -<summary>Example: golangci-lint as a pre-receive hook</summary>

      
        193
        
        -```bash

      
        194
        
        -# file: `<repo>.git/hooks/pre-receive.d/golangci-lint.sh`:

      
        195
        
        -#!/bin/sh

      
        196
        
        -set -eu

      
        197
        
        -

      
        198
        
        -while read oldrev newrev refname; do

      
        199
        
        -  tmpdir=$(mktemp -d)

      
        200
        
        -  trap 'rm -rf "$tmpdir"' EXIT INT TERM

      
        201
        
        -

      
        202
        
        -  git archive "$newrev" | tar -xC "$tmpdir"

      
        203
        
        -

      
        204
        
        -  if ! (cd "$tmpdir" && golangci-lint run ./...); then

      
        205
        
        -    echo "golangci-lint failed for $refname โ€” push rejected" >&2

      
        206
        
        -    exit 1

      
        207
        
        -  fi

      
        208
        
        -done

      
        209
        
        -```

      
        210
        
        -</details>

      
        211
        
        -

      
        212
        185
         ## License

      
        213
        186
         

      
        214
        187
         mugit is licensed under the MIT License.

      
D internal/git/hooks.go
ยทยทยท
        1
        
        -package git

      
        2
        
        -

      
        3
        
        -import (

      
        4
        
        -	"fmt"

      
        5
        
        -	"os"

      
        6
        
        -	"os/exec"

      
        7
        
        -	"path/filepath"

      
        8
        
        -)

      
        9
        
        -

      
        10
        
        -const hookDelegateScriptBody = `# AUTO GENERATED BY MUGIT, DO NOT MODIFY

      
        11
        
        -data=$(cat)

      
        12
        
        -exitcodes=""

      
        13
        
        -hookname=$(basename "$0")

      
        14
        
        -git_dir="${GIT_DIR:-$(dirname "$0")/..}"

      
        15
        
        -

      
        16
        
        -for hook in "${git_dir}/hooks/${hookname}.d"/*; do

      
        17
        
        -  test -x "${hook}" && test -f "${hook}" || continue

      
        18
        
        -  echo "${data}" | "${hook}" "$@"

      
        19
        
        -  exitcodes="${exitcodes} $?"

      
        20
        
        -done

      
        21
        
        -

      
        22
        
        -for i in ${exitcodes}; do

      
        23
        
        -  [ ${i} -eq 0 ] || exit ${i}

      
        24
        
        -done

      
        25
        
        -`

      
        26
        
        -

      
        27
        
        -var serverHookNames = []string{"pre-receive", "post-receive", "post-update", "update"}

      
        28
        
        -

      
        29
        
        -func hookDelegateScript() (string, error) {

      
        30
        
        -	bashPath, err := exec.LookPath("bash")

      
        31
        
        -	if err != nil {

      
        32
        
        -		return "", fmt.Errorf("failed to locate bash: %w", err)

      
        33
        
        -	}

      
        34
        
        -	return fmt.Sprintf("#!%s\n%s", bashPath, hookDelegateScriptBody), nil

      
        35
        
        -}

      
        36
        
        -

      
        37
        
        -// SetupHooks installs default server-side hook delegates for this repository.

      
        38
        
        -func (g *Repo) SetupHooks() error {

      
        39
        
        -	delegateScript, err := hookDelegateScript()

      
        40
        
        -	if err != nil {

      
        41
        
        -		return err

      
        42
        
        -	}

      
        43
        
        -

      
        44
        
        -	for _, hook := range serverHookNames {

      
        45
        
        -		hookDir := filepath.Join(g.path, "hooks", hook+".d")

      
        46
        
        -		if err := os.MkdirAll(hookDir, 0o755); err != nil {

      
        47
        
        -			return fmt.Errorf("failed to create %s: %w", hookDir, err)

      
        48
        
        -		}

      
        49
        
        -

      
        50
        
        -		delegatePath := filepath.Join(g.path, "hooks", hook)

      
        51
        
        -		if err := os.WriteFile(delegatePath, []byte(delegateScript), 0o755); err != nil {

      
        52
        
        -			return fmt.Errorf("failed to write hook delegate %s: %w", delegatePath, err)

      
        53
        
        -		}

      
        54
        
        -	}

      
        55
        
        -

      
        56
        
        -	return nil

      
        57
        
        -}

      
D internal/git/hooks_test.go
ยทยทยท
        1
        
        -package git

      
        2
        
        -

      
        3
        
        -import (

      
        4
        
        -	"bytes"

      
        5
        
        -	"os"

      
        6
        
        -	"path/filepath"

      
        7
        
        -	"strings"

      
        8
        
        -	"testing"

      
        9
        
        -

      
        10
        
        -	"olexsmir.xyz/x/is"

      
        11
        
        -)

      
        12
        
        -

      
        13
        
        -func TestRepo_SetupHooks(t *testing.T) {

      
        14
        
        -	t.Run("creates hook delegates and hook directories", func(t *testing.T) {

      
        15
        
        -		repoPath := filepath.Join(t.TempDir(), "hooks.git")

      
        16
        
        -		is.Err(t, Init(repoPath), nil)

      
        17
        
        -

      
        18
        
        -		for _, hook := range serverHookNames {

      
        19
        
        -			delegatePath := filepath.Join(repoPath, "hooks", hook)

      
        20
        
        -			info, err := os.Stat(delegatePath)

      
        21
        
        -			is.Err(t, err, nil)

      
        22
        
        -			is.Equal(t, info.Mode()&0o111 != 0, true)

      
        23
        
        -

      
        24
        
        -			hookDir := filepath.Join(repoPath, "hooks", hook+".d")

      
        25
        
        -			dirInfo, err := os.Stat(hookDir)

      
        26
        
        -			is.Err(t, err, nil)

      
        27
        
        -			is.Equal(t, dirInfo.IsDir(), true)

      
        28
        
        -		}

      
        29
        
        -	})

      
        30
        
        -

      
        31
        
        -	t.Run("is idempotent", func(t *testing.T) {

      
        32
        
        -		repoPath := filepath.Join(t.TempDir(), "hooks.git")

      
        33
        
        -		is.Err(t, Init(repoPath), nil)

      
        34
        
        -

      
        35
        
        -		repo, err := Open(repoPath, "")

      
        36
        
        -		is.Err(t, err, nil)

      
        37
        
        -

      
        38
        
        -		is.Err(t, repo.SetupHooks(), nil)

      
        39
        
        -		is.Err(t, repo.SetupHooks(), nil)

      
        40
        
        -	})

      
        41
        
        -

      
        42
        
        -	t.Run("keeps custom scripts in hook directories", func(t *testing.T) {

      
        43
        
        -		repoPath := filepath.Join(t.TempDir(), "hooks.git")

      
        44
        
        -		is.Err(t, Init(repoPath), nil)

      
        45
        
        -

      
        46
        
        -		customHook := filepath.Join(repoPath, "hooks", "pre-receive.d", "90-custom.sh")

      
        47
        
        -		is.Err(t, os.WriteFile(customHook, []byte("#!/bin/sh\necho ok\n"), 0o755), nil)

      
        48
        
        -

      
        49
        
        -		repo, err := Open(repoPath, "")

      
        50
        
        -		is.Err(t, err, nil)

      
        51
        
        -		is.Err(t, repo.SetupHooks(), nil)

      
        52
        
        -

      
        53
        
        -		data, err := os.ReadFile(customHook)

      
        54
        
        -		is.Err(t, err, nil)

      
        55
        
        -		is.Equal(t, string(data), "#!/bin/sh\necho ok\n")

      
        56
        
        -	})

      
        57
        
        -

      
        58
        
        -	t.Run("delegate forwards stdin and args to hook scripts", func(t *testing.T) {

      
        59
        
        -		repoPath := filepath.Join(t.TempDir(), "hooks.git")

      
        60
        
        -		is.Err(t, Init(repoPath), nil)

      
        61
        
        -

      
        62
        
        -		delegatePath := filepath.Join(repoPath, "hooks", "update")

      
        63
        
        -		delegate, err := os.ReadFile(delegatePath)

      
        64
        
        -		is.Err(t, err, nil)

      
        65
        
        -		firstLine, _, _ := strings.Cut(string(delegate), "\n")

      
        66
        
        -		is.Equal(t, strings.HasPrefix(firstLine, "#!"), true)

      
        67
        
        -		is.Equal(t, strings.Contains(firstLine, "bash"), true)

      
        68
        
        -		is.Equal(t, strings.Contains(firstLine, "/usr/bin/env"), false)

      
        69
        
        -		is.Equal(t, bytes.Contains(delegate, []byte(`echo "${data}" | "${hook}" "$@"`)), true)

      
        70
        
        -	})

      
        71
        
        -}

      
M internal/git/repo.go
ยทยทยท
        92
        92
         	if _, err := git.PlainInit(path, true); err != nil {

      
        93
        93
         		return fmt.Errorf("failed to initialize repo: %w", err)

      
        94
        94
         	}

      
        95
        
        -	if err := (&Repo{path: path}).SetupHooks(); err != nil {

      
        96
        
        -		return fmt.Errorf("failed to setup hooks: %w", err)

      
        97
        
        -	}

      
        98
        95
         	return nil

      
        99
        96
         }

      
        100
        97
         

      
M testscript/cli-repo-new.txtar
ยทยทยท
        4
        4
         exists $REPOS/new-test-repo.git/HEAD

      
        5
        5
         exists $REPOS/new-test-repo.git/objects/

      
        6
        6
         exists $REPOS/new-test-repo.git/refs/

      
        7
        
        -exists $REPOS/new-test-repo.git/hooks/pre-receive

      
        8
        
        -exists $REPOS/new-test-repo.git/hooks/update

      
        9
        
        -exists $REPOS/new-test-repo.git/hooks/post-receive

      
        10
        
        -exists $REPOS/new-test-repo.git/hooks/post-update

      
        11
        
        -exists $REPOS/new-test-repo.git/hooks/pre-receive.d/

      
        12
        
        -exists $REPOS/new-test-repo.git/hooks/update.d/

      
        13
        
        -exists $REPOS/new-test-repo.git/hooks/post-receive.d/

      
        14
        
        -exists $REPOS/new-test-repo.git/hooks/post-update.d/

      
        15
        7
         

      
        16
        8
         

      
        17
        9
         # missing repo name