sketch: create empty commit on empty repo, both in innie and outtie

Life's a bit too short to fail when the git repo exists but is empty.
For innie (e.g., using -unsafe on an empty repo), we just create the
empty commit. For outtie, we do the same thing, since initializing an
empty repo would be a weird different code path.

Co-Authored-By: sketch <hello@sketch.dev>
Change-ID: se0a9606cc9f43388k
diff --git a/dockerimg/dockerimg.go b/dockerimg/dockerimg.go
index 2ada9c6..16865cf 100644
--- a/dockerimg/dockerimg.go
+++ b/dockerimg/dockerimg.go
@@ -176,11 +176,6 @@
 	// Capture the original git origin URL before we set up the temporary git server
 	config.OriginalGitOrigin = getOriginalGitOrigin(ctx, gitRoot)
 
-	err = checkForEmptyGitRepo(ctx, config.Path)
-	if err != nil {
-		return err
-	}
-
 	imgName, err := findOrBuildDockerImage(ctx, gitRoot, config.BaseImage, config.ForceRebuild, config.Verbose)
 	if err != nil {
 		return err
@@ -215,6 +210,21 @@
 		errCh <- gitSrv.serve(ctx)
 	}()
 
+	// Check if we have any commits, and if not, create an empty initial commit
+	cmd := exec.CommandContext(ctx, "git", "rev-list", "--all", "--count")
+	countOut, err := cmd.CombinedOutput()
+	if err != nil {
+		return fmt.Errorf("git rev-list --all --count: %s: %w", countOut, err)
+	}
+	commitCount := strings.TrimSpace(string(countOut))
+	if commitCount == "0" {
+		slog.Info("No commits found, creating empty initial commit")
+		cmd = exec.CommandContext(ctx, "git", "commit", "--allow-empty", "-m", "Initial empty commit")
+		if commitOut, err := cmd.CombinedOutput(); err != nil {
+			return fmt.Errorf("git commit --allow-empty: %s: %w", commitOut, err)
+		}
+	}
+
 	// Get the current host git commit
 	var commit string
 	if out, err := combinedOutput(ctx, "git", "rev-parse", "HEAD"); err != nil {
@@ -910,17 +920,6 @@
 	return nil
 }
 
-func checkForEmptyGitRepo(ctx context.Context, path string) error {
-	cmd := exec.CommandContext(ctx, "git", "rev-parse", "-q", "--verify", "HEAD")
-	cmd.Dir = path
-	_, err := cmd.CombinedOutput()
-	if err != nil {
-		return fmt.Errorf("sketch needs to run from within a git repo with at least one commit.\nRun: %s",
-			"git commit --allow-empty -m 'initial commit'")
-	}
-	return nil
-}
-
 // requireGitRepo confirms that path is within a git repository.
 func requireGitRepo(ctx context.Context, path string) error {
 	cmd := exec.CommandContext(ctx, "git", "rev-parse", "--git-dir")
diff --git a/loop/agent.go b/loop/agent.go
index b10e2d6..21e10d7 100644
--- a/loop/agent.go
+++ b/loop/agent.go
@@ -1273,7 +1273,24 @@
 			}
 		}
 
-		cmd := exec.CommandContext(ctx, "git", "tag", "-f", a.SketchGitBaseRef(), "HEAD")
+		// Check if we have any commits, and if not, create an empty initial commit
+		cmd := exec.CommandContext(ctx, "git", "rev-list", "--all", "--count")
+		cmd.Dir = repoRoot
+		countOut, err := cmd.CombinedOutput()
+		if err != nil {
+			return fmt.Errorf("git rev-list --all --count: %s: %w", countOut, err)
+		}
+		commitCount := strings.TrimSpace(string(countOut))
+		if commitCount == "0" {
+			slog.Info("No commits found, creating empty initial commit")
+			cmd = exec.CommandContext(ctx, "git", "commit", "--allow-empty", "-m", "Initial empty commit")
+			cmd.Dir = repoRoot
+			if commitOut, err := cmd.CombinedOutput(); err != nil {
+				return fmt.Errorf("git commit --allow-empty: %s: %w", commitOut, err)
+			}
+		}
+
+		cmd = exec.CommandContext(ctx, "git", "tag", "-f", a.SketchGitBaseRef(), "HEAD")
 		cmd.Dir = repoRoot
 		if out, err := cmd.CombinedOutput(); err != nil {
 			return fmt.Errorf("git tag -f %s %s: %s: %w", a.SketchGitBaseRef(), "HEAD", out, err)