dockerimg: fix Docker image reuse for Dockerfile.sketch

Add sketch_context label to Docker images built from Dockerfile.sketch to enable
proper image reuse detection across runs, eliminating unnecessary rebuilds.

Co-Authored-By: sketch <hello@sketch.dev>
Change-ID: sfa4d61590e7e00f9k
diff --git a/dockerimg/dockerimg.go b/dockerimg/dockerimg.go
index e207904..5791083 100644
--- a/dockerimg/dockerimg.go
+++ b/dockerimg/dockerimg.go
@@ -887,14 +887,31 @@
 	}
 
 	start := time.Now()
-	cmd := exec.CommandContext(ctx,
-		"docker", "build",
+	cmdArgs := []string{
+		"build",
 		"-t", imgName,
 		"-f", dockerfilePath,
-		"--build-arg", "GIT_USER_EMAIL="+gitUserEmail,
-		"--build-arg", "GIT_USER_NAME="+gitUserName,
-		".",
-	)
+		"--build-arg", "GIT_USER_EMAIL=" + gitUserEmail,
+		"--build-arg", "GIT_USER_NAME=" + gitUserName,
+	}
+
+	// Add the sketch_context label for image reuse detection
+	var contextHash string
+	if len(candidates) > 0 {
+		// Building from Dockerfile.sketch or similar static file
+		contents, err := os.ReadFile(dockerfilePath)
+		if err != nil {
+			return "", err
+		}
+		contextHash = hashInitFiles(map[string]string{dockerfilePath: string(contents)})
+	} else {
+		// Building from generated dockerfile
+		contextHash = hashInitFiles(initFiles)
+	}
+	cmdArgs = append(cmdArgs, "--label", "sketch_context="+contextHash)
+	cmdArgs = append(cmdArgs, ".")
+
+	cmd := exec.CommandContext(ctx, "docker", cmdArgs...)
 	cmd.Dir = gitRoot
 	// We print the docker build output whether or not the user
 	// has selected --verbose. Building an image takes a while
diff --git a/dockerimg/dockerimg_test.go b/dockerimg/dockerimg_test.go
index be5344a..c3cc65c 100644
--- a/dockerimg/dockerimg_test.go
+++ b/dockerimg/dockerimg_test.go
@@ -446,3 +446,79 @@
 		t.Errorf("expected Dockerfile.sketch to be prioritized, but got %s", prioritized)
 	}
 }
+
+// TestDockerfileSketchImageReuse tests that Docker images built from Dockerfile.sketch
+// are properly reused when the Dockerfile content hasn't changed.
+func TestDockerfileSketchImageReuse(t *testing.T) {
+	// Create a temporary directory for the test repo
+	tmpDir := t.TempDir()
+
+	// Create a simple Dockerfile.sketch
+	dockerfileContent := `FROM ubuntu:24.04
+LABEL test=true
+CMD ["echo", "hello"]
+`
+	dockerfilePath := filepath.Join(tmpDir, "Dockerfile.sketch")
+	err := os.WriteFile(dockerfilePath, []byte(dockerfileContent), 0o644)
+	if err != nil {
+		t.Fatalf("Failed to write Dockerfile.sketch: %v", err)
+	}
+
+	// Test that hashInitFiles produces consistent results
+	initFiles := map[string]string{
+		dockerfilePath: dockerfileContent,
+	}
+	hash1 := hashInitFiles(initFiles)
+	hash2 := hashInitFiles(initFiles)
+
+	if hash1 != hash2 {
+		t.Errorf("hashInitFiles should be deterministic, got %s and %s", hash1, hash2)
+	}
+
+	// Test that hash changes when content changes
+	modifiedFiles := map[string]string{
+		dockerfilePath: dockerfileContent + "RUN echo modified\n",
+	}
+	hash3 := hashInitFiles(modifiedFiles)
+
+	if hash1 == hash3 {
+		t.Errorf("hashInitFiles should produce different hashes for different content, got %s for both", hash1)
+	}
+
+	t.Logf("Original hash: %s", hash1)
+	t.Logf("Modified hash: %s", hash3)
+}
+
+// TestPrioritizeDockerfiles tests the Dockerfile prioritization logic
+func TestDockerfileSketchPriority(t *testing.T) {
+	tests := []struct {
+		name       string
+		candidates []string
+		expected   string
+	}{
+		{
+			name:       "dockerfile.sketch_wins_over_dockerfile",
+			candidates: []string{"/path/Dockerfile", "/path/Dockerfile.sketch"},
+			expected:   "/path/Dockerfile.sketch",
+		},
+		{
+			name:       "dockerfile.sketch_case_insensitive",
+			candidates: []string{"/path/dockerfile", "/path/dockerfile.sketch"},
+			expected:   "/path/dockerfile.sketch",
+		},
+		{
+			name:       "dockerfile_wins_over_variations",
+			candidates: []string{"/path/Dockerfile.dev", "/path/Dockerfile"},
+			expected:   "/path/Dockerfile",
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			result := prioritizeDockerfiles(tt.candidates)
+			if result != tt.expected {
+				t.Errorf("prioritizeDockerfiles(%v) = %s, want %s", tt.candidates, result, tt.expected)
+			}
+		})
+	}
+}