Compare commits
2
Commits
33b1b2370b
...
5b101bc6dd
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5b101bc6dd | ||
|
|
70188eb4ea |
@@ -53,10 +53,10 @@ jobs:
|
||||
tea login add --name ci --url https://git.mroberts.dev --token "$GITEA_TOKEN"
|
||||
# Idempotent: drop any existing release for this tag so re-runs replace
|
||||
# stale assets instead of failing with "already a release for this tag".
|
||||
tea releases delete -y --login ci --repo mroberts/sush "$TAG" >/dev/null 2>&1 || true
|
||||
tea releases delete -y --login ci --repo mroberts/shush "$TAG" >/dev/null 2>&1 || true
|
||||
tea releases create \
|
||||
--login ci \
|
||||
--repo mroberts/sush \
|
||||
--repo mroberts/shush \
|
||||
--tag "$TAG" \
|
||||
--title "$TAG" \
|
||||
--asset dist/shush-linux-x86_64 \
|
||||
|
||||
@@ -95,7 +95,7 @@ jobs:
|
||||
ls -la release-assets/
|
||||
|
||||
- name: Create release
|
||||
uses: softprops/action-gh-release@v1
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
files: release-assets/*
|
||||
fail_on_unmatched_files: true
|
||||
|
||||
+1
-1
@@ -12,7 +12,7 @@ import (
|
||||
"github.com/carlosarraes/shush/internal/types"
|
||||
)
|
||||
|
||||
var version = "0.6.1"
|
||||
var version = "0.6.2"
|
||||
|
||||
func main() {
|
||||
var cliArgs types.CLI
|
||||
|
||||
+72
-7
@@ -10,7 +10,11 @@ import (
|
||||
)
|
||||
|
||||
func DetectRepo() (*GitStatus, error) {
|
||||
cmd := exec.Command("git", "rev-parse", "--show-toplevel")
|
||||
if root, ok := jjRoot(); ok {
|
||||
return &GitStatus{IsRepo: true, RootDir: root, IsJJ: true}, nil
|
||||
}
|
||||
|
||||
cmd := exec.Command("git", "--no-pager", "rev-parse", "--show-toplevel")
|
||||
output, err := cmd.Output()
|
||||
if err != nil {
|
||||
return &GitStatus{IsRepo: false}, nil
|
||||
@@ -23,9 +27,62 @@ func DetectRepo() (*GitStatus, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
func GetStagedChanges() ([]FileChange, error) {
|
||||
func jjRoot() (string, bool) {
|
||||
output, err := exec.Command("jj", "--no-pager", "root").Output()
|
||||
if err != nil {
|
||||
return "", false
|
||||
}
|
||||
return strings.TrimSpace(string(output)), true
|
||||
}
|
||||
|
||||
cmd := exec.Command("git", "diff", "--cached", "--name-only")
|
||||
func getJJChanges() ([]FileChange, error) {
|
||||
output, err := exec.Command("jj", "--no-pager", "diff", "--name-only").Output()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to list jj changes: %v", err)
|
||||
}
|
||||
|
||||
var changes []FileChange
|
||||
for _, file := range strings.Split(strings.TrimSpace(string(output)), "\n") {
|
||||
if file == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
lineRanges, err := getJJLineRanges(file)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get line ranges for %s: %v", file, err)
|
||||
}
|
||||
|
||||
if len(lineRanges) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
changes = append(changes, FileChange{
|
||||
Path: file,
|
||||
Status: StatusUnstaged,
|
||||
LineRanges: lineRanges,
|
||||
})
|
||||
}
|
||||
|
||||
return changes, nil
|
||||
}
|
||||
|
||||
func getJJLineRanges(file string) ([]LineRange, error) {
|
||||
cmd := exec.Command("jj", "--no-pager", "diff", "--git", "--context=0", "--", file)
|
||||
|
||||
output, err := cmd.Output()
|
||||
if err != nil {
|
||||
return []LineRange{}, nil
|
||||
}
|
||||
|
||||
return ParseDiffUnified(string(output))
|
||||
}
|
||||
|
||||
func GetStagedChanges() ([]FileChange, error) {
|
||||
if _, ok := jjRoot(); ok {
|
||||
return getJJChanges()
|
||||
}
|
||||
|
||||
cmd := exec.Command("git", "--no-pager", "diff", "--cached", "--name-only")
|
||||
output, err := cmd.Output()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get staged files: %v", err)
|
||||
@@ -58,9 +115,13 @@ func GetStagedChanges() ([]FileChange, error) {
|
||||
}
|
||||
|
||||
func GetUnstagedChanges() ([]FileChange, error) {
|
||||
if _, ok := jjRoot(); ok {
|
||||
return getJJChanges()
|
||||
}
|
||||
|
||||
var changes []FileChange
|
||||
|
||||
cmd := exec.Command("git", "diff", "--name-only")
|
||||
cmd := exec.Command("git", "--no-pager", "diff", "--name-only")
|
||||
output, err := cmd.Output()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get unstaged modified files: %v", err)
|
||||
@@ -84,7 +145,7 @@ func GetUnstagedChanges() ([]FileChange, error) {
|
||||
})
|
||||
}
|
||||
|
||||
cmd = exec.Command("git", "ls-files", "--others", "--exclude-standard")
|
||||
cmd = exec.Command("git", "--no-pager", "ls-files", "--others", "--exclude-standard")
|
||||
output, err = cmd.Output()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get untracked files: %v", err)
|
||||
@@ -109,9 +170,9 @@ func GetUnstagedChanges() ([]FileChange, error) {
|
||||
func getLineRangesFromDiff(file string, staged bool) ([]LineRange, error) {
|
||||
var cmd *exec.Cmd
|
||||
if staged {
|
||||
cmd = exec.Command("git", "diff", "--no-ext-diff", "--cached", "--unified=0", file)
|
||||
cmd = exec.Command("git", "--no-pager", "diff", "--no-ext-diff", "--cached", "--unified=0", file)
|
||||
} else {
|
||||
cmd = exec.Command("git", "diff", "--no-ext-diff", "--unified=0", file)
|
||||
cmd = exec.Command("git", "--no-pager", "diff", "--no-ext-diff", "--unified=0", file)
|
||||
}
|
||||
|
||||
output, err := cmd.Output()
|
||||
@@ -172,6 +233,10 @@ func IsInLineRanges(lineNum int, ranges []LineRange) bool {
|
||||
}
|
||||
|
||||
func GetChangesOnly() ([]FileChange, error) {
|
||||
if _, ok := jjRoot(); ok {
|
||||
return getJJChanges()
|
||||
}
|
||||
|
||||
var allChanges []FileChange
|
||||
|
||||
staged, err := GetStagedChanges()
|
||||
|
||||
@@ -54,6 +54,111 @@ func TestGetLineRangesFromDiffIgnoresExternalDiff(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func runJJ(t *testing.T, dir string, args ...string) {
|
||||
t.Helper()
|
||||
cmd := exec.Command("jj", args...)
|
||||
cmd.Dir = dir
|
||||
cmd.Env = append(os.Environ(), "JJ_USER=t", "JJ_EMAIL=t@t")
|
||||
if out, err := cmd.CombinedOutput(); err != nil {
|
||||
t.Fatalf("jj %v: %v\n%s", args, err, out)
|
||||
}
|
||||
}
|
||||
|
||||
func TestJJRepoUsesJJDiffNotGitIndex(t *testing.T) {
|
||||
if _, err := exec.LookPath("jj"); err != nil {
|
||||
t.Skip("jj not installed")
|
||||
}
|
||||
|
||||
dir := t.TempDir()
|
||||
runGit(t, dir, "init", "-q")
|
||||
runGit(t, dir, "config", "user.email", "t@t")
|
||||
runGit(t, dir, "config", "user.name", "t")
|
||||
|
||||
path := filepath.Join(dir, "f.py")
|
||||
original := "a = 1\nb = 2\nc = 3\nd = 4\ne = 5\n"
|
||||
if err := os.WriteFile(path, []byte(original), 0644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
runGit(t, dir, "add", "f.py")
|
||||
runGit(t, dir, "commit", "-qm", "init")
|
||||
runJJ(t, dir, "git", "init", "--colocate")
|
||||
|
||||
modified := "a = 1\nb = 2\nc = 3 changed\nd = 4\ne = 5\n"
|
||||
if err := os.WriteFile(path, []byte(modified), 0644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
runGit(t, dir, "rm", "-q", "--cached", "f.py")
|
||||
|
||||
wd, err := os.Getwd()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.Chdir(wd)
|
||||
if err := os.Chdir(dir); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
changes, err := GetChangesOnly()
|
||||
if err != nil {
|
||||
t.Fatalf("GetChangesOnly: %v", err)
|
||||
}
|
||||
|
||||
if len(changes) != 1 {
|
||||
t.Fatalf("changes = %v, want exactly one file (duplicated per staged/unstaged pass?)", changes)
|
||||
}
|
||||
|
||||
want := []LineRange{{Start: 3, End: 3}}
|
||||
got := changes[0].LineRanges
|
||||
if len(got) != len(want) || got[0] != want[0] {
|
||||
t.Fatalf("ranges = %v, want %v (whole file treated as changed?)", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestJJDeletionOnlyChangeIsNotWholeFile(t *testing.T) {
|
||||
if _, err := exec.LookPath("jj"); err != nil {
|
||||
t.Skip("jj not installed")
|
||||
}
|
||||
|
||||
dir := t.TempDir()
|
||||
runGit(t, dir, "init", "-q")
|
||||
runGit(t, dir, "config", "user.email", "t@t")
|
||||
runGit(t, dir, "config", "user.name", "t")
|
||||
|
||||
path := filepath.Join(dir, "f.py")
|
||||
original := "a = 1 # keep\nb = 2\nc = 3\n"
|
||||
if err := os.WriteFile(path, []byte(original), 0644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
runGit(t, dir, "add", "f.py")
|
||||
runGit(t, dir, "commit", "-qm", "init")
|
||||
runJJ(t, dir, "git", "init", "--colocate")
|
||||
|
||||
if err := os.WriteFile(path, []byte("a = 1 # keep\nb = 2\n"), 0644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
wd, err := os.Getwd()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.Chdir(wd)
|
||||
if err := os.Chdir(dir); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
changes, err := GetChangesOnly()
|
||||
if err != nil {
|
||||
t.Fatalf("GetChangesOnly: %v", err)
|
||||
}
|
||||
|
||||
for _, c := range changes {
|
||||
if c.Path == "f.py" && len(c.LineRanges) == 0 {
|
||||
t.Fatal("deletion-only change yielded empty ranges, which means whole-file processing")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseGitDiffUnified(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
|
||||
@@ -3,6 +3,7 @@ package git
|
||||
type GitStatus struct {
|
||||
IsRepo bool
|
||||
RootDir string
|
||||
IsJJ bool
|
||||
}
|
||||
|
||||
type FileStatus int
|
||||
|
||||
@@ -31,7 +31,11 @@ func (p *Processor) processGitChanges() error {
|
||||
}
|
||||
|
||||
if p.cli.Verbose {
|
||||
fmt.Printf("Git repository detected: %s\n", gitStatus.RootDir)
|
||||
vcs := "Git"
|
||||
if gitStatus.IsJJ {
|
||||
vcs = "Jujutsu"
|
||||
}
|
||||
fmt.Printf("%s repository detected: %s\n", vcs, gitStatus.RootDir)
|
||||
}
|
||||
|
||||
var changes []git.FileChange
|
||||
|
||||
Reference in New Issue
Block a user