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
6 changes: 5 additions & 1 deletion config.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ const (
var defaultRoot = path.Join(os.TempDir(), "git-mirror", "src")

// WatchConfig polls the config file every interval and reloads if modified
func WatchConfig(ctx context.Context, path string, interval time.Duration, onChange func(*mirror.RepoPoolConfig)) {
func WatchConfig(ctx context.Context, path string, watchConfig bool, interval time.Duration, onChange func(*mirror.RepoPoolConfig)) {
var lastModTime time.Time

for {
Expand All @@ -47,6 +47,10 @@ func WatchConfig(ctx context.Context, path string, interval time.Duration, onCha
}
}

if !watchConfig {
return
}

t := time.NewTimer(interval)
select {
case <-t.C:
Expand Down
1 change: 0 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ require (
github.com/google/go-cmp v0.7.0
github.com/prometheus/client_golang v1.21.1
github.com/sasha-s/go-deadlock v0.3.5
github.com/urfave/cli/v3 v3.0.0-beta1
gopkg.in/yaml.v3 v3.0.1
)

Expand Down
11 changes: 0 additions & 11 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -15,20 +15,15 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/petermattis/goid v0.0.0-20240813172612-4fcff4a6cae7 h1:Dx7Ovyv/SFnMFw3fD4oEoeorXc6saIiQ23LrGLth0Gw=
github.com/petermattis/goid v0.0.0-20240813172612-4fcff4a6cae7/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4=
github.com/petermattis/goid v0.0.0-20250303134427-723919f7f203 h1:E7Kmf11E4K7B5hDti2K2NqPb1nlYlGYsu02S1JNd/Bs=
github.com/petermattis/goid v0.0.0-20250303134427-723919f7f203/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v1.21.0 h1:DIsaGmiaBkSangBgMtWdNfxbMNdku5IK6iNhrEqWvdA=
github.com/prometheus/client_golang v1.21.0/go.mod h1:U9NM32ykUErtVBxdvD3zfi+EuFkkaBvMb09mIfe0Zgg=
github.com/prometheus/client_golang v1.21.1 h1:DOvXXTqVzvkIewV/CDPFdejpMCGeMcbGCQ8YOmu+Ibk=
github.com/prometheus/client_golang v1.21.1/go.mod h1:U9NM32ykUErtVBxdvD3zfi+EuFkkaBvMb09mIfe0Zgg=
github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io=
github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I=
github.com/prometheus/common v0.63.0 h1:YR/EIY1o3mEFP/kZCD7iDMnLPlGyuU2Gb3HIcXnA98k=
github.com/prometheus/common v0.63.0/go.mod h1:VVFF/fBIoToEnWRVkYoXEkq3R3paCoxG9PXP74SnV18=
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
Expand All @@ -39,14 +34,8 @@ github.com/sasha-s/go-deadlock v0.3.5 h1:tNCOEEDG6tBqrNDOX35j/7hL5FcFViG6awUGROb
github.com/sasha-s/go-deadlock v0.3.5/go.mod h1:bugP6EGbdGYObIlx7pUZtWqlvo8k9H6vCBBsiChJQ5U=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/urfave/cli/v3 v3.0.0-beta1 h1:6DTaaUarcM0wX7qj5Hcvs+5Dm3dyUTBbEwIWAjcw9Zg=
github.com/urfave/cli/v3 v3.0.0-beta1/go.mod h1:FnIeEMYu+ko8zP1F9Ypr3xkZMIDqW3DR92yUtY39q1Y=
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
google.golang.org/protobuf v1.36.1 h1:yBPeRvTftaleIgM3PZ/WBIZ7XM/eEYAaEyCwvyjq/gk=
google.golang.org/protobuf v1.36.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM=
google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
Expand Down
157 changes: 82 additions & 75 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,16 @@ package main

import (
"context"
"flag"
"fmt"
"log/slog"
"os"
"os/signal"
"strconv"
"strings"
"syscall"
"time"

"github.com/urfave/cli/v3"
"github.com/utilitywarehouse/git-mirror/pkg/mirror"
)

Expand All @@ -25,27 +26,6 @@ var (
"warn": slog.LevelWarn,
"error": slog.LevelError,
}

flags = []cli.Flag{
&cli.StringFlag{
Name: "config",
Sources: cli.EnvVars("GIT_MIRROR_CONFIG"),
Value: "/etc/git-mirror/config.yaml",
Usage: "Absolute path to the config file.",
},
&cli.StringFlag{
Name: "log-level",
Sources: cli.EnvVars("LOG_LEVEL"),
Value: "info",
Usage: "Log level",
},
&cli.BoolFlag{
Name: "watch-config",
Usage: "watch config for changes and reload when changes encountered.\n" +
"Only changes related to add,remove repository or worktrees will be actioned.",
Value: true,
},
}
)

func init() {
Expand All @@ -55,62 +35,89 @@ func init() {
}))
}

func envString(key, fallback string) string {
value, ok := os.LookupEnv(key)
if ok {
return value
}
return fallback
}

func envBool(key string, fallback bool) bool {
value, ok := os.LookupEnv(key)
if ok {
parsed, err := strconv.ParseBool(value)
if err == nil {
return parsed
}
return fallback
}
return fallback
}

func usage() {
fmt.Fprintf(os.Stderr, "NAME:\n")
fmt.Fprintf(os.Stderr, "\tgit-mirror - git-mirror is a tool to periodically mirror remote repositories locally.\n")
fmt.Fprintf(os.Stderr, "\nUsage:\n")
fmt.Fprintf(os.Stderr, "\tgit-mirror [global options]\n")
fmt.Fprintf(os.Stderr, "\nGLOBAL OPTIONS:\n")
fmt.Fprintf(os.Stderr, "\t-log-level value (default: 'info') Log level [$LOG_LEVEL]\n")
fmt.Fprintf(os.Stderr, "\t-config value (default: '/etc/git-mirror/config.yaml') Absolute path to the config file. [$GIT_MIRROR_CONFIG]\n")
fmt.Fprintf(os.Stderr, "\t-watch-config value (default: true) watch config for changes and reload when changes encountered. [$GIT_MIRROR_WATCH_CONFIG]\n")

os.Exit(2)
}

func main() {
cmd := &cli.Command{
Name: "git-mirror",
Usage: "git-mirror is a tool to periodically mirror remote repositories locally.",
Flags: flags,
Action: func(ctx context.Context, c *cli.Command) error {
ctx, cancel := context.WithCancel(ctx)

// set log level according to argument
if v, ok := levelStrings[strings.ToLower(c.String("log-level"))]; ok {
loggerLevel.Set(v)
}

// path to resolve git
gitENV := []string{fmt.Sprintf("PATH=%s", os.Getenv("PATH"))}

// create empty repo pool which will be populated by watchConfig
repoPool, err := mirror.NewRepoPool(ctx, mirror.RepoPoolConfig{}, logger.With("logger", "git-mirror"), gitENV)
if err != nil {
logger.Error("could not create git mirror pool", "err", err)
os.Exit(1)
}

onConfigChange := func(config *mirror.RepoPoolConfig) {
ensureConfig(repoPool, config)
// start mirror Loop on newly added repos
repoPool.StartLoop()
}

// Start watching the config file
go WatchConfig(ctx, c.String("config"), 10*time.Second, onConfigChange)

//listenForShutdown
stop := make(chan os.Signal, 2)
signal.Notify(stop, os.Interrupt, syscall.SIGTERM)

<-stop

logger.Info("shutting down...")
cancel()

select {
case <-repoPool.Stopped:
logger.Info("all repositories mirror loop is stopped")
os.Exit(0)

case <-stop:
logger.Info("second signal received, terminating")
os.Exit(1)
}
return nil
},
ctx, cancel := context.WithCancel(context.Background())

flagLogLevel := flag.String("log-level", envString("LOG_LEVEL", "info"), "Log level")
flagConfig := flag.String("config", envString("GIT_MIRROR_CONFIG", "/etc/git-mirror/config.yaml"), "Absolute path to the config file")
flagWatchConfig := flag.Bool("watch-config", envBool("GIT_MIRROR_WATCH_CONFIG", true), "watch config for changes and reload when changes encountered")

flag.Usage = usage
flag.Parse()

// set log level according to argument
if v, ok := levelStrings[strings.ToLower(*flagLogLevel)]; ok {
loggerLevel.Set(v)
}

// path to resolve git
gitENV := []string{fmt.Sprintf("PATH=%s", os.Getenv("PATH"))}

// create empty repo pool which will be populated by watchConfig
repoPool, err := mirror.NewRepoPool(ctx, mirror.RepoPoolConfig{}, logger.With("logger", "git-mirror"), gitENV)
if err != nil {
logger.Error("could not create git mirror pool", "err", err)
os.Exit(1)
}

onConfigChange := func(config *mirror.RepoPoolConfig) {
ensureConfig(repoPool, config)
// start mirror Loop on newly added repos
repoPool.StartLoop()
}

if err := cmd.Run(context.Background(), os.Args); err != nil {
logger.Error("failed to run app", "err", err)
// Start watching the config file
go WatchConfig(ctx, *flagConfig, *flagWatchConfig, 10*time.Second, onConfigChange)

//listenForShutdown
stop := make(chan os.Signal, 2)
signal.Notify(stop, os.Interrupt, syscall.SIGTERM)

<-stop

logger.Info("shutting down...")
cancel()

select {
case <-repoPool.Stopped:
logger.Info("all repositories mirror loop is stopped")
os.Exit(0)

case <-stop:
logger.Info("second signal received, terminating")
os.Exit(1)
}
}
Loading