Skip to content

Commit 961816f

Browse files
authored
Add --type filter to diff command (#75)
* Add --type filter to diff command Adds a --type/-t flag to filter diff output by dependency type (runtime, development, etc.), consistent with the list command. The diff command now: - Captures and preserves DependencyType in DiffEntry - Supports filtering via --type flag - Displays non-runtime types in brackets (e.g., [development]) - Includes dependency_type in JSON output related to #71
1 parent 59efc9b commit 961816f

File tree

3 files changed

+156
-14
lines changed

3 files changed

+156
-14
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -380,6 +380,8 @@ git pkgs diff # HEAD vs working tree
380380
git pkgs diff --from=abc123 --to=def456 # between two commits
381381
git pkgs diff --from=HEAD~10 # HEAD~10 vs working tree
382382
git pkgs diff main..feature # compare branches
383+
git pkgs diff --type=development # only dev dependency changes
384+
git pkgs diff --ecosystem=npm # filter by ecosystem
383385
```
384386

385387
With no arguments, compares HEAD against the working tree (like `git diff`). Shows added, removed, and modified packages with version info.

cmd/diff.go

Lines changed: 41 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ Supports range syntax (main..feature) or explicit --from/--to flags.`,
2626
diffCmd.Flags().String("from", "", "Starting commit (default: HEAD)")
2727
diffCmd.Flags().String("to", "", "Ending commit (default: working tree)")
2828
diffCmd.Flags().StringP("ecosystem", "e", "", "Filter by ecosystem")
29+
diffCmd.Flags().StringP("type", "t", "", "Filter by dependency type (runtime, development, etc.)")
2930
diffCmd.Flags().StringP("format", "f", "text", "Output format: text, json")
3031
parent.AddCommand(diffCmd)
3132
}
@@ -40,6 +41,7 @@ type DiffEntry struct {
4041
Name string `json:"name"`
4142
Ecosystem string `json:"ecosystem,omitempty"`
4243
ManifestPath string `json:"manifest_path"`
44+
DependencyType string `json:"dependency_type,omitempty"`
4345
FromRequirement string `json:"from_requirement,omitempty"`
4446
ToRequirement string `json:"to_requirement,omitempty"`
4547
}
@@ -48,6 +50,7 @@ func runDiff(cmd *cobra.Command, args []string) error {
4850
fromRef, _ := cmd.Flags().GetString("from")
4951
toRef, _ := cmd.Flags().GetString("to")
5052
ecosystem, _ := cmd.Flags().GetString("ecosystem")
53+
depType, _ := cmd.Flags().GetString("type")
5154
format, _ := cmd.Flags().GetString("format")
5255

5356
// Parse range syntax if provided
@@ -85,9 +88,9 @@ func runDiff(cmd *cobra.Command, args []string) error {
8588
return err
8689
}
8790

88-
// Apply ecosystem filter
89-
if ecosystem != "" {
90-
result = filterDiffResult(result, ecosystem)
91+
// Apply filters
92+
if ecosystem != "" || depType != "" {
93+
result = filterDiffResult(result, ecosystem, depType)
9194
}
9295

9396
if len(result.Added) == 0 && len(result.Modified) == 0 && len(result.Removed) == 0 {
@@ -176,16 +179,18 @@ func computeDiff(fromDeps, toDeps []database.Dependency) *DiffResult {
176179
Name: toDep.Name,
177180
Ecosystem: toDep.Ecosystem,
178181
ManifestPath: toDep.ManifestPath,
182+
DependencyType: toDep.DependencyType,
179183
FromRequirement: fromDep.Requirement,
180184
ToRequirement: toDep.Requirement,
181185
})
182186
}
183187
} else {
184188
result.Added = append(result.Added, DiffEntry{
185-
Name: toDep.Name,
186-
Ecosystem: toDep.Ecosystem,
187-
ManifestPath: toDep.ManifestPath,
188-
ToRequirement: toDep.Requirement,
189+
Name: toDep.Name,
190+
Ecosystem: toDep.Ecosystem,
191+
ManifestPath: toDep.ManifestPath,
192+
DependencyType: toDep.DependencyType,
193+
ToRequirement: toDep.Requirement,
189194
})
190195
}
191196
}
@@ -197,6 +202,7 @@ func computeDiff(fromDeps, toDeps []database.Dependency) *DiffResult {
197202
Name: fromDep.Name,
198203
Ecosystem: fromDep.Ecosystem,
199204
ManifestPath: fromDep.ManifestPath,
205+
DependencyType: fromDep.DependencyType,
200206
FromRequirement: fromDep.Requirement,
201207
})
202208
}
@@ -219,23 +225,35 @@ func sortDiffEntries(entries []DiffEntry) {
219225
})
220226
}
221227

222-
func filterDiffResult(result *DiffResult, ecosystem string) *DiffResult {
228+
func filterDiffResult(result *DiffResult, ecosystem, depType string) *DiffResult {
223229
filtered := &DiffResult{}
224230

225231
for _, e := range result.Added {
226-
if strings.EqualFold(e.Ecosystem, ecosystem) {
227-
filtered.Added = append(filtered.Added, e)
232+
if ecosystem != "" && !strings.EqualFold(e.Ecosystem, ecosystem) {
233+
continue
228234
}
235+
if depType != "" && !strings.EqualFold(e.DependencyType, depType) {
236+
continue
237+
}
238+
filtered.Added = append(filtered.Added, e)
229239
}
230240
for _, e := range result.Modified {
231-
if strings.EqualFold(e.Ecosystem, ecosystem) {
232-
filtered.Modified = append(filtered.Modified, e)
241+
if ecosystem != "" && !strings.EqualFold(e.Ecosystem, ecosystem) {
242+
continue
243+
}
244+
if depType != "" && !strings.EqualFold(e.DependencyType, depType) {
245+
continue
233246
}
247+
filtered.Modified = append(filtered.Modified, e)
234248
}
235249
for _, e := range result.Removed {
236-
if strings.EqualFold(e.Ecosystem, ecosystem) {
237-
filtered.Removed = append(filtered.Removed, e)
250+
if ecosystem != "" && !strings.EqualFold(e.Ecosystem, ecosystem) {
251+
continue
252+
}
253+
if depType != "" && !strings.EqualFold(e.DependencyType, depType) {
254+
continue
238255
}
256+
filtered.Removed = append(filtered.Removed, e)
239257
}
240258

241259
return filtered
@@ -255,6 +273,9 @@ func outputDiffText(cmd *cobra.Command, result *DiffResult) error {
255273
if e.ToRequirement != "" {
256274
line += fmt.Sprintf(" %s", e.ToRequirement)
257275
}
276+
if e.DependencyType != "" && e.DependencyType != "runtime" {
277+
line += fmt.Sprintf(" [%s]", e.DependencyType)
278+
}
258279
line += fmt.Sprintf(" %s", Dim("("+e.ManifestPath+")"))
259280
_, _ = fmt.Fprintln(cmd.OutOrStdout(), line)
260281
}
@@ -265,6 +286,9 @@ func outputDiffText(cmd *cobra.Command, result *DiffResult) error {
265286
_, _ = fmt.Fprintln(cmd.OutOrStdout(), Bold("Modified:"))
266287
for _, e := range result.Modified {
267288
line := fmt.Sprintf(" %s %s %s -> %s", Yellow("~"), Yellow(e.Name), Dim(e.FromRequirement), e.ToRequirement)
289+
if e.DependencyType != "" && e.DependencyType != "runtime" {
290+
line += fmt.Sprintf(" [%s]", e.DependencyType)
291+
}
268292
line += fmt.Sprintf(" %s", Dim("("+e.ManifestPath+")"))
269293
_, _ = fmt.Fprintln(cmd.OutOrStdout(), line)
270294
}
@@ -278,6 +302,9 @@ func outputDiffText(cmd *cobra.Command, result *DiffResult) error {
278302
if e.FromRequirement != "" {
279303
line += fmt.Sprintf(" %s", e.FromRequirement)
280304
}
305+
if e.DependencyType != "" && e.DependencyType != "runtime" {
306+
line += fmt.Sprintf(" [%s]", e.DependencyType)
307+
}
281308
line += fmt.Sprintf(" %s", Dim("("+e.ManifestPath+")"))
282309
_, _ = fmt.Fprintln(cmd.OutOrStdout(), line)
283310
}

cmd/diff_test.go

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,119 @@ jobs:
241241
}
242242
}
243243

244+
func TestDiff_TypeFilter(t *testing.T) {
245+
// Create a temp repo
246+
dir := t.TempDir()
247+
248+
// Initialize git
249+
run := func(args ...string) {
250+
cmd := exec.Command("git", args...)
251+
cmd.Dir = dir
252+
if out, err := cmd.CombinedOutput(); err != nil {
253+
t.Fatalf("git %v failed: %v\n%s", args, err, out)
254+
}
255+
}
256+
257+
run("init")
258+
run("config", "user.email", "[email protected]")
259+
run("config", "user.name", "Test")
260+
261+
// Create package.json with both runtime and dev dependencies
262+
pkgJSON := `{
263+
"name": "test",
264+
"dependencies": {
265+
"lodash": "^4.0.0"
266+
},
267+
"devDependencies": {
268+
"eslint": "^8.0.0"
269+
}
270+
}`
271+
if err := os.WriteFile(filepath.Join(dir, "package.json"), []byte(pkgJSON), 0644); err != nil {
272+
t.Fatal(err)
273+
}
274+
275+
run("add", "package.json")
276+
run("commit", "-m", "Initial")
277+
278+
// Update both dependencies
279+
pkgJSON2 := `{
280+
"name": "test",
281+
"dependencies": {
282+
"lodash": "^4.1.0"
283+
},
284+
"devDependencies": {
285+
"eslint": "^8.1.0"
286+
}
287+
}`
288+
if err := os.WriteFile(filepath.Join(dir, "package.json"), []byte(pkgJSON2), 0644); err != nil {
289+
t.Fatal(err)
290+
}
291+
292+
// Change to repo dir
293+
oldDir, _ := os.Getwd()
294+
if err := os.Chdir(dir); err != nil {
295+
t.Fatal(err)
296+
}
297+
defer func() { _ = os.Chdir(oldDir) }()
298+
299+
// Run diff with --type=runtime (should only show lodash)
300+
rootCmd := NewRootCmd()
301+
rootCmd.SetArgs([]string{"diff", "--type=runtime"})
302+
303+
var buf bytes.Buffer
304+
rootCmd.SetOut(&buf)
305+
rootCmd.SetErr(&bytes.Buffer{})
306+
if err := rootCmd.Execute(); err != nil {
307+
t.Fatalf("diff command failed: %v", err)
308+
}
309+
out := buf.String()
310+
311+
if !containsString(out, "lodash") {
312+
t.Errorf("expected lodash (runtime) to be shown, got:\n%s", out)
313+
}
314+
if containsString(out, "eslint") {
315+
t.Errorf("expected eslint (development) to NOT be shown, got:\n%s", out)
316+
}
317+
318+
// Run diff with --type=development (should only show eslint)
319+
rootCmd = NewRootCmd()
320+
rootCmd.SetArgs([]string{"diff", "--type=development"})
321+
322+
buf.Reset()
323+
rootCmd.SetOut(&buf)
324+
rootCmd.SetErr(&bytes.Buffer{})
325+
if err := rootCmd.Execute(); err != nil {
326+
t.Fatalf("diff command failed: %v", err)
327+
}
328+
out = buf.String()
329+
330+
if containsString(out, "lodash") {
331+
t.Errorf("expected lodash (runtime) to NOT be shown, got:\n%s", out)
332+
}
333+
if !containsString(out, "eslint") {
334+
t.Errorf("expected eslint (development) to be shown, got:\n%s", out)
335+
}
336+
337+
// Run diff without filter (should show both)
338+
rootCmd = NewRootCmd()
339+
rootCmd.SetArgs([]string{"diff"})
340+
341+
buf.Reset()
342+
rootCmd.SetOut(&buf)
343+
rootCmd.SetErr(&bytes.Buffer{})
344+
if err := rootCmd.Execute(); err != nil {
345+
t.Fatalf("diff command failed: %v", err)
346+
}
347+
out = buf.String()
348+
349+
if !containsString(out, "lodash") {
350+
t.Errorf("expected lodash to be shown, got:\n%s", out)
351+
}
352+
if !containsString(out, "eslint") {
353+
t.Errorf("expected eslint to be shown, got:\n%s", out)
354+
}
355+
}
356+
244357
func containsString(s, substr string) bool {
245358
for i := 0; i <= len(s)-len(substr); i++ {
246359
if s[i:i+len(substr)] == substr {

0 commit comments

Comments
 (0)