Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 95 additions & 0 deletions cleanup.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package main

import (
"os"
"os/exec"
"path/filepath"
"slices"
"strconv"
"strings"

"github.com/utilitywarehouse/git-mirror/pkg/mirror"
)

var gitExecutablePath = exec.Command("git").String()

// cleanupOrphanedRepos deletes directory of the repos from the default root
// which are no longer referenced in config and it was removed while app was down.
// Any removal while app is running is already handled by ensureConfig() hence
// this function should be called once
// this is best effort clean up as orphaned published link will not be clean up
// as its not known where it was published.
func cleanupOrphanedRepos(config *mirror.RepoPoolConfig, repoPool *mirror.RepoPool) {
// if default root is not set repos might not be located in same dir
if config.Defaults.Root == "" {
return
}

repoDirs := repoPool.RepositoriesDirPath()

entries, err := os.ReadDir(config.Defaults.Root)
if err != nil {
logger.Error("unable to read root dir for clean up", "err", err)
return
}

for _, entry := range entries {
if !entry.IsDir() {
continue
}

fullPath := filepath.Join(config.Defaults.Root, entry.Name())

if slices.Contains(repoDirs, fullPath) {
continue
}

// since git-mirror creates bare repository for mirror
// non-repo dir or non-bare repo dir must be skipped
ok, err := isBareRepo(fullPath)
if err != nil {
logger.Error("unable to check if bare repo", "path", fullPath, "err", err)
continue
}

if !ok {
continue
}

logger.Info("removing orphaned repo dir...", "path", fullPath)
if err := os.RemoveAll(fullPath); err != nil {
logger.Error("unable orphaned repo dir", "path", fullPath, "err", err)
continue
}
}
}

func isInsideGitDir(cwd string) bool {
// err is expected here
output, _ := runGitCommand(cwd, "rev-parse", "--is-inside-git-dir")
return output == "true"
}

func isBareRepo(cwd string) (bool, error) {
// bare repository doesn't have worktrees
if !isInsideGitDir(cwd) {
return false, nil
}

output, err := runGitCommand(cwd, "rev-parse", "--is-bare-repository")
if err != nil {
return false, err
}

return strconv.ParseBool(output)
}

// runGitCommand runs git command with given arguments on given CWD
func runGitCommand(cwd string, args ...string) (string, error) {
cmd := exec.Command(gitExecutablePath, args...)
if cwd != "" {
cmd.Dir = cwd
}
output, err := cmd.CombinedOutput()
return strings.TrimSpace(string(output)), err
}
10 changes: 9 additions & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,8 +120,16 @@ func main() {
os.Exit(1)
}

firstRun := true
onConfigChange := func(config *mirror.RepoPoolConfig) bool {
return ensureConfig(repoPool, config)
ok := ensureConfig(repoPool, config)

if firstRun {
cleanupOrphanedRepos(config, repoPool)
firstRun = false
}

return ok
}

// Start watching the config file
Expand Down
11 changes: 11 additions & 0 deletions pkg/mirror/repo_pool.go
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,17 @@ func (rp *RepoPool) RepositoriesRemote() []string {
return urls
}

func (rp *RepoPool) RepositoriesDirPath() []string {
rp.lock.RLock()
defer rp.lock.RUnlock()

var paths []string
for _, repo := range rp.repos {
paths = append(paths, repo.dir)
}
return paths
}

// AddWorktreeLink is wrapper around repositories AddWorktreeLink method
func (rp *RepoPool) AddWorktreeLink(remote string, wt WorktreeConfig) error {
rp.lock.RLock()
Expand Down
Loading