package commands import ( "fmt" "github.com/go-git/go-git/v5" "github.com/go-git/go-git/v5/plumbing" "github.com/go-git/go-git/v5/plumbing/object" "gitlab.com/revalus/grm/internal/config" "path" "sort" ) type StatusChecker struct { workspace string } func NewStatusChecker(workspace string) StatusChecker { return StatusChecker{ workspace: workspace, } } func findNumberOfCommitDiffs(srcCommit *object.Commit, dstCommit *object.Commit) int { // This function is a helper function to get only five latest items, based on given commit 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 } // Compare diff between sources by hash list (the same hash list must be present to assume the end of changes) 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 } var baseCommitHashes []string var destCommitHashes []string // Try to find all differences, limit only to five last changes to avoid reading whole repository at once 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, } repositoryPath := path.Join(sc.workspace, repoCfg.Dest) repo, err := git.PlainOpen(repositoryPath) 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 } var remoteNames []string remoteStatues := 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 { remoteStatues[remoteName] = remoteStatus{ err: err, } continue } remoteBranchCommit, err := repo.CommitObject(*remoteRevision) if err != nil { remoteStatues[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 } remoteNames = append(remoteNames, remoteName) remoteStatues[remoteName] = status } sort.Strings(remoteNames) cmdStatus.Message = fmt.Sprintf("branch %v", headReference.Name().Short()) for _, remoteName := range remoteNames { status := remoteStatues[remoteName] 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 }