From 8f63ef04014b8217c1ad008939f72ce667c94e37 Mon Sep 17 00:00:00 2001
From: Ada <ada@gnous.eu>
Date: Thu, 4 Apr 2024 01:12:42 +0200
Subject: [PATCH] :tada: First commit

---
 .golangci.yaml                            | 25 ++++++++++++
 .woodpecker/lint.yaml                     |  6 +++
 .woodpecker/test.yaml                     |  5 +++
 README.adoc                               |  3 ++
 config.example.toml                       | 13 ++++++
 docs/config/README.adoc                   | 18 +++++++++
 go.mod                                    |  9 +++++
 go.sum                                    | 26 ++++++++++++
 internal/config/config.go                 | 14 +++++++
 internal/config/errors.go                 |  9 +++++
 internal/config/test_resources/valid.toml | 13 ++++++
 internal/config/toml.go                   | 46 ++++++++++++++++++++++
 internal/config/toml_test.go              | 48 +++++++++++++++++++++++
 internal/log/config.go                    |  6 +++
 internal/log/init.go                      | 33 ++++++++++++++++
 main.go                                   | 17 ++++++++
 16 files changed, 291 insertions(+)
 create mode 100644 .golangci.yaml
 create mode 100644 .woodpecker/lint.yaml
 create mode 100644 .woodpecker/test.yaml
 create mode 100644 README.adoc
 create mode 100644 config.example.toml
 create mode 100644 docs/config/README.adoc
 create mode 100644 go.mod
 create mode 100644 go.sum
 create mode 100644 internal/config/config.go
 create mode 100644 internal/config/errors.go
 create mode 100644 internal/config/test_resources/valid.toml
 create mode 100644 internal/config/toml.go
 create mode 100644 internal/config/toml_test.go
 create mode 100644 internal/log/config.go
 create mode 100644 internal/log/init.go
 create mode 100644 main.go

diff --git a/.golangci.yaml b/.golangci.yaml
new file mode 100644
index 0000000..99c947d
--- /dev/null
+++ b/.golangci.yaml
@@ -0,0 +1,25 @@
+linters:
+  enable-all: true
+  disable:
+    # Deprecated
+    - varcheck
+    - ifshort
+    - interfacer
+    - maligned
+    - deadcode
+    - scopelint
+    - golint
+    - structcheck
+    - exhaustivestruct
+    - nosnakecase
+    # To extremist/unusable
+    - depguard
+    - varnamelen
+    - exhaustruct
+    - wsl
+    - contextcheck
+    - wrapcheck
+linters-settings:
+  lll:
+    # Too short by default
+    line-length: 160 
\ No newline at end of file
diff --git a/.woodpecker/lint.yaml b/.woodpecker/lint.yaml
new file mode 100644
index 0000000..51e5a9e
--- /dev/null
+++ b/.woodpecker/lint.yaml
@@ -0,0 +1,6 @@
+steps:
+  lint:
+    image: golang:1.22
+    commands:
+      - go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest
+      - golangci-lint run
diff --git a/.woodpecker/test.yaml b/.woodpecker/test.yaml
new file mode 100644
index 0000000..7a4f5db
--- /dev/null
+++ b/.woodpecker/test.yaml
@@ -0,0 +1,5 @@
+steps:
+  test:
+    image: golang:1.22
+    commands:
+      - go test ./...
diff --git a/README.adoc b/README.adoc
new file mode 100644
index 0000000..c4d2253
--- /dev/null
+++ b/README.adoc
@@ -0,0 +1,3 @@
+= A light git repository mirror tools
+
+Yes, i have no cool name idea for this project
\ No newline at end of file
diff --git a/config.example.toml b/config.example.toml
new file mode 100644
index 0000000..436d4a3
--- /dev/null
+++ b/config.example.toml
@@ -0,0 +1,13 @@
+clonedirectory = "archive/"
+
+[log]
+level = "DEBUG"
+file = "" # Default in stderr
+
+[[RepoList]]
+URL = "https://github.com/torvalds/linux/"
+name = "linux"
+
+[[RepoList]]
+URL = "https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git"
+name = "linuxtwo"
\ No newline at end of file
diff --git a/docs/config/README.adoc b/docs/config/README.adoc
new file mode 100644
index 0000000..0ed3e4d
--- /dev/null
+++ b/docs/config/README.adoc
@@ -0,0 +1,18 @@
+= Configuration
+
+File format used is https://toml.io/en/[toml]
+
+== Options:
+* `clonedirectory`: Directory where mirror is clone
+* `log`: Log config section
+** `level`: Log level, allowed value is: "DEBUG", "INFO", "WARN", "ERROR", "FATAL"
+** `file`: Log file, default (empty) is in stderr
+** `RepoList`: List of mirrored git repository
+*** `URL`: Source URL
+*** `name`: directory name of clone
+
+== Example:
+[source,toml]
+----
+include::../../config.example.toml[]
+----
diff --git a/go.mod b/go.mod
new file mode 100644
index 0000000..0015a86
--- /dev/null
+++ b/go.mod
@@ -0,0 +1,9 @@
+module git.gnous.eu/ada/git-mirror
+
+go 1.22
+
+require (
+	github.com/pelletier/go-toml/v2 v2.2.0
+	github.com/sirupsen/logrus v1.9.3
+	golang.org/x/sys v0.18.0
+)
diff --git a/go.sum b/go.sum
new file mode 100644
index 0000000..d82dacc
--- /dev/null
+++ b/go.sum
@@ -0,0 +1,26 @@
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/pelletier/go-toml/v2 v2.2.0 h1:QLgLl2yMN7N+ruc31VynXs1vhMZa7CeHHejIeBAsoHo=
+github.com/pelletier/go-toml/v2 v2.2.0/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
+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/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
+github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
+github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
+github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
+github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
+github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
+github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
+github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
+golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
+golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/internal/config/config.go b/internal/config/config.go
new file mode 100644
index 0000000..911b5a8
--- /dev/null
+++ b/internal/config/config.go
@@ -0,0 +1,14 @@
+package config
+
+import "git.gnous.eu/ada/git-mirror/internal/log"
+
+type Config struct {
+	CloneDirectory string // Repository where gir-mirror keep repository
+	Log            log.Config
+	RepoList       []repoConfig
+}
+
+type repoConfig struct {
+	URL  string // Source url
+	Name string // Name of clone (directory name)
+}
diff --git a/internal/config/errors.go b/internal/config/errors.go
new file mode 100644
index 0000000..6289e9f
--- /dev/null
+++ b/internal/config/errors.go
@@ -0,0 +1,9 @@
+package config
+
+import "errors"
+
+var (
+	errLogLevel                 = errors.New("log level is invalid, valid list is : \"DEBUG\", \"INFO\", \"WARN\", \"ERROR\", \"FATAL\"")
+	errCloneDirectoryUnwritable = errors.New("clone directory is not writable")
+	errConfigFileNotReadable    = errors.New("config file is not loadable")
+)
diff --git a/internal/config/test_resources/valid.toml b/internal/config/test_resources/valid.toml
new file mode 100644
index 0000000..4cdabab
--- /dev/null
+++ b/internal/config/test_resources/valid.toml
@@ -0,0 +1,13 @@
+clonedirectory = "archive/"
+
+[log]
+level = "WARN"
+file = "log.txt"
+
+[[RepoList]]
+URL = "https://github.com/torvalds/linux/"
+name = "linux"
+
+[[RepoList]]
+URL = "https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git"
+name = "linuxtwo"
diff --git a/internal/config/toml.go b/internal/config/toml.go
new file mode 100644
index 0000000..4c69493
--- /dev/null
+++ b/internal/config/toml.go
@@ -0,0 +1,46 @@
+package config
+
+import (
+	"os"
+
+	"github.com/pelletier/go-toml/v2"
+	"golang.org/x/sys/unix"
+)
+
+func LoadToml(file string) (Config, error) {
+	var config Config
+
+	source, err := os.ReadFile(file)
+	if err != nil {
+		return config, errConfigFileNotReadable
+	}
+
+	err = toml.Unmarshal(source, &config)
+	if err != nil {
+		panic(err)
+	}
+
+	return config, nil
+}
+
+func VerifyConfig(config Config) error {
+	allowedValue := []string{"DEBUG", "INFO", "WARN", "ERROR", "FATAL"}
+	found := false
+	for _, v := range allowedValue {
+		if v == config.Log.Level {
+			found = true
+		}
+	}
+
+	if !found {
+		return errLogLevel
+	}
+
+	if unix.Access(config.CloneDirectory, unix.W_OK) != nil {
+		return errCloneDirectoryUnwritable
+	}
+
+	// TODO: verify RepoList not redundant
+
+	return nil
+}
diff --git a/internal/config/toml_test.go b/internal/config/toml_test.go
new file mode 100644
index 0000000..caa6c89
--- /dev/null
+++ b/internal/config/toml_test.go
@@ -0,0 +1,48 @@
+package config_test
+
+import (
+	"testing"
+
+	"git.gnous.eu/ada/git-mirror/internal/config"
+)
+
+func TestToml(t *testing.T) {
+	t.Parallel()
+	testLoadTomlValid(t)
+}
+
+func testLoadTomlValid(t *testing.T) {
+	t.Helper()
+	got, err := config.LoadToml("test_resources/valid.toml")
+	if err != nil {
+		t.Fatal("Cannot load config: ", err)
+	}
+	// {aa {WARN } [{aa yy} {bb yy}]}
+	if got.CloneDirectory != "archive/" {
+		t.Fatal("Invalid CloneDirectory: ", got.CloneDirectory)
+	}
+
+	if got.Log.Level != "WARN" {
+		t.Fatal("Invalid log level: ", got.Log.Level)
+	}
+
+	if got.Log.File != "log.txt" {
+		t.Fatal("Invalid log file: ", got.Log.File)
+	}
+
+	if got.RepoList[0].Name != "linux" {
+		t.Fatal("Invalid first repo name: ", got.RepoList[0].Name)
+	}
+
+	if got.RepoList[0].URL != "https://github.com/torvalds/linux/" {
+		t.Fatal("Invalid first repo url: ", got.RepoList[0].URL)
+	}
+
+	if got.RepoList[1].Name != "linuxtwo" {
+		t.Fatal("Invalid second repo name: ", got.RepoList[1].Name)
+	}
+
+	if got.RepoList[1].URL != "https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git" {
+		t.Fatal("Invalid second repo URL: ", got.RepoList[1].URL)
+	}
+}
diff --git a/internal/log/config.go b/internal/log/config.go
new file mode 100644
index 0000000..0f09914
--- /dev/null
+++ b/internal/log/config.go
@@ -0,0 +1,6 @@
+package log
+
+type Config struct {
+	Level string
+	File  string // Output file for log, default stderr
+}
diff --git a/internal/log/init.go b/internal/log/init.go
new file mode 100644
index 0000000..a8b123a
--- /dev/null
+++ b/internal/log/init.go
@@ -0,0 +1,33 @@
+package log
+
+import (
+	"os"
+
+	"github.com/sirupsen/logrus"
+)
+
+func (config Config) Init() {
+	if config.File != "" {
+		file, err := os.OpenFile(config.File, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0o640) //nolint:gomnd
+		if err != nil {
+			logrus.Fatal("Cannot open log file: ", err)
+		}
+		logrus.SetOutput(file)
+	}
+
+	if config.Level == "DEBUG" {
+		logrus.SetLevel(logrus.DebugLevel)
+	}
+	if config.Level == "INFO" {
+		logrus.SetLevel(logrus.InfoLevel)
+	}
+	if config.Level == "WARN" {
+		logrus.SetLevel(logrus.WarnLevel)
+	}
+	if config.Level == "ERROR" {
+		logrus.SetLevel(logrus.ErrorLevel)
+	}
+	if config.Level == "FATAL" {
+		logrus.SetLevel(logrus.ErrorLevel)
+	}
+}
diff --git a/main.go b/main.go
new file mode 100644
index 0000000..19dba9d
--- /dev/null
+++ b/main.go
@@ -0,0 +1,17 @@
+package main
+
+import (
+	"git.gnous.eu/ada/git-mirror/internal/config"
+	"github.com/sirupsen/logrus"
+)
+
+func main() {
+	initConfig, err := config.LoadToml("config.example.toml")
+	if err != nil {
+		logrus.Fatal(err)
+	}
+
+	initConfig.Log.Init()
+	logrus.Info("Config loaded")
+	logrus.Debug("Config: ", initConfig)
+}