Restructure project directories

Follow the standard of all go projects
This commit is contained in:
Mikołaj Pęczkowski 2024-01-13 15:39:40 +01:00
parent 741f18efd1
commit 5445ce1ccf
20 changed files with 39 additions and 42 deletions

View file

@ -0,0 +1,15 @@
package commands
import (
"gitlab.com/revalus/grm/internal/config"
)
type Command interface {
Command(repoCfg config.RepositoryConfig) CommandStatus
}
type CommandStatus struct {
Name string
Changed bool
Message string
Error bool
}

View file

@ -0,0 +1,138 @@
package commands
import (
"fmt"
"gitlab.com/revalus/grm/internal/config"
"os"
"github.com/go-git/go-billy/v5"
"github.com/go-git/go-billy/v5/osfs"
"github.com/go-git/go-git/v5"
gitcfg "github.com/go-git/go-git/v5/config"
"github.com/go-git/go-git/v5/plumbing/cache"
"github.com/go-git/go-git/v5/storage/filesystem"
)
type TestSetup struct {
rootFS billy.Filesystem
baseRepository struct {
fileSystem billy.Filesystem
repo *git.Repository
}
}
func checkErrorDuringPreparation(err error) {
if err != nil {
fmt.Printf("Cannot prepare a temporary directory for testing! %v ", err.Error())
os.Exit(2)
}
}
func createTmpDir() string {
baseForTMPDir := fmt.Sprintf("%v/grmTest", os.TempDir())
if _, ok := os.Stat(baseForTMPDir); ok != nil {
err := os.Mkdir(baseForTMPDir, 0777)
checkErrorDuringPreparation(err)
}
tempDir, err := os.MkdirTemp(baseForTMPDir, "*")
checkErrorDuringPreparation(err)
return tempDir
}
func getTestSetup() TestSetup {
tmpDir := createTmpDir()
baseFileSystem := osfs.New(tmpDir)
initRepositoryFileSystem, err := baseFileSystem.Chroot("worktree")
checkErrorDuringPreparation(err)
directoryForGitMetadata, err := initRepositoryFileSystem.Chroot(".git")
checkErrorDuringPreparation(err)
repository, err := git.Init(filesystem.NewStorage(directoryForGitMetadata, cache.NewObjectLRUDefault()), initRepositoryFileSystem)
checkErrorDuringPreparation(err)
fileForFirstCommit, err := initRepositoryFileSystem.Create("TestFile.txt")
checkErrorDuringPreparation(err)
_, err = fileForFirstCommit.Write([]byte("foo-conent"))
checkErrorDuringPreparation(err)
repositoryWorkTree, err := repository.Worktree()
checkErrorDuringPreparation(err)
repositoryWorkTree.Add(fileForFirstCommit.Name())
_, err = repositoryWorkTree.Commit("First commit", &git.CommitOptions{})
checkErrorDuringPreparation(err)
return TestSetup{
baseRepository: struct {
fileSystem billy.Filesystem
repo *git.Repository
}{
fileSystem: initRepositoryFileSystem,
repo: repository,
},
rootFS: baseFileSystem,
}
}
func makeCommit(wk *git.Worktree, commitMessage string) {
_, err := wk.Commit(commitMessage, &git.CommitOptions{})
checkErrorDuringPreparation(err)
}
func getFSForLocalRepo(dirName string, baseFileSystem billy.Filesystem) (billy.Filesystem, *filesystem.Storage) {
fsForLocalRepo, err := baseFileSystem.Chroot(dirName)
checkErrorDuringPreparation(err)
fsForMetadata, err := fsForLocalRepo.Chroot(".git")
checkErrorDuringPreparation(err)
storageForTestRepo := filesystem.NewStorage(fsForMetadata, cache.NewObjectLRUDefault())
return fsForLocalRepo, storageForTestRepo
}
func getBaseForTestingSyncCommand() (StatusChecker, *git.Repository, config.RepositoryConfig, TestSetup) {
tmpDirWithInitialRepository := getTestSetup()
dirNameForLocalRepository := "testRepo"
fsForLocalRepo, storageForTestRepo := getFSForLocalRepo(dirNameForLocalRepository, tmpDirWithInitialRepository.rootFS)
fakeLocalRepository, err := git.Clone(storageForTestRepo, fsForLocalRepo, &git.CloneOptions{
URL: tmpDirWithInitialRepository.baseRepository.fileSystem.Root(),
})
checkErrorDuringPreparation(err)
sc := StatusChecker{
workspace: tmpDirWithInitialRepository.rootFS.Root(),
}
repoCfg := config.RepositoryConfig{
Name: "test",
Src: tmpDirWithInitialRepository.baseRepository.fileSystem.Root(),
Dest: dirNameForLocalRepository,
}
return sc, fakeLocalRepository, repoCfg, tmpDirWithInitialRepository
}
func getBaseForTestingSyncMultipleRemote() (StatusChecker, *git.Repository, config.RepositoryConfig) {
sc, fakeLocalRepository, repoCfg, tmpDirWithInitialRepository := getBaseForTestingSyncCommand()
fakeLocalRepository.CreateRemote(&gitcfg.RemoteConfig{
Name: "subremote",
URLs: []string{tmpDirWithInitialRepository.baseRepository.fileSystem.Root()},
})
fakeLocalRepository.Fetch(&git.FetchOptions{
RemoteName: "subremote",
})
return sc, fakeLocalRepository, repoCfg
}

View file

@ -0,0 +1,160 @@
package commands
import (
"fmt"
"gitlab.com/revalus/grm/internal/config"
"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/plumbing"
"github.com/go-git/go-git/v5/plumbing/object"
)
type StatusChecker struct {
workspace string
}
func NewStatusChecker(workspace string) StatusChecker {
return StatusChecker{
workspace: workspace,
}
}
func findNumberOfCommitDiffs(srcCommit *object.Commit, dstCommit *object.Commit) int {
getFiveElementsFromHashes := func(commit *object.Commit, hashedSlice *[]string) *object.Commit {
var err error
for iter := 0; iter <= 5; iter++ {
*hashedSlice = append(*hashedSlice, commit.Hash.String())
commit, err = commit.Parents().Next()
if err != nil {
return nil
}
}
return commit
}
getRangeDiff := func(listFist, listSecond []string) (int, bool) {
diffRange := 0
for _, itemFirst := range listFist {
for _, itemSecond := range listSecond {
if itemFirst == itemSecond {
return diffRange, true
}
}
diffRange++
}
return diffRange, false
}
baseCommitHashes := []string{}
destCommitHashes := []string{}
for {
if srcCommit != nil {
srcCommit = getFiveElementsFromHashes(srcCommit, &baseCommitHashes)
}
if dstCommit != nil {
dstCommit = getFiveElementsFromHashes(dstCommit, &destCommitHashes)
}
diff, finished := getRangeDiff(baseCommitHashes, destCommitHashes)
if finished {
return diff
}
}
}
func (sc StatusChecker) Command(repoCfg config.RepositoryConfig) CommandStatus {
cmdStatus := CommandStatus{
Name: repoCfg.Name,
Changed: false,
Message: "",
Error: false,
}
destPath := fmt.Sprintf("%v/%v", sc.workspace, repoCfg.Dest)
repo, err := git.PlainOpen(destPath)
if err != nil {
cmdStatus.Error = true
cmdStatus.Message = err.Error()
return cmdStatus
}
headReference, err := repo.Head()
if err != nil {
cmdStatus.Error = true
cmdStatus.Message = err.Error()
return cmdStatus
}
remotes, err := repo.Remotes()
if err != nil || len(remotes) == 0 {
cmdStatus.Error = true
cmdStatus.Message = "cannot find remote branches"
return cmdStatus
}
currentBranchCommit, err := repo.CommitObject(headReference.Hash())
if err != nil {
cmdStatus.Error = true
cmdStatus.Message = err.Error()
return cmdStatus
}
type remoteStatus struct {
ahead int
behind int
err error
}
remotesStatus := make(map[string]remoteStatus)
for _, remote := range remotes {
remoteName := remote.Config().Name
remoteRevision, err := repo.ResolveRevision(plumbing.Revision(fmt.Sprintf("%v/%v", remoteName, headReference.Name().Short())))
if err != nil {
remotesStatus[remoteName] = remoteStatus{
err: err,
}
continue
}
remoteBranchCommit, err := repo.CommitObject(*remoteRevision)
if err != nil {
remotesStatus[remoteName] = remoteStatus{
err: err,
}
continue
}
status := remoteStatus{
ahead: findNumberOfCommitDiffs(currentBranchCommit, remoteBranchCommit),
behind: findNumberOfCommitDiffs(remoteBranchCommit, currentBranchCommit),
}
if status.ahead > 0 || status.behind > 0 {
cmdStatus.Changed = true
}
remotesStatus[remoteName] = status
}
cmdStatus.Message = fmt.Sprintf("branch %v", headReference.Name().Short())
for remoteName, status := range remotesStatus {
if status.err != nil {
cmdStatus.Message = fmt.Sprintf("%v - ( | %v | problem: %v )", cmdStatus.Message, remoteName, status.err.Error())
continue
}
cmdStatus.Message = fmt.Sprintf("%v - ( | %v | \u2191%v \u2193%v )", cmdStatus.Message, remoteName, status.ahead, status.behind)
}
return cmdStatus
}

View file

@ -0,0 +1,250 @@
package commands
import (
"fmt"
"gitlab.com/revalus/grm/internal/config"
"testing"
"github.com/go-git/go-billy/v5/memfs"
"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/plumbing"
"github.com/go-git/go-git/v5/storage/memory"
)
func TestIfBranchesAreEqual(t *testing.T) {
tmpDirWithInitialRepository := getTestSetup()
fakeLocalRepo, err := git.Clone(memory.NewStorage(), memfs.New(), &git.CloneOptions{
URL: tmpDirWithInitialRepository.baseRepository.fileSystem.Root(),
})
checkErrorDuringPreparation(err)
currentReference, err := fakeLocalRepo.Head()
checkErrorDuringPreparation(err)
remote, err := fakeLocalRepo.Remote("origin")
checkErrorDuringPreparation(err)
remoteRevision, err := fakeLocalRepo.ResolveRevision(plumbing.Revision(fmt.Sprintf("%v/%v", remote.Config().Name, currentReference.Name().Short())))
checkErrorDuringPreparation(err)
currentBranchCommit, err := fakeLocalRepo.CommitObject(currentReference.Hash())
checkErrorDuringPreparation(err)
remoteBranchCommit, err := fakeLocalRepo.CommitObject(*remoteRevision)
checkErrorDuringPreparation(err)
result := findNumberOfCommitDiffs(currentBranchCommit, remoteBranchCommit)
if result != 0 {
t.Errorf("Expected to get 0 changes, instead of this got %v", result)
}
}
func TestIfCurrentBranchIsDifferent(t *testing.T) {
tmpDirWithInitialRepository := getTestSetup()
fakeLocalRepo, err := git.Clone(memory.NewStorage(), memfs.New(), &git.CloneOptions{
URL: tmpDirWithInitialRepository.baseRepository.fileSystem.Root(),
})
checkErrorDuringPreparation(err)
localWorktree, err := fakeLocalRepo.Worktree()
checkErrorDuringPreparation(err)
makeCommit(localWorktree, "commit 1")
localReference, err := fakeLocalRepo.Head()
checkErrorDuringPreparation(err)
remoteConnection, err := fakeLocalRepo.Remote("origin")
checkErrorDuringPreparation(err)
remoteRevision, err := fakeLocalRepo.ResolveRevision(plumbing.Revision(fmt.Sprintf("%v/%v", remoteConnection.Config().Name, localReference.Name().Short())))
checkErrorDuringPreparation(err)
currentBranchCommit, err := fakeLocalRepo.CommitObject(localReference.Hash())
checkErrorDuringPreparation(err)
remoteBranchCommit, err := fakeLocalRepo.CommitObject(*remoteRevision)
checkErrorDuringPreparation(err)
result := findNumberOfCommitDiffs(currentBranchCommit, remoteBranchCommit)
if result != 1 {
t.Errorf("Expected to get 1 changes, instead of this got %v", result)
}
for i := 1; i < 15; i++ {
makeCommit(localWorktree, fmt.Sprintf("Commit +%v", i))
}
localReference, err = fakeLocalRepo.Head()
checkErrorDuringPreparation(err)
currentBranchCommit, err = fakeLocalRepo.CommitObject(localReference.Hash())
checkErrorDuringPreparation(err)
result = findNumberOfCommitDiffs(currentBranchCommit, remoteBranchCommit)
if result != 15 {
t.Errorf("Expected to get 5 changes, instead of this got %v", result)
}
}
func TestCommandRepositoryDoesNotExists(t *testing.T) {
tmpDirWithInitialRepository := getTestSetup()
fsForLocalRepo, storageForTestRepo := getFSForLocalRepo("noMatterValue", tmpDirWithInitialRepository.rootFS)
_, err := git.Clone(storageForTestRepo, fsForLocalRepo, &git.CloneOptions{
URL: tmpDirWithInitialRepository.baseRepository.fileSystem.Root(),
})
checkErrorDuringPreparation(err)
sc := StatusChecker{
workspace: tmpDirWithInitialRepository.rootFS.Root(),
}
repoCfg := config.RepositoryConfig{
Name: "test",
Src: tmpDirWithInitialRepository.baseRepository.fileSystem.Root(),
Dest: tmpDirWithInitialRepository.rootFS.Root(),
}
repoStatus := sc.Command(repoCfg)
expectedMessage := "repository does not exist"
if !repoStatus.Error {
t.Errorf("Expected error")
}
if repoStatus.Changed {
t.Errorf("Unexpected change value")
}
if repoStatus.Message != expectedMessage {
t.Errorf("Expected to get \"%v\", instead of this got \"%v\"", expectedMessage, repoStatus.Message)
}
}
func TestCommandRepositoryNoRemoteBranch(t *testing.T) {
tmpDirWithInitialRepository := getTestSetup()
dirNameForLocalRepository := "testRepo"
fsForLocalRepo, storageForTestRepo := getFSForLocalRepo(dirNameForLocalRepository, tmpDirWithInitialRepository.rootFS)
fakeLocalRepository, err := git.Clone(storageForTestRepo, fsForLocalRepo, &git.CloneOptions{
URL: tmpDirWithInitialRepository.baseRepository.fileSystem.Root(),
})
checkErrorDuringPreparation(err)
err = fakeLocalRepository.DeleteRemote("origin")
checkErrorDuringPreparation(err)
sc := StatusChecker{
workspace: tmpDirWithInitialRepository.rootFS.Root(),
}
repoCfg := config.RepositoryConfig{
Name: "test",
Src: tmpDirWithInitialRepository.baseRepository.fileSystem.Root(),
Dest: dirNameForLocalRepository,
}
repoStatus := sc.Command(repoCfg)
expectedMessage := "cannot find remote branches"
if !repoStatus.Error {
t.Errorf("Expected error")
}
if repoStatus.Changed {
t.Errorf("Unexpected change value")
}
if repoStatus.Message != expectedMessage {
t.Errorf("Expected to get \"%v\", instead of this got \"%v\"", expectedMessage, repoStatus.Message)
}
}
func TestCommandAllCorrectWithoutChanges(t *testing.T) {
sc, _, repoCfg, _ := getBaseForTestingSyncCommand()
repoStatus := sc.Command(repoCfg)
expectedMessage := "branch master - ( | origin | \u21910 \u21930 )"
if repoStatus.Error {
t.Errorf("Unexpected error")
t.Errorf("Message %v", repoStatus.Message)
}
if repoStatus.Changed {
t.Errorf("Expeected that changed will be true")
}
if repoStatus.Message != expectedMessage {
t.Errorf("Expected to get \"%v\", instead of this got \"%v\"", expectedMessage, repoStatus.Message)
}
}
func TestCommandAllCorrectWithOneChange(t *testing.T) {
sc, fakeLocalRepository, repoCfg, _ := getBaseForTestingSyncCommand()
fakeLocalWorkTree, err := fakeLocalRepository.Worktree()
checkErrorDuringPreparation(err)
makeCommit(fakeLocalWorkTree, "commit 1")
repoStatus := sc.Command(repoCfg)
expectedMessage := "branch master - ( | origin | \u21911 \u21930 )"
if repoStatus.Message != expectedMessage {
t.Errorf("Expected to get \"%v\", instead of this got \"%v\"", expectedMessage, repoStatus.Message)
}
if repoStatus.Error {
t.Errorf("Unexpected error")
t.Errorf("Message %v", repoStatus.Message)
}
if !repoStatus.Changed {
t.Errorf("Expeected that changed will be true")
}
if repoStatus.Message != expectedMessage {
t.Errorf("Expected to get \"%v\", instead of this got \"%v\"", expectedMessage, repoStatus.Message)
}
}
func TestCommandMultiRemoteNoChanges(t *testing.T) {
sc, _, repoCfg := getBaseForTestingSyncMultipleRemote()
repoStatus := sc.Command(repoCfg)
expectedMessage := "branch master - ( | origin | \u21910 \u21930 ) - ( | subremote | \u21910 \u21930 )"
if repoStatus.Error {
t.Errorf("Unexpected error")
t.Errorf("Message %v", repoStatus.Message)
}
if repoStatus.Changed {
t.Errorf("Expeected that changed will be true")
}
if repoStatus.Message != expectedMessage {
t.Errorf("Expected to get \"%v\", instead of this got \"%v\"", expectedMessage, repoStatus.Message)
}
}
func TestCommandMultiRemoteWithOneChange(t *testing.T) {
sc, fakeLocalRepository, repoCfg := getBaseForTestingSyncMultipleRemote()
fakeLocalWorkTree, err := fakeLocalRepository.Worktree()
checkErrorDuringPreparation(err)
makeCommit(fakeLocalWorkTree, "commit 1")
checkErrorDuringPreparation(err)
repoStatus := sc.Command(repoCfg)
expectedMessage := "branch master - ( | origin | \u21911 \u21930 ) - ( | subremote | \u21911 \u21930 )"
if repoStatus.Error {
t.Errorf("Unexpected error")
t.Errorf("Message %v", repoStatus.Message)
}
if !repoStatus.Changed {
t.Errorf("Expeected that changed will be true")
}
if repoStatus.Message != expectedMessage {
t.Errorf("Expected to get \"%v\", instead of this got \"%v\"", expectedMessage, repoStatus.Message)
}
}

View file

@ -0,0 +1,88 @@
package commands
import (
"fmt"
"gitlab.com/revalus/grm/internal/config"
"github.com/go-git/go-git/v5"
)
type Synchronizer struct {
workspace string
}
func NewSynchronizer(workspace string) Synchronizer {
return Synchronizer{
workspace: workspace,
}
}
const (
syncUpToDate = "up to date"
syncFetched = "has been fetched" // Why fetched, instead of updated? To be consistent with git commands :D
syncCloned = "has been cloned"
)
func fetchRepository(repo *git.Repository) (bool, error) {
err := repo.Fetch(&git.FetchOptions{})
if err == git.NoErrAlreadyUpToDate {
return false, nil
}
if err != nil && err != git.NoErrAlreadyUpToDate {
return false, err
}
return true, nil
}
func cloneRepository(destPath string, repoCfg *config.RepositoryConfig) (bool, error) {
_, err := git.PlainClone(destPath, false, &git.CloneOptions{
URL: repoCfg.Src,
})
if err != nil {
return false, err
}
return true, nil
}
func (s Synchronizer) Command(repoCfg config.RepositoryConfig) CommandStatus {
var err error
cmdStatus := CommandStatus{
Name: repoCfg.Name,
Changed: false,
Message: "",
Error: false,
}
destPath := fmt.Sprintf("%v/%v", s.workspace, repoCfg.Dest)
repo, err := git.PlainOpen(destPath)
if err != nil && err == git.ErrRepositoryNotExists {
cmdStatus.Changed, err = cloneRepository(destPath, &repoCfg)
cmdStatus.Message = syncCloned
} else if err == nil {
cmdStatus.Changed, err = fetchRepository(repo)
if cmdStatus.Changed {
cmdStatus.Message = syncFetched
} else {
cmdStatus.Message = syncUpToDate
}
} else {
cmdStatus.Error = true
cmdStatus.Message = err.Error()
return cmdStatus
}
if err != nil {
cmdStatus.Error = true
cmdStatus.Message = err.Error()
}
return cmdStatus
}

View file

@ -0,0 +1,63 @@
package commands
import (
"fmt"
"gitlab.com/revalus/grm/internal/config"
"os"
"testing"
)
func TestSyncInit(t *testing.T) {
sync := NewSynchronizer("test")
if sync.workspace != "test" {
t.Errorf("Expected to get \"test\", instead of this got %v", sync.workspace)
}
}
func TestSyncCommand(t *testing.T) {
testSetup := getTestSetup()
sync := Synchronizer{
workspace: testSetup.rootFS.Root(),
}
cfg := config.RepositoryConfig{
Src: fmt.Sprintf("file://%v", testSetup.baseRepository.fileSystem.Root()),
Dest: "awesome-go",
}
cloneStatus := sync.Command(cfg)
if cloneStatus.Error {
t.Errorf("Unexpected error: %v", cloneStatus.Message)
}
info, err := os.Stat(fmt.Sprintf("%v/awesome-go/.git", testSetup.rootFS.Root()))
if err != nil {
t.Errorf("Unexpected error: %v", err.Error())
}
if !info.IsDir() {
t.Errorf("Expected that the selected path is dir")
}
if cloneStatus.Changed != true {
t.Errorf("Expected that the status is changed")
}
if cloneStatus.Message != syncCloned {
t.Errorf("Expected to get %v, instead of this got %v", syncCloned, cloneStatus.Message)
}
fetchStatus := sync.Command(cfg)
if fetchStatus.Error {
t.Errorf("Unexpected error: %v", err.Error())
}
if fetchStatus.Changed != false {
t.Errorf("Expected that the status is not changed")
}
if fetchStatus.Message != syncUpToDate {
t.Errorf("Expected to get %v, instead of this got %v", syncUpToDate, cloneStatus.Message)
}
}