Skip to content

Commit c8fdc36

Browse files
committed
Prevent panic when cloning empty git repository
This covers the edge case in which a user creates a GitRepository CR referencing an empty Git repository. Currently, the controller will panic in this situation since the returned commit pointer is nil. Signed-off-by: Aurel Canciu <aurelcanciu@gmail.com>
1 parent ae3a81e commit c8fdc36

File tree

2 files changed

+71
-0
lines changed

2 files changed

+71
-0
lines changed

controllers/gitrepository_controller.go

+12
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,10 @@ import (
6161
"github.com/fluxcd/source-controller/internal/util"
6262
)
6363

64+
// EmptyGitRepositoryReason signals that a referenced Git repository is empty
65+
// (only has an initial branch without any commits).
66+
const EmptyGitRepositoryReason string = "EmptyGitRepository"
67+
6468
// gitRepositoryReadyCondition contains the information required to summarize a
6569
// v1beta2.GitRepository Ready Condition.
6670
var gitRepositoryReadyCondition = summarize.Conditions{
@@ -537,6 +541,14 @@ func (r *GitRepositoryReconciler) reconcileSource(ctx context.Context, sp *patch
537541
if err != nil {
538542
return sreconcile.ResultEmpty, err
539543
}
544+
if c == nil {
545+
e := serror.NewGeneric(
546+
fmt.Errorf("git repository is empty"),
547+
EmptyGitRepositoryReason,
548+
)
549+
conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, e.Err.Error())
550+
return sreconcile.ResultEmpty, e
551+
}
540552
// Assign the commit to the shared commit reference.
541553
*commit = *c
542554

controllers/gitrepository_controller_test.go

+59
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,65 @@ func TestGitRepositoryReconciler_Reconcile(t *testing.T) {
220220
testSuspendedObjectDeleteWithArtifact(ctx, g, obj)
221221
}
222222

223+
func TestGitRepositoryReconciler_reconcileSource_emptyRepository(t *testing.T) {
224+
g := NewWithT(t)
225+
226+
server, err := gittestserver.NewTempGitServer()
227+
g.Expect(err).NotTo(HaveOccurred())
228+
defer os.RemoveAll(server.Root())
229+
server.AutoCreate()
230+
g.Expect(server.StartHTTP()).To(Succeed())
231+
defer server.StopHTTP()
232+
233+
obj := &sourcev1.GitRepository{
234+
ObjectMeta: metav1.ObjectMeta{
235+
GenerateName: "empty-",
236+
Generation: 1,
237+
},
238+
Spec: sourcev1.GitRepositorySpec{
239+
Interval: metav1.Duration{Duration: interval},
240+
Timeout: &metav1.Duration{Duration: timeout},
241+
URL: server.HTTPAddress() + "/test.git",
242+
},
243+
}
244+
245+
fs := memfs.New()
246+
localRepo, err := gogit.Init(memory.NewStorage(), fs)
247+
g.Expect(err).NotTo(HaveOccurred())
248+
249+
builder := fakeclient.NewClientBuilder().WithScheme(testEnv.GetScheme())
250+
251+
r := &GitRepositoryReconciler{
252+
Client: builder.Build(),
253+
EventRecorder: record.NewFakeRecorder(32),
254+
Storage: testStorage,
255+
patchOptions: getPatchOptions(gitRepositoryReadyCondition.Owned, "sc"),
256+
}
257+
258+
tmpDir := t.TempDir()
259+
260+
head, _ := localRepo.Head()
261+
g.Expect(head).To(BeNil())
262+
263+
g.Expect(r.Client.Create(context.TODO(), obj)).ToNot(HaveOccurred())
264+
defer func() {
265+
g.Expect(r.Client.Delete(context.TODO(), obj)).ToNot(HaveOccurred())
266+
}()
267+
268+
var commit git.Commit
269+
var includes artifactSet
270+
sp := patch.NewSerialPatcher(obj, r.Client)
271+
272+
got, err := r.reconcileSource(context.TODO(), sp, obj, &commit, &includes, tmpDir)
273+
assertConditions := []metav1.Condition{
274+
*conditions.TrueCondition(sourcev1.FetchFailedCondition, EmptyGitRepositoryReason, "git repository is empty"),
275+
}
276+
g.Expect(obj.Status.Conditions).To(conditions.MatchConditions(assertConditions))
277+
g.Expect(err).To(HaveOccurred())
278+
g.Expect(got).To(Equal(sreconcile.ResultEmpty))
279+
g.Expect(commit).ToNot(BeNil())
280+
}
281+
223282
func TestGitRepositoryReconciler_reconcileSource_authStrategy(t *testing.T) {
224283
type options struct {
225284
username string

0 commit comments

Comments
 (0)