Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5b101bc6dd | ||
|
|
70188eb4ea |
@@ -53,10 +53,10 @@ jobs:
|
|||||||
tea login add --name ci --url https://git.mroberts.dev --token "$GITEA_TOKEN"
|
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
|
# Idempotent: drop any existing release for this tag so re-runs replace
|
||||||
# stale assets instead of failing with "already a release for this tag".
|
# 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 \
|
tea releases create \
|
||||||
--login ci \
|
--login ci \
|
||||||
--repo mroberts/sush \
|
--repo mroberts/shush \
|
||||||
--tag "$TAG" \
|
--tag "$TAG" \
|
||||||
--title "$TAG" \
|
--title "$TAG" \
|
||||||
--asset dist/shush-linux-x86_64 \
|
--asset dist/shush-linux-x86_64 \
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ jobs:
|
|||||||
ls -la release-assets/
|
ls -la release-assets/
|
||||||
|
|
||||||
- name: Create release
|
- name: Create release
|
||||||
uses: softprops/action-gh-release@v1
|
uses: softprops/action-gh-release@v2
|
||||||
with:
|
with:
|
||||||
files: release-assets/*
|
files: release-assets/*
|
||||||
fail_on_unmatched_files: true
|
fail_on_unmatched_files: true
|
||||||
|
|||||||
+1
-1
@@ -12,7 +12,7 @@ import (
|
|||||||
"github.com/carlosarraes/shush/internal/types"
|
"github.com/carlosarraes/shush/internal/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
var version = "0.6.1"
|
var version = "0.6.2"
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
var cliArgs types.CLI
|
var cliArgs types.CLI
|
||||||
|
|||||||
+72
-7
@@ -10,7 +10,11 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func DetectRepo() (*GitStatus, error) {
|
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()
|
output, err := cmd.Output()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &GitStatus{IsRepo: false}, nil
|
return &GitStatus{IsRepo: false}, nil
|
||||||
@@ -23,9 +27,62 @@ func DetectRepo() (*GitStatus, error) {
|
|||||||
}, nil
|
}, 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()
|
output, err := cmd.Output()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to get staged files: %v", err)
|
return nil, fmt.Errorf("failed to get staged files: %v", err)
|
||||||
@@ -58,9 +115,13 @@ func GetStagedChanges() ([]FileChange, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func GetUnstagedChanges() ([]FileChange, error) {
|
func GetUnstagedChanges() ([]FileChange, error) {
|
||||||
|
if _, ok := jjRoot(); ok {
|
||||||
|
return getJJChanges()
|
||||||
|
}
|
||||||
|
|
||||||
var changes []FileChange
|
var changes []FileChange
|
||||||
|
|
||||||
cmd := exec.Command("git", "diff", "--name-only")
|
cmd := exec.Command("git", "--no-pager", "diff", "--name-only")
|
||||||
output, err := cmd.Output()
|
output, err := cmd.Output()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to get unstaged modified files: %v", err)
|
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()
|
output, err = cmd.Output()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to get untracked files: %v", err)
|
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) {
|
func getLineRangesFromDiff(file string, staged bool) ([]LineRange, error) {
|
||||||
var cmd *exec.Cmd
|
var cmd *exec.Cmd
|
||||||
if staged {
|
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 {
|
} 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()
|
output, err := cmd.Output()
|
||||||
@@ -172,6 +233,10 @@ func IsInLineRanges(lineNum int, ranges []LineRange) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func GetChangesOnly() ([]FileChange, error) {
|
func GetChangesOnly() ([]FileChange, error) {
|
||||||
|
if _, ok := jjRoot(); ok {
|
||||||
|
return getJJChanges()
|
||||||
|
}
|
||||||
|
|
||||||
var allChanges []FileChange
|
var allChanges []FileChange
|
||||||
|
|
||||||
staged, err := GetStagedChanges()
|
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) {
|
func TestParseGitDiffUnified(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package git
|
|||||||
type GitStatus struct {
|
type GitStatus struct {
|
||||||
IsRepo bool
|
IsRepo bool
|
||||||
RootDir string
|
RootDir string
|
||||||
|
IsJJ bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type FileStatus int
|
type FileStatus int
|
||||||
|
|||||||
@@ -31,7 +31,11 @@ func (p *Processor) processGitChanges() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if p.cli.Verbose {
|
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
|
var changes []git.FileChange
|
||||||
|
|||||||
Reference in New Issue
Block a user