all repos

mugit @ 2c8d7d2e171b6299cfa099657fd2020b17e5cfa3

馃惍 git server that your cow will love
6 files changed, 59 insertions(+), 4 deletions(-)
auto init repository on push
Author: Oleksandr Smirnov olexsmir@gmail.com
Committed at: 2026-04-24 15:22:00 +0300
Authored at: 2026-04-23 22:30:04 +0300
Change ID: pnprxulsmlqspvzlttzutlrnpvunwtrw
Parent: aaf0e5f
M CHANGELOG.md
路路路
        19
        19
         - `mugit repo default` changes default branch of a repo.

      
        20
        20
         - `mugit repo sync` syncs specified mirror repo.

      
        21
        21
         - Mirror status now inclues last sync time(when there were changes fetched) and last checked time.

      
        
        22
        +- Automatically initialize repository on push.

      
        22
        23
         

      
        23
        24
         ### Bug fixes:

      
        24
        25
         - Allow downloading only valid and existing refs.

      
M internal/git/repo.go
路路路
        22
        22
         	ErrEmptyRepo    = errors.New("repository has no commits")

      
        23
        23
         	ErrFileNotFound = errors.New("file not found")

      
        24
        24
         	ErrPrivate      = errors.New("repository is private")

      
        
        25
        +	ErrRepoNotFound = errors.New("repository not found")

      
        25
        26
         )

      
        26
        27
         

      
        27
        28
         type Repo struct {

      路路路
        37
        38
         	g.path = path

      
        38
        39
         	g.r, err = git.PlainOpen(path)

      
        39
        40
         	if err != nil {

      
        
        41
        +		if errors.Is(err, git.ErrRepositoryNotExists) {

      
        
        42
        +			return nil, ErrRepoNotFound

      
        
        43
        +		}

      
        40
        44
         		return nil, fmt.Errorf("opening %s: %w", path, err)

      
        41
        45
         	}

      
        42
        46
         

      
M internal/git/repo_test.go
路路路
        87
        87
         

      
        88
        88
         	t.Run("fails on invalid path", func(t *testing.T) {

      
        89
        89
         		_, err := Open("/nonexistent/path", "")

      
        90
        
        -		is.Err(t, err, "does not exist")

      
        
        90
        +		is.Err(t, err, ErrRepoNotFound)

      
        91
        91
         	})

      
        92
        92
         

      
        93
        93
         	t.Run("fails on invalid ref", func(t *testing.T) {

      
M internal/ssh/ssh.go
路路路
        48
        48
         

      
        49
        49
         	repo, err := git.Open(repoPath, "")

      
        50
        50
         	if err != nil {

      
        51
        
        -		return s.replyWithGitError(stderr, "repository not found", err)

      
        
        51
        +		if !errors.Is(err, git.ErrRepoNotFound) || gitCmd != "git-receive-pack" {

      
        
        52
        +			return s.replyWithGitError(stderr, "repository not found", err)

      
        
        53
        +		}

      
        
        54
        +

      
        
        55
        +		// SSH Git clients display informational messages from stderr; stdout must remain protocol-only for git-receive-pack.

      
        
        56
        +		if ierr := s.replyWithGitInfo(stderr, "auto-initializing "+repoName); ierr != nil {

      
        
        57
        +			return ierr

      
        
        58
        +		}

      
        
        59
        +

      
        
        60
        +		if ierr := git.Init(repoPath); ierr != nil {

      
        
        61
        +			return s.replyWithGitError(stderr, "failed to init repo", ierr)

      
        
        62
        +		}

      
        
        63
        +

      
        
        64
        +		repo, err = git.Open(repoPath, "")

      
        
        65
        +		if err != nil {

      
        
        66
        +			return s.replyWithGitError(stderr, "failed to open initialized repo", err)

      
        
        67
        +		}

      
        52
        68
         	}

      
        53
        69
         

      
        54
        70
         	switch gitCmd {

      路路路
        105
        121
         }

      
        106
        122
         

      
        107
        123
         func (s *Shell) replyWithGitError(stderr io.Writer, msg string, cause error) error {

      
        108
        
        -	if _, err := fmt.Fprintf(stderr, "fatal: %s\n", msg); err != nil {

      
        
        124
        +	if _, err := fmt.Fprintf(stderr, "error: %s\n", msg); err != nil {

      
        109
        125
         		return err

      
        110
        126
         	}

      
        111
        127
         

      
        112
        128
         	return cause

      
        113
        129
         }

      
        
        130
        +

      
        
        131
        +func (s *Shell) replyWithGitInfo(msgOut io.Writer, msg string) error {

      
        
        132
        +	_, err := fmt.Fprintf(msgOut, "info: %s\n", msg)

      
        
        133
        +	return err

      
        
        134
        +}

      
M testscript/ssh-clone-nonexistent.txtar
路路路
        16
        16
         

      
        17
        17
         # clone non existent repo

      
        18
        18
         ! exec env GIT_SSH_COMMAND=$SSH_WRAPPER git clone git@localhost:nonexistent.git wont-clone

      
        19
        
        -stderr 'fatal: repository not found'

      
        
        19
        +stderr 'error: repository not found'

      
        20
        20
         ! stderr 'ERROR mugit'

      
        21
        21
         

      
        22
        22
         

      
M testscript/ssh-push.txtar
路路路
        12
        12
         stdout 'hello from ssh'

      
        13
        13
         

      
        14
        14
         

      
        
        15
        +# auto initializing on first push

      
        
        16
        +git init local2

      
        
        17
        +cp file.txt local2/file.txt

      
        
        18
        +git -C local2 add file.txt

      
        
        19
        +git -C local2 commit -m initial

      
        
        20
        +

      
        
        21
        +

      
        
        22
        +exec env GIT_SSH_COMMAND=$SSH_WRAPPER git -C local2 push git@localhost:auto-init master

      
        
        23
        +stderr 'info: auto-initializing auto-init'

      
        
        24
        +exists $REPOS/auto-init.git/HEAD

      
        
        25
        +

      
        
        26
        +# subsequent pushes should not re-initialize

      
        
        27
        +cp file2.txt local2/file2.txt

      
        
        28
        +git -C local2 add file2.txt

      
        
        29
        +git -C local2 commit -m second

      
        
        30
        +

      
        
        31
        +exec env GIT_SSH_COMMAND=$SSH_WRAPPER git -C local2 push git@localhost:auto-init master

      
        
        32
        +! stderr 'auto-initializing auto-init'

      
        
        33
        +

      
        
        34
        +

      
        
        35
        +# verify pushed content is available

      
        
        36
        +exec env GIT_SSH_COMMAND=$SSH_WRAPPER git clone git@localhost:auto-init verify-auto-init

      
        
        37
        +exists verify-auto-init/file.txt

      
        
        38
        +exists verify-auto-init/file2.txt

      
        
        39
        +

      
        
        40
        +

      
        15
        41
         # should not allow execution of commands

      
        16
        42
         ! exec $SSH_WRAPPER ignored 'echo hi'

      
        17
        43
         stderr 'access denied:'

      路路路
        19
        45
         

      
        20
        46
         -- file.txt --

      
        21
        47
         hello from ssh

      
        
        48
        +

      
        
        49
        +-- file2.txt --

      
        
        50
        +hello second push