From bb81c3259c05d30985b717d72af8e44ddd5f91f7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 15 Nov 2025 20:06:30 +0000 Subject: [PATCH 1/4] Initial plan From 911ac6839c8fa19a053b0ce67dbe0cd4b18884c5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 15 Nov 2025 20:10:24 +0000 Subject: [PATCH 2/4] Change port parameter to -p with random default Co-authored-by: alexec <1142830+alexec@users.noreply.github.com> --- README.md | 6 ++-- handlers.go | 10 +++--- handlers_test.go | 94 ++++++++++++++++++++++++------------------------ main.go | 8 ++++- 4 files changed, 62 insertions(+), 56 deletions(-) diff --git a/README.md b/README.md index 7ebb4d3..6b2e7fb 100644 --- a/README.md +++ b/README.md @@ -8,14 +8,14 @@ A tiny Go program that shows git diffs in the current directory and all git repo # Build go build -o diffs-cli . -# Run (default port 8080, current directory) +# Run (default random port, current directory) ./diffs-cli # Run on custom port -./diffs-cli -port 9000 +./diffs-cli -p 9000 # Scan a different directory ./diffs-cli -C /path/to/workspace ``` -Then open http://localhost:8080 (or your custom port) in a browser to view the diffs. +Then open the URL shown in the output (e.g., http://localhost:52341) in a browser to view the diffs. diff --git a/handlers.go b/handlers.go index 1b2cbf5..9c4e31e 100644 --- a/handlers.go +++ b/handlers.go @@ -31,21 +31,21 @@ func serveDiffsHTML(w http.ResponseWriter, _ *http.Request) { func findGitRepos(root string) ([]string, error) { var repos []string - + err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error { if err != nil { return nil } - + if info.IsDir() && info.Name() == ".git" { repoPath := filepath.Dir(path) repos = append(repos, repoPath) return filepath.SkipDir } - + return nil }) - + return repos, err } @@ -68,7 +68,7 @@ func serveDiffsText(w http.ResponseWriter, r *http.Request) { if relPath == "." { relPath = "" } - + repoName := relPath if repoName == "" { repoName = filepath.Base(repoPath) diff --git a/handlers_test.go b/handlers_test.go index 615f0b1..ae0702a 100644 --- a/handlers_test.go +++ b/handlers_test.go @@ -12,19 +12,19 @@ import ( func setupTestGitRepo(t *testing.T, dir string) { t.Helper() - + cmd := exec.Command("git", "init") cmd.Dir = dir if err := cmd.Run(); err != nil { t.Fatalf("failed to init git repo: %v", err) } - + cmd = exec.Command("git", "config", "user.email", "test@example.com") cmd.Dir = dir if err := cmd.Run(); err != nil { t.Fatalf("failed to set git email: %v", err) } - + cmd = exec.Command("git", "config", "user.name", "Test User") cmd.Dir = dir if err := cmd.Run(); err != nil { @@ -35,20 +35,20 @@ func setupTestGitRepo(t *testing.T, dir string) { func TestServeDiffsHTML(t *testing.T) { req := httptest.NewRequest("GET", "/", nil) req.Header.Set("Accept", "text/html") - + w := httptest.NewRecorder() diffsHandler(w, req) - + resp := w.Result() if resp.StatusCode != http.StatusOK { t.Errorf("expected status 200, got %d", resp.StatusCode) } - + contentType := resp.Header.Get("Content-Type") if !strings.Contains(contentType, "text/html") { t.Errorf("expected content-type text/html, got %s", contentType) } - + body := w.Body.String() if !strings.Contains(body, "") { t.Errorf("expected HTML document") @@ -57,53 +57,53 @@ func TestServeDiffsHTML(t *testing.T) { func TestServeDiffsText_PWDIsGitRepo(t *testing.T) { tmpDir := t.TempDir() - + setupTestGitRepo(t, tmpDir) - + testFile := filepath.Join(tmpDir, "test.txt") if err := os.WriteFile(testFile, []byte("initial content\n"), 0644); err != nil { t.Fatalf("failed to write test file: %v", err) } - + cmd := exec.Command("git", "add", "test.txt") cmd.Dir = tmpDir if err := cmd.Run(); err != nil { t.Fatalf("failed to git add: %v", err) } - + cmd = exec.Command("git", "commit", "-m", "initial commit") cmd.Dir = tmpDir if err := cmd.Run(); err != nil { t.Fatalf("failed to commit: %v", err) } - + if err := os.WriteFile(testFile, []byte("modified content\n"), 0644); err != nil { t.Fatalf("failed to modify test file: %v", err) } - + oldDir, _ := os.Getwd() defer os.Chdir(oldDir) - + if err := os.Chdir(tmpDir); err != nil { t.Fatalf("failed to chdir: %v", err) } - + req := httptest.NewRequest("GET", "/", nil) req.Header.Set("Accept", "text/x-diff") - + w := httptest.NewRecorder() diffsHandler(w, req) - + resp := w.Result() if resp.StatusCode != http.StatusOK { t.Errorf("expected status 200, got %d", resp.StatusCode) } - + contentType := resp.Header.Get("Content-Type") if !strings.Contains(contentType, "text/x-diff") { t.Errorf("expected content-type text/x-diff, got %s", contentType) } - + body := w.Body.String() if !strings.Contains(body, "diff --git") { t.Errorf("expected diff output, got: %s", body) @@ -115,53 +115,53 @@ func TestServeDiffsText_PWDIsGitRepo(t *testing.T) { func TestServeDiffsText_GitSubdirectory(t *testing.T) { tmpDir := t.TempDir() - + subDir := filepath.Join(tmpDir, "subproject") if err := os.MkdirAll(subDir, 0755); err != nil { t.Fatalf("failed to create subdir: %v", err) } - + setupTestGitRepo(t, subDir) - + testFile := filepath.Join(subDir, "sub.txt") if err := os.WriteFile(testFile, []byte("sub content\n"), 0644); err != nil { t.Fatalf("failed to write test file: %v", err) } - + cmd := exec.Command("git", "add", "sub.txt") cmd.Dir = subDir if err := cmd.Run(); err != nil { t.Fatalf("failed to git add: %v", err) } - + cmd = exec.Command("git", "commit", "-m", "sub commit") cmd.Dir = subDir if err := cmd.Run(); err != nil { t.Fatalf("failed to commit: %v", err) } - + if err := os.WriteFile(testFile, []byte("modified sub\n"), 0644); err != nil { t.Fatalf("failed to modify test file: %v", err) } - + oldDir, _ := os.Getwd() defer os.Chdir(oldDir) - + if err := os.Chdir(tmpDir); err != nil { t.Fatalf("failed to chdir: %v", err) } - + req := httptest.NewRequest("GET", "/", nil) req.Header.Set("Accept", "text/x-diff") - + w := httptest.NewRecorder() diffsHandler(w, req) - + resp := w.Result() if resp.StatusCode != http.StatusOK { t.Errorf("expected status 200, got %d", resp.StatusCode) } - + body := w.Body.String() if !strings.Contains(body, "diff --git") { t.Errorf("expected diff output, got: %s", body) @@ -176,78 +176,78 @@ func TestServeDiffsText_GitSubdirectory(t *testing.T) { func TestServeDiffsText_LargeDiffTruncation(t *testing.T) { tmpDir := t.TempDir() - + setupTestGitRepo(t, tmpDir) - + testFile := filepath.Join(tmpDir, "large.txt") largeContent := strings.Repeat("x", 1024*1024) if err := os.WriteFile(testFile, []byte(largeContent), 0644); err != nil { t.Fatalf("failed to write test file: %v", err) } - + cmd := exec.Command("git", "add", "large.txt") cmd.Dir = tmpDir if err := cmd.Run(); err != nil { t.Fatalf("failed to git add: %v", err) } - + cmd = exec.Command("git", "commit", "-m", "large commit") cmd.Dir = tmpDir if err := cmd.Run(); err != nil { t.Fatalf("failed to commit: %v", err) } - + modifiedContent := strings.Repeat("y", 1024*1024) if err := os.WriteFile(testFile, []byte(modifiedContent), 0644); err != nil { t.Fatalf("failed to modify test file: %v", err) } - + anotherFile := filepath.Join(tmpDir, "another.txt") moreContent := strings.Repeat("z", 5*1024*1024) if err := os.WriteFile(anotherFile, []byte(moreContent), 0644); err != nil { t.Fatalf("failed to write another file: %v", err) } - + cmd = exec.Command("git", "add", "another.txt") cmd.Dir = tmpDir if err := cmd.Run(); err != nil { t.Fatalf("failed to git add: %v", err) } - + cmd = exec.Command("git", "commit", "-m", "another commit") cmd.Dir = tmpDir if err := cmd.Run(); err != nil { t.Fatalf("failed to commit: %v", err) } - + yetAnotherContent := strings.Repeat("w", 5*1024*1024) if err := os.WriteFile(anotherFile, []byte(yetAnotherContent), 0644); err != nil { t.Fatalf("failed to modify another file: %v", err) } - + oldDir, _ := os.Getwd() defer os.Chdir(oldDir) - + if err := os.Chdir(tmpDir); err != nil { t.Fatalf("failed to chdir: %v", err) } - + req := httptest.NewRequest("GET", "/", nil) req.Header.Set("Accept", "text/x-diff") - + w := httptest.NewRecorder() diffsHandler(w, req) - + resp := w.Result() if resp.StatusCode != http.StatusOK { t.Errorf("expected status 200, got %d", resp.StatusCode) } - + body := w.Body.String() if len(body) > 5*1024*1024 { t.Errorf("expected diff to be truncated to 5MB, got %d bytes", len(body)) } - + if len(body) == 0 { t.Error("expected some diff output") } diff --git a/main.go b/main.go index 3873152..73e43b3 100644 --- a/main.go +++ b/main.go @@ -3,12 +3,18 @@ package main import ( "flag" "fmt" + "math/rand" "net/http" "os" + "strconv" + "time" ) func main() { - port := flag.String("port", "8080", "Port to listen on") + rand.Seed(time.Now().UnixNano()) + randomPort := strconv.Itoa(rand.Intn(65535-49152) + 49152) + + port := flag.String("p", randomPort, "Port to listen on") workspaceDir := flag.String("C", ".", "Directory to scan for git repositories") flag.Parse() From ce9f3e8e922cef52f1a6d3ce703edcdc0e284992 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 15 Nov 2025 20:16:45 +0000 Subject: [PATCH 3/4] Change default port to 3844 Co-authored-by: alexec <1142830+alexec@users.noreply.github.com> --- README.md | 4 ++-- main.go | 8 +------- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 6b2e7fb..c9abfac 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ A tiny Go program that shows git diffs in the current directory and all git repo # Build go build -o diffs-cli . -# Run (default random port, current directory) +# Run (default port 3844, current directory) ./diffs-cli # Run on custom port @@ -18,4 +18,4 @@ go build -o diffs-cli . ./diffs-cli -C /path/to/workspace ``` -Then open the URL shown in the output (e.g., http://localhost:52341) in a browser to view the diffs. +Then open http://localhost:3844 (or your custom port) in a browser to view the diffs. diff --git a/main.go b/main.go index 73e43b3..b7fd14f 100644 --- a/main.go +++ b/main.go @@ -3,18 +3,12 @@ package main import ( "flag" "fmt" - "math/rand" "net/http" "os" - "strconv" - "time" ) func main() { - rand.Seed(time.Now().UnixNano()) - randomPort := strconv.Itoa(rand.Intn(65535-49152) + 49152) - - port := flag.String("p", randomPort, "Port to listen on") + port := flag.String("p", "3844", "Port to listen on") workspaceDir := flag.String("C", ".", "Directory to scan for git repositories") flag.Parse() From d23c8ddb4e1ea55b3d0b7f2a8e0916a85faef56f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 15 Nov 2025 20:21:46 +0000 Subject: [PATCH 4/4] Merge main branch and resolve conflicts Co-authored-by: alexec <1142830+alexec@users.noreply.github.com> --- .github/workflows/release.yml | 22 +++++----- .gitignore | 2 +- README.md | 10 ++--- diffs.html | 78 +++++++++++++++++++++++++---------- go.mod | 2 +- handlers.go | 15 +++++-- handlers_test.go | 31 ++++++++++++++ main.go | 5 ++- 8 files changed, 122 insertions(+), 43 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 190cd77..5171d77 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -28,22 +28,22 @@ jobs: - run: go test -v ./... # https://gist.github.com/asukakenji/f15ba7e588ac42795f421b48b8aede63 - - run: CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -o diffs-cli_${{ github.ref_name }}_darwin_amd64 . - - run: CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 go build -o diffs-cli_${{ github.ref_name }}_darwin_arm64 . - - run: CGO_ENABLED=0 GOOS=linux GOARCH=386 go build -o diffs-cli_${{ github.ref_name }}_linux_386 . - - run: CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o diffs-cli_${{ github.ref_name }}_linux_amd64 . - - run: CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -o diffs-cli_${{ github.ref_name }}_linux_arm64 . + - run: CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -o diff-server_${{ github.ref_name }}_darwin_amd64 . + - run: CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 go build -o diff-server_${{ github.ref_name }}_darwin_arm64 . + - run: CGO_ENABLED=0 GOOS=linux GOARCH=386 go build -o diff-server_${{ github.ref_name }}_linux_386 . + - run: CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o diff-server_${{ github.ref_name }}_linux_amd64 . + - run: CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -o diff-server_${{ github.ref_name }}_linux_arm64 . # create checksums.txt - - run: shasum -a 256 diffs-cli_* > checksums.txt + - run: shasum -a 256 diff-server_* > checksums.txt - name: Create a Release in a GitHub Action uses: softprops/action-gh-release@v2 with: files: | - diffs-cli_${{ github.ref_name }}_darwin_amd64 - diffs-cli_${{ github.ref_name }}_darwin_arm64 - diffs-cli_${{ github.ref_name }}_linux_386 - diffs-cli_${{ github.ref_name }}_linux_amd64 - diffs-cli_${{ github.ref_name }}_linux_arm64 + diff-server_${{ github.ref_name }}_darwin_amd64 + diff-server_${{ github.ref_name }}_darwin_arm64 + diff-server_${{ github.ref_name }}_linux_386 + diff-server_${{ github.ref_name }}_linux_amd64 + diff-server_${{ github.ref_name }}_linux_arm64 checksums.txt diff --git a/.gitignore b/.gitignore index 845bcb5..a027352 100644 --- a/.gitignore +++ b/.gitignore @@ -7,7 +7,7 @@ *.dll *.so *.dylib -diffs-cli +diff-server # Test binary, built with `go test -c` *.test diff --git a/README.md b/README.md index c9abfac..cb6456e 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# diffs-cli +# diff-server A tiny Go program that shows git diffs in the current directory and all git repositories in subdirectories. @@ -6,16 +6,16 @@ A tiny Go program that shows git diffs in the current directory and all git repo ```bash # Build -go build -o diffs-cli . +go build -o diff-server . # Run (default port 3844, current directory) -./diffs-cli +./diff-server # Run on custom port -./diffs-cli -p 9000 +./diff-server -p 9000 # Scan a different directory -./diffs-cli -C /path/to/workspace +./diff-server -C /path/to/workspace ``` Then open http://localhost:3844 (or your custom port) in a browser to view the diffs. diff --git a/diffs.html b/diffs.html index 3181ca5..7db1405 100644 --- a/diffs.html +++ b/diffs.html @@ -1,25 +1,61 @@ - - - Diffs - - -

Git Diffs

-
Loading diffs...
- + - + + fetchAndRender(); + setInterval(fetchAndRender, 10000); + + diff --git a/go.mod b/go.mod index ad3dae5..d7f2731 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,3 @@ -module github.com/kitproj/diffs-cli +module github.com/kitproj/diff-server go 1.24.4 diff --git a/handlers.go b/handlers.go index 9c4e31e..afae489 100644 --- a/handlers.go +++ b/handlers.go @@ -3,6 +3,7 @@ package main import ( "context" _ "embed" + "log" "net/http" "os" "os/exec" @@ -26,7 +27,9 @@ func diffsHandler(w http.ResponseWriter, r *http.Request) { func serveDiffsHTML(w http.ResponseWriter, _ *http.Request) { w.Header().Set("Content-Type", "text/html; charset=utf-8") - _, _ = w.Write(diffsHTML) + if _, err := w.Write(diffsHTML); err != nil { + log.Printf("Failed to write HTML response: %v", err) + } } func findGitRepos(root string) ([]string, error) { @@ -64,7 +67,11 @@ func serveDiffsText(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "text/x-diff; charset=utf-8") for _, repoPath := range repos { - relPath, _ := filepath.Rel(".", repoPath) + relPath, err := filepath.Rel(".", repoPath) + if err != nil { + log.Printf("Failed to get relative path for %s: %v", repoPath, err) + relPath = repoPath + } if relPath == "." { relPath = "" } @@ -85,6 +92,8 @@ done cmd.Dir = repoPath cmd.Stdout = writer cmd.Stderr = writer - _ = cmd.Run() + if err := cmd.Run(); err != nil { + log.Printf("Git command failed for %s: %v", repoPath, err) + } } } diff --git a/handlers_test.go b/handlers_test.go index ae0702a..f84ec8f 100644 --- a/handlers_test.go +++ b/handlers_test.go @@ -252,3 +252,34 @@ func TestServeDiffsText_LargeDiffTruncation(t *testing.T) { t.Error("expected some diff output") } } + +func TestServeDiffsText_NonGitDirectory(t *testing.T) { + tmpDir := t.TempDir() + + // Create a non-git directory with a file + testFile := filepath.Join(tmpDir, "test.txt") + if err := os.WriteFile(testFile, []byte("test content\n"), 0644); err != nil { + t.Fatalf("failed to write test file: %v", err) + } + + oldDir, _ := os.Getwd() + defer os.Chdir(oldDir) + + if err := os.Chdir(tmpDir); err != nil { + t.Fatalf("failed to chdir: %v", err) + } + + req := httptest.NewRequest("GET", "/", nil) + req.Header.Set("Accept", "text/x-diff") + + w := httptest.NewRecorder() + diffsHandler(w, req) + + resp := w.Result() + if resp.StatusCode != http.StatusOK { + t.Errorf("expected status 200, got %d", resp.StatusCode) + } + + // Should return empty response since there are no git repos + // This tests that the handler completes successfully even with no repos +} diff --git a/main.go b/main.go index b7fd14f..e4ae3f6 100644 --- a/main.go +++ b/main.go @@ -20,5 +20,8 @@ func main() { http.HandleFunc("/", diffsHandler) fmt.Printf("Starting server on http://localhost:%s\n", *port) - http.ListenAndServe(":"+*port, nil) + if err := http.ListenAndServe(":"+*port, nil); err != nil { + fmt.Fprintf(os.Stderr, "Server failed: %v\n", err) + os.Exit(1) + } }