mirror of
https://github.com/gabrie30/ghorg.git
synced 2025-08-06 06:17:09 +02:00
Empty repos should not fail on pull (#513)
This commit is contained in:
parent
4500043663
commit
a1395a2a6f
53
cmd/clone.go
53
cmd/clone.go
@ -852,9 +852,23 @@ func CloneAllRepos(git git.Gitter, cloneTargets []scm.Repo) {
|
|||||||
// Retry checkout
|
// Retry checkout
|
||||||
errRetry := git.Checkout(repo)
|
errRetry := git.Checkout(repo)
|
||||||
if errRetry != nil {
|
if errRetry != nil {
|
||||||
e := fmt.Sprintf("Could not checkout out %s, branch may not exist or may not have any contents/commits, no changes made on: %s Error: %v", repo.CloneBranch, repo.URL, errRetry)
|
hasRemoteHeads, errHasRemoteHeads := git.HasRemoteHeads(repo)
|
||||||
cloneErrors = append(cloneErrors, e)
|
if errHasRemoteHeads != nil {
|
||||||
return
|
e := fmt.Sprintf("Could not checkout %s, branch may not exist or may not have any contents/commits, no changes made on: %s Errors: %v %v", repo.CloneBranch, repo.URL, errRetry, errHasRemoteHeads)
|
||||||
|
cloneErrors = append(cloneErrors, e)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if hasRemoteHeads {
|
||||||
|
// weird, should not happen, return original checkout error
|
||||||
|
e := fmt.Sprintf("Could not checkout %s, branch may not exist or may not have any contents/commits, no changes made on: %s Error: %v", repo.CloneBranch, repo.URL, errRetry)
|
||||||
|
cloneErrors = append(cloneErrors, e)
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
// this is _just_ an empty repository
|
||||||
|
e := fmt.Sprintf("Could not checkout %s due to repository being empty, no changes made on: %s", repo.CloneBranch, repo.URL)
|
||||||
|
cloneInfos = append(cloneInfos, e)
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1032,24 +1046,29 @@ func CloneAllRepos(git git.Gitter, cloneTargets []scm.Repo) {
|
|||||||
writeGhorgStats(date, allReposToCloneCount, cloneCount, pulledCount, cloneInfosCount, cloneErrorsCount, updateRemoteCount, newCommits, pruneCount, hasCollisions)
|
writeGhorgStats(date, allReposToCloneCount, cloneCount, pulledCount, cloneInfosCount, cloneErrorsCount, updateRemoteCount, newCommits, pruneCount, hasCollisions)
|
||||||
}
|
}
|
||||||
|
|
||||||
if os.Getenv("GHORG_EXIT_CODE_ON_CLONE_INFOS") != "0" && cloneInfosCount > 0 {
|
if os.Getenv("GHORG_DONT_EXIT_UNDER_TEST") != "true" {
|
||||||
exitCode, err := strconv.Atoi(os.Getenv("GHORG_EXIT_CODE_ON_CLONE_INFOS"))
|
if os.Getenv("GHORG_EXIT_CODE_ON_CLONE_INFOS") != "0" && cloneInfosCount > 0 {
|
||||||
if err != nil {
|
exitCode, err := strconv.Atoi(os.Getenv("GHORG_EXIT_CODE_ON_CLONE_INFOS"))
|
||||||
colorlog.PrintError("Could not convert GHORG_EXIT_CODE_ON_CLONE_INFOS from string to integer")
|
if err != nil {
|
||||||
os.Exit(1)
|
colorlog.PrintError("Could not convert GHORG_EXIT_CODE_ON_CLONE_INFOS from string to integer")
|
||||||
}
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
os.Exit(exitCode)
|
os.Exit(exitCode)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if cloneErrorsCount > 0 {
|
if os.Getenv("GHORG_DONT_EXIT_UNDER_TEST") != "true" {
|
||||||
exitCode, err := strconv.Atoi(os.Getenv("GHORG_EXIT_CODE_ON_CLONE_ISSUES"))
|
if cloneErrorsCount > 0 {
|
||||||
if err != nil {
|
exitCode, err := strconv.Atoi(os.Getenv("GHORG_EXIT_CODE_ON_CLONE_ISSUES"))
|
||||||
colorlog.PrintError("Could not convert GHORG_EXIT_CODE_ON_CLONE_ISSUES from string to integer")
|
if err != nil {
|
||||||
os.Exit(1)
|
colorlog.PrintError("Could not convert GHORG_EXIT_CODE_ON_CLONE_ISSUES from string to integer")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
os.Exit(exitCode)
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
os.Exit(exitCode)
|
cloneErrorsCount = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"reflect"
|
"reflect"
|
||||||
@ -13,6 +14,7 @@ import (
|
|||||||
func TestShouldLowerRegularString(t *testing.T) {
|
func TestShouldLowerRegularString(t *testing.T) {
|
||||||
|
|
||||||
upperName := "RepoName"
|
upperName := "RepoName"
|
||||||
|
defer setOutputDirName([]string{""})
|
||||||
setOutputDirName([]string{upperName})
|
setOutputDirName([]string{upperName})
|
||||||
|
|
||||||
if outputDirName != "reponame" {
|
if outputDirName != "reponame" {
|
||||||
@ -23,6 +25,7 @@ func TestShouldLowerRegularString(t *testing.T) {
|
|||||||
func TestShouldNotChangeLowerCasedRegularString(t *testing.T) {
|
func TestShouldNotChangeLowerCasedRegularString(t *testing.T) {
|
||||||
|
|
||||||
lowerName := "repo_name"
|
lowerName := "repo_name"
|
||||||
|
defer setOutputDirName([]string{""})
|
||||||
setOutputDirName([]string{lowerName})
|
setOutputDirName([]string{lowerName})
|
||||||
|
|
||||||
if outputDirName != "repo_name" {
|
if outputDirName != "repo_name" {
|
||||||
@ -34,6 +37,7 @@ func TestReplaceDashWithUnderscore(t *testing.T) {
|
|||||||
|
|
||||||
want := "repo-name"
|
want := "repo-name"
|
||||||
lowerName := "repo-name"
|
lowerName := "repo-name"
|
||||||
|
defer setOutputDirName([]string{""})
|
||||||
setOutputDirName([]string{lowerName})
|
setOutputDirName([]string{lowerName})
|
||||||
|
|
||||||
if outputDirName != want {
|
if outputDirName != want {
|
||||||
@ -44,6 +48,7 @@ func TestReplaceDashWithUnderscore(t *testing.T) {
|
|||||||
func TestShouldNotChangeNonLettersString(t *testing.T) {
|
func TestShouldNotChangeNonLettersString(t *testing.T) {
|
||||||
|
|
||||||
numberName := "1234567_8"
|
numberName := "1234567_8"
|
||||||
|
defer setOutputDirName([]string{""})
|
||||||
setOutputDirName([]string{numberName})
|
setOutputDirName([]string{numberName})
|
||||||
|
|
||||||
if outputDirName != "1234567_8" {
|
if outputDirName != "1234567_8" {
|
||||||
@ -57,6 +62,13 @@ func NewMockGit() MockGitClient {
|
|||||||
return MockGitClient{}
|
return MockGitClient{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (g MockGitClient) HasRemoteHeads(repo scm.Repo) (bool, error) {
|
||||||
|
if repo.Name == "testRepoEmpty" {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (g MockGitClient) Clone(repo scm.Repo) error {
|
func (g MockGitClient) Clone(repo scm.Repo) error {
|
||||||
_, err := os.MkdirTemp(os.Getenv("GHORG_ABSOLUTE_PATH_TO_CLONE_TO"), repo.Name)
|
_, err := os.MkdirTemp(os.Getenv("GHORG_ABSOLUTE_PATH_TO_CLONE_TO"), repo.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -74,6 +86,9 @@ func (g MockGitClient) SetOriginWithCredentials(repo scm.Repo) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (g MockGitClient) Checkout(repo scm.Repo) error {
|
func (g MockGitClient) Checkout(repo scm.Repo) error {
|
||||||
|
if repo.Name == "testRepoEmpty" {
|
||||||
|
return errors.New("Cannot checkout any specific branch in an empty repository")
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -144,6 +159,49 @@ func TestInitialClone(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCloneEmptyRepo(t *testing.T) {
|
||||||
|
defer UnsetEnv("GHORG_")()
|
||||||
|
dir, err := os.MkdirTemp("", "ghorg_test_empty_repo")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(dir)
|
||||||
|
|
||||||
|
os.Setenv("GHORG_ABSOLUTE_PATH_TO_CLONE_TO", dir)
|
||||||
|
setOuputDirAbsolutePath()
|
||||||
|
|
||||||
|
os.Setenv("GHORG_DONT_EXIT_UNDER_TEST", "true")
|
||||||
|
|
||||||
|
// simulate a previous clone of empty git repository
|
||||||
|
repoErr := os.Mkdir(outputDirAbsolutePath+"/"+"testRepoEmpty", 0o700)
|
||||||
|
if repoErr != nil {
|
||||||
|
log.Fatal(repoErr)
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(outputDirAbsolutePath + "/" + "testRepoEmpty")
|
||||||
|
|
||||||
|
os.Setenv("GHORG_CONCURRENCY", "1")
|
||||||
|
var testRepos = []scm.Repo{
|
||||||
|
{
|
||||||
|
Name: "testRepoEmpty",
|
||||||
|
URL: "git@github.com:org/testRepoEmpty.git",
|
||||||
|
CloneBranch: "main",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
mockGit := NewMockGit()
|
||||||
|
CloneAllRepos(mockGit, testRepos)
|
||||||
|
gotInfos := len(cloneInfos)
|
||||||
|
expectedInfos := 1
|
||||||
|
if gotInfos != expectedInfos {
|
||||||
|
t.Fatalf("Wrong number of cloneInfos, expected: %v, got: %v", expectedInfos, gotInfos)
|
||||||
|
}
|
||||||
|
gotInfo := cloneInfos[0]
|
||||||
|
expected := "Could not checkout main due to repository being empty, no changes made on: git@github.com:org/testRepoEmpty.git"
|
||||||
|
if gotInfo != expected {
|
||||||
|
t.Errorf("Wrong cloneInfo, expected: %v, got: %v", expected, gotInfo)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestMatchPrefix(t *testing.T) {
|
func TestMatchPrefix(t *testing.T) {
|
||||||
defer UnsetEnv("GHORG_")()
|
defer UnsetEnv("GHORG_")()
|
||||||
dir, err := os.MkdirTemp("", "ghorg_test_match_prefix")
|
dir, err := os.MkdirTemp("", "ghorg_test_match_prefix")
|
||||||
@ -154,6 +212,8 @@ func TestMatchPrefix(t *testing.T) {
|
|||||||
os.Setenv("GHORG_ABSOLUTE_PATH_TO_CLONE_TO", dir)
|
os.Setenv("GHORG_ABSOLUTE_PATH_TO_CLONE_TO", dir)
|
||||||
os.Setenv("GHORG_CONCURRENCY", "1")
|
os.Setenv("GHORG_CONCURRENCY", "1")
|
||||||
os.Setenv("GHORG_MATCH_PREFIX", "test")
|
os.Setenv("GHORG_MATCH_PREFIX", "test")
|
||||||
|
os.Setenv("GHORG_DONT_EXIT_UNDER_TEST", "true")
|
||||||
|
|
||||||
var testRepos = []scm.Repo{
|
var testRepos = []scm.Repo{
|
||||||
{
|
{
|
||||||
Name: "testRepoOne",
|
Name: "testRepoOne",
|
||||||
@ -191,6 +251,8 @@ func TestExcludeMatchPrefix(t *testing.T) {
|
|||||||
os.Setenv("GHORG_ABSOLUTE_PATH_TO_CLONE_TO", dir)
|
os.Setenv("GHORG_ABSOLUTE_PATH_TO_CLONE_TO", dir)
|
||||||
os.Setenv("GHORG_CONCURRENCY", "1")
|
os.Setenv("GHORG_CONCURRENCY", "1")
|
||||||
os.Setenv("GHORG_EXCLUDE_MATCH_PREFIX", "test")
|
os.Setenv("GHORG_EXCLUDE_MATCH_PREFIX", "test")
|
||||||
|
os.Setenv("GHORG_DONT_EXIT_UNDER_TEST", "true")
|
||||||
|
|
||||||
var testRepos = []scm.Repo{
|
var testRepos = []scm.Repo{
|
||||||
{
|
{
|
||||||
Name: "testRepoOne",
|
Name: "testRepoOne",
|
||||||
@ -228,6 +290,8 @@ func TestMatchRegex(t *testing.T) {
|
|||||||
os.Setenv("GHORG_ABSOLUTE_PATH_TO_CLONE_TO", dir)
|
os.Setenv("GHORG_ABSOLUTE_PATH_TO_CLONE_TO", dir)
|
||||||
os.Setenv("GHORG_CONCURRENCY", "1")
|
os.Setenv("GHORG_CONCURRENCY", "1")
|
||||||
os.Setenv("GHORG_MATCH_REGEX", "^test-")
|
os.Setenv("GHORG_MATCH_REGEX", "^test-")
|
||||||
|
os.Setenv("GHORG_DONT_EXIT_UNDER_TEST", "true")
|
||||||
|
|
||||||
var testRepos = []scm.Repo{
|
var testRepos = []scm.Repo{
|
||||||
{
|
{
|
||||||
Name: "test-RepoOne",
|
Name: "test-RepoOne",
|
||||||
@ -267,6 +331,8 @@ func TestExcludeMatchRegex(t *testing.T) {
|
|||||||
os.Setenv("GHORG_CONCURRENCY", "1")
|
os.Setenv("GHORG_CONCURRENCY", "1")
|
||||||
os.Setenv("GHORG_OUTPUT_DIR", testDescriptor)
|
os.Setenv("GHORG_OUTPUT_DIR", testDescriptor)
|
||||||
os.Setenv("GHORG_EXCLUDE_MATCH_REGEX", "^test-")
|
os.Setenv("GHORG_EXCLUDE_MATCH_REGEX", "^test-")
|
||||||
|
os.Setenv("GHORG_DONT_EXIT_UNDER_TEST", "true")
|
||||||
|
|
||||||
var testRepos = []scm.Repo{
|
var testRepos = []scm.Repo{
|
||||||
{
|
{
|
||||||
Name: "test-RepoOne",
|
Name: "test-RepoOne",
|
||||||
|
31
git/git.go
31
git/git.go
@ -1,6 +1,7 @@
|
|||||||
package git
|
package git
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
@ -26,6 +27,7 @@ type Gitter interface {
|
|||||||
FetchAll(scm.Repo) error
|
FetchAll(scm.Repo) error
|
||||||
FetchCloneBranch(scm.Repo) error
|
FetchCloneBranch(scm.Repo) error
|
||||||
RepoCommitCount(scm.Repo) (int, error)
|
RepoCommitCount(scm.Repo) (int, error)
|
||||||
|
HasRemoteHeads(scm.Repo) (bool, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type GitClient struct{}
|
type GitClient struct{}
|
||||||
@ -51,6 +53,35 @@ func printDebugCmd(cmd *exec.Cmd, repo scm.Repo) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (g GitClient) HasRemoteHeads(repo scm.Repo) (bool, error) {
|
||||||
|
cmd := exec.Command("git", "ls-remote", "--heads", "--quiet", "--exit-code")
|
||||||
|
|
||||||
|
err := cmd.Run()
|
||||||
|
if err == nil {
|
||||||
|
// successfully listed the remote heads
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var exitError *exec.ExitError
|
||||||
|
if !errors.As(err, &exitError) {
|
||||||
|
// error, but no exit code, return err
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
exitCode := exitError.ExitCode()
|
||||||
|
if exitCode == 0 {
|
||||||
|
// ls-remote did successfully list the remote heads
|
||||||
|
return true, nil
|
||||||
|
} else if exitCode == 2 {
|
||||||
|
// repository is empty
|
||||||
|
return false, nil
|
||||||
|
} else {
|
||||||
|
// another exit code, simply return err
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
func (g GitClient) Clone(repo scm.Repo) error {
|
func (g GitClient) Clone(repo scm.Repo) error {
|
||||||
args := []string{"clone", repo.CloneURL, repo.HostPath}
|
args := []string{"clone", repo.CloneURL, repo.HostPath}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user