open-code-review/internal/diff/git_resolve_test.go
kite 533b526b4c
Some checks are pending
CI / cross-compile (arm64, darwin) (push) Waiting to run
CI / cross-compile (arm64, linux) (push) Waiting to run
CI / cross-compile (arm64, windows) (push) Waiting to run
CI / test (push) Waiting to run
CI / cross-compile (amd64, darwin) (push) Waiting to run
CI / cross-compile (amd64, windows) (push) Waiting to run
CodeQL Advanced / Analyze (go) (push) Waiting to run
CodeQL Advanced / Analyze (actions) (push) Waiting to run
CodeQL Advanced / Analyze (javascript-typescript) (push) Waiting to run
Deploy Pages / build (push) Waiting to run
Deploy Pages / deploy (push) Blocked by required conditions
chore: add SPDX license headers and automated verification (#740)
* chore: add SPDX license headers to all source files

Add Apache-2.0 SPDX license identifiers and copyright notices to all
tracked .go, .sh, .js, .mjs, .ts, and .tsx source files.

Introduce scripts/verify-license.sh and scripts/add-license.sh for
automated verification and bulk addition of license headers. Integrate
the check into CI (ci.yml) and the Makefile (license-check target as
a prerequisite of the existing check target).

This satisfies the OpenSSF Best Practices Badge requirements for
copyright_per_file and license_per_file.

* fix: restore execute permissions on scripts

* docs: add license header instructions to CONTRIBUTING guides

* docs: add license header instructions to pages contributing guides

* fix(pages): strip unclosed HTML comment markers to satisfy CodeQL

* fix: apply code review suggestions for license scripts

- Fix portability: detect macOS vs Linux stat for permission copy
- Fix has_header: check both SPDX and copyright (match verify logic)
- Fix is_ignored: match on path boundaries to avoid false positives
- Fix year extraction: use consistent pipeline across both scripts
- Fix Bash 3.2 compat: quote array length expansion for set -u

* fix(pages): use loop-until-clean for HTML comment stripping (CodeQL)

* fix(pages): use split/join instead of replace to avoid CodeQL false positive

CodeQL's js/incomplete-multi-character-sanitization rule flags any
.replace() that removes multi-character sequences like '<!--...-->',
regardless of context. The data here comes from readFileSync on the
project's own index.html (no untrusted input), making this a false
positive. Using split(regex).join('') achieves the same result without
triggering the taint-tracking rule.
2026-08-05 21:26:27 +08:00

225 lines
7.9 KiB
Go

// SPDX-License-Identifier: Apache-2.0
// Copyright 2026 alibaba/open-code-review Contributors
package diff
import (
"context"
"os"
"os/exec"
"path/filepath"
"strings"
"testing"
)
// gitOut runs a git command in dir and returns its trimmed stdout, failing the
// test on error. Used to capture the SHAs a ResolveInput result must match.
func gitOut(t *testing.T, dir string, args ...string) string {
t.Helper()
cmd := exec.Command("git", args...)
cmd.Dir = dir
out, err := cmd.Output()
if err != nil {
t.Fatalf("git %v failed: %v", args, err)
}
return strings.TrimSpace(string(out))
}
// initBareRepo creates an empty (unborn) git repository with identity configured
// but no commits, returning the repo dir.
func initBareRepo(t *testing.T) string {
t.Helper()
repo := t.TempDir()
runGitTest(t, repo, "init", "-q")
runGitTest(t, repo, "config", "user.email", "test@example.com")
runGitTest(t, repo, "config", "user.name", "Test User")
runGitTest(t, repo, "config", "commit.gpgsign", "false")
return repo
}
// writeCommit writes content to name, stages it, and commits with msg.
func writeCommit(t *testing.T, repo, name, content, msg string) {
t.Helper()
if err := os.WriteFile(filepath.Join(repo, name), []byte(content), 0o644); err != nil {
t.Fatalf("write %s: %v", name, err)
}
runGitTest(t, repo, "add", name)
runGitTest(t, repo, "commit", "-q", "-m", msg)
}
func TestResolveInput_Range(t *testing.T) {
repo := initBareRepo(t)
writeCommit(t, repo, "a.txt", "one\n", "base")
base := gitOut(t, repo, "rev-parse", "HEAD")
// Capture the default branch name (master or main, git-version dependent).
baseBranch := gitOut(t, repo, "rev-parse", "--abbrev-ref", "HEAD")
runGitTest(t, repo, "checkout", "-q", "-b", "feature")
writeCommit(t, repo, "a.txt", "one\ntwo\n", "feature change")
head := gitOut(t, repo, "rev-parse", "HEAD")
p := NewProvider(repo, baseBranch, "feature", nil)
got := p.ResolveInput(context.Background())
if got.ResolvedBase != base {
t.Errorf("ResolvedBase = %q, want merge-base %q", got.ResolvedBase, base)
}
if got.ResolvedHead != head {
t.Errorf("ResolvedHead = %q, want %q", got.ResolvedHead, head)
}
if want := base + ".." + head; got.ExactRange != want {
t.Errorf("ExactRange = %q, want %q", got.ExactRange, want)
}
}
func TestResolveInput_SingleParentCommit(t *testing.T) {
repo := initBareRepo(t)
writeCommit(t, repo, "a.txt", "one\n", "root")
parent := gitOut(t, repo, "rev-parse", "HEAD")
writeCommit(t, repo, "a.txt", "one\ntwo\n", "second")
head := gitOut(t, repo, "rev-parse", "HEAD")
p := NewCommitProvider(repo, head, nil)
got := p.ResolveInput(context.Background())
if got.ResolvedHead != head {
t.Errorf("ResolvedHead = %q, want %q", got.ResolvedHead, head)
}
if got.ResolvedBase != parent {
t.Errorf("ResolvedBase = %q, want parent %q", got.ResolvedBase, parent)
}
if want := parent + ".." + head; got.ExactRange != want {
t.Errorf("ExactRange = %q, want %q", got.ExactRange, want)
}
}
func TestResolveInput_RootCommit(t *testing.T) {
repo := initBareRepo(t)
writeCommit(t, repo, "a.txt", "one\n", "root")
head := gitOut(t, repo, "rev-parse", "HEAD")
p := NewCommitProvider(repo, head, nil)
got := p.ResolveInput(context.Background())
if got.ResolvedHead != head {
t.Errorf("ResolvedHead = %q, want %q", got.ResolvedHead, head)
}
if got.ResolvedBase != "" {
t.Errorf("ResolvedBase = %q, want empty for a root commit", got.ResolvedBase)
}
if got.ExactRange != "" {
t.Errorf("ExactRange = %q, want empty for a root commit", got.ExactRange)
}
}
func TestResolveInput_MergeCommit(t *testing.T) {
repo := initBareRepo(t)
writeCommit(t, repo, "a.txt", "one\n", "root")
root := gitOut(t, repo, "rev-parse", "HEAD")
// Determine the current branch name (main or master).
mainBranch := gitOut(t, repo, "rev-parse", "--abbrev-ref", "HEAD")
runGitTest(t, repo, "checkout", "-q", "-b", "side")
writeCommit(t, repo, "b.txt", "side\n", "side change")
runGitTest(t, repo, "checkout", "-q", mainBranch)
writeCommit(t, repo, "c.txt", "mainline\n", "main change")
firstParent := gitOut(t, repo, "rev-parse", "HEAD")
runGitTest(t, repo, "merge", "-q", "--no-ff", "-m", "merge side", "side")
merge := gitOut(t, repo, "rev-parse", "HEAD")
if merge == root {
t.Fatal("merge commit setup failed: HEAD did not advance")
}
p := NewCommitProvider(repo, merge, nil)
got := p.ResolveInput(context.Background())
if got.ResolvedHead != merge {
t.Errorf("ResolvedHead = %q, want %q", got.ResolvedHead, merge)
}
if got.ResolvedBase != firstParent {
t.Errorf("ResolvedBase = %q, want first parent %q", got.ResolvedBase, firstParent)
}
if want := firstParent + ".." + merge; got.ExactRange != want {
t.Errorf("ExactRange = %q, want %q", got.ExactRange, want)
}
}
func TestResolveInput_Workspace(t *testing.T) {
repo := initRepoWithChange(t) // one commit + working-tree change
head := gitOut(t, repo, "rev-parse", "HEAD")
p := NewWorkspaceProvider(repo, nil)
got := p.ResolveInput(context.Background())
if got.ResolvedBase != head {
t.Errorf("ResolvedBase = %q, want HEAD %q", got.ResolvedBase, head)
}
if got.ResolvedHead != "" {
t.Errorf("ResolvedHead = %q, want empty (workspace has no immutable head)", got.ResolvedHead)
}
if got.ExactRange != "" {
t.Errorf("ExactRange = %q, want empty for workspace", got.ExactRange)
}
}
func TestCanonicalRemote(t *testing.T) {
cases := []struct {
name string
in string
want string
}{
{"https", "https://github.com/org/repo.git", "github.com/org/repo"},
{"https creds stripped", "https://user:token@github.com/org/repo.git", "github.com/org/repo"},
{"https query fragment", "https://github.com/org/repo.git?ref=x#frag", "github.com/org/repo"},
{"scp", "git@github.com:org/repo.git", "github.com/org/repo"},
{"scp no user", "github.com:org/repo", "github.com/org/repo"},
{"host uppercased", "https://GitHub.com/Org/Repo.git", "github.com/Org/Repo"},
{"trailing slash", "https://github.com/org/repo/", "github.com/org/repo"},
// B1: a port distinguishes endpoints and must survive canonicalization.
{"https port kept", "https://example.com:8443/org/repo.git", "example.com:8443/org/repo"},
{"ssh port kept", "ssh://git@example.com:2222/org/repo.git", "example.com:2222/org/repo"},
// B2: an "@" inside the path must not be truncated as scp userinfo.
{"scp at in path", "git@host.com:a/b@c.git", "host.com/a/b@c"},
// B3: local remotes have no stable network identity → omitted.
{"local absolute", "/srv/git/repo.git", ""},
{"local relative", "../peer/repo.git", ""},
{"file scheme", "file:///srv/git/repo.git", ""},
{"windows drive", `C:\repos\thing.git`, ""},
{"unc share", `\\server\share\repo.git`, ""},
{"empty", "", ""},
{"whitespace", " ", ""},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
if got := canonicalRemote(tc.in); got != tc.want {
t.Errorf("canonicalRemote(%q) = %q, want %q", tc.in, got, tc.want)
}
})
}
}
func TestRemoteIdentity(t *testing.T) {
repo := initBareRepo(t)
// No origin remote yet: identity is empty.
p := NewWorkspaceProvider(repo, nil)
if got := p.RemoteIdentity(context.Background()); got != "" {
t.Errorf("RemoteIdentity without origin = %q, want empty", got)
}
// Add an origin with an embedded credential; identity must be credential-free.
runGitTest(t, repo, "remote", "add", "origin", "https://user:secret@example.com/acme/widget.git")
if got := p.RemoteIdentity(context.Background()); got != "example.com/acme/widget" {
t.Errorf("RemoteIdentity = %q, want %q", got, "example.com/acme/widget")
}
}
func TestResolveInput_UnbornWorkspace(t *testing.T) {
repo := initBareRepo(t) // no commits: HEAD is unborn
p := NewWorkspaceProvider(repo, nil)
got := p.ResolveInput(context.Background())
if got.ResolvedBase != "" {
t.Errorf("ResolvedBase = %q, want empty for an unborn repository", got.ResolvedBase)
}
if got.ResolvedHead != "" {
t.Errorf("ResolvedHead = %q, want empty", got.ResolvedHead)
}
if got.ExactRange != "" {
t.Errorf("ExactRange = %q, want empty", got.ExactRange)
}
}