Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 52 additions & 0 deletions cmd/diff_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -327,3 +327,55 @@ func TestGitExternal7ArgsFailFast(t *testing.T) {
t.Errorf("output = %q, want substring %q", string(out), want)
}
}

func TestValidateNoMisplacedFlags(t *testing.T) {
tests := []struct {
name string
args []string
wantErr string
}{
{
name: "valid positional args",
args: []string{"main", "cmd/diff_test.go"},
},
{
name: "stdin symbol",
args: []string{"-"},
},
{
name: "flag with value after separator",
args: []string{"main", "cmd/diff_test.go", "--color=always"},
wantErr: `flag "--color=always" cannot be placed after '--'`,
},
{
name: "short flag after separator",
args: []string{"main", "cmd/diff_test.go", "-f", "sbs"},
wantErr: `flag "-f" cannot be placed after '--'`,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := validateNoMisplacedFlags(tt.args)
if tt.wantErr == "" {
if err != nil {
t.Errorf("validateNoMisplacedFlags(%v) error = %v, want nil", tt.args, err)
}
return
}
if err == nil || !strings.Contains(err.Error(), tt.wantErr) {
t.Errorf("validateNoMisplacedFlags(%v) error = %v, want substring %q", tt.args, err, tt.wantErr)
}
})
}

t.Run("existing file starting with dash", func(t *testing.T) {
dashFile := filepath.Join(t.TempDir(), "-testfile.go")
if err := os.WriteFile(dashFile, []byte("package main\n"), 0o644); err != nil {
t.Fatalf("failed to write temp file: %v", err)
}
if err := validateNoMisplacedFlags([]string{dashFile}); err != nil {
t.Errorf("validateNoMisplacedFlags(%v) error = %v, want nil", dashFile, err)
}
})
}
20 changes: 20 additions & 0 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,11 @@ Examples:
return refs, cobra.ShellCompDirectiveDefault
},
Run: func(cmd *cobra.Command, args []string) {
if err := validateNoMisplacedFlags(args); err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}

cfg, err := config.Load()
if err != nil {
fmt.Fprintf(os.Stderr, "Warning: %v\n", err)
Expand Down Expand Up @@ -827,6 +832,21 @@ func isFileOrDevNull(path string) bool {
return err == nil && !info.IsDir()
}

// Cobra stops parsing flags at '--' and treats everything after as a positional
// arg. Catch flags accidentally put after '--', unless they're real files starting with '-'.
func validateNoMisplacedFlags(args []string) error {
for _, arg := range args {
if strings.HasPrefix(arg, "-") && arg != "-" && !isFileOrDevNull(arg) && !git.IsTrackedFile(".", arg) {
return fmt.Errorf("flag %q cannot be placed after '--'\n\n"+
"In CLI syntax, '--' marks the end of options; all subsequent arguments are treated as paths.\n"+
"Place flags before '--' or omit '--':\n"+
" diffm [flags] [refs...] [--] [paths...]\n"+
" diffm [refs...] [paths...] [flags]", arg)
}
}
return nil
}

func runFileDiff(cmd *cobra.Command, fileA, fileB, displayPath string, format string, ignoreComments bool, parseErrorLimit int, sizeLimitKB int, lineLimitLines int, noPager bool) {
uiMode, _ := cmd.Flags().GetBool("ui")
fullMode, _ := cmd.Flags().GetBool("full")
Expand Down
Loading