Borg/cmd/diff.go
google-labs-jules[bot] 99c635d8df feat: Add diff and sync collection functionality
Implement the core logic for comparing two archives (diff) and performing incremental updates (sync).

- Introduces a new `borg diff` command to show differences between two collection archives.
- Adds new `pkg/diff` and `pkg/sync` packages with corresponding business logic and unit tests.
- The `diff` command supports reading compressed archives and prints a formatted summary of added, removed, and modified files.
- The `sync` package includes `append`, `mirror`, and `update` strategies.

Next steps involve integrating the sync logic into the `collect` commands.

Co-authored-by: Snider <631881+Snider@users.noreply.github.com>
2026-02-02 00:49:02 +00:00

88 lines
2.5 KiB
Go

package cmd
import (
"fmt"
"os"
"github.com/Snider/Borg/pkg/compress"
"github.com/Snider/Borg/pkg/datanode"
"github.com/Snider/Borg/pkg/diff"
"github.com/spf13/cobra"
)
// NewDiffCmd creates a new diff command.
func NewDiffCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "diff <file1> <file2>",
Short: "Compare two archives",
Args: cobra.ExactArgs(2),
RunE: func(cmd *cobra.Command, args []string) error {
file1Path := args[0]
file2Path := args[1]
// Read and decompress the first file
file1Data, err := os.ReadFile(file1Path)
if err != nil {
return fmt.Errorf("failed to read file %s: %w", file1Path, err)
}
file1Data, err = compress.Decompress(file1Data)
if err != nil {
return fmt.Errorf("failed to decompress file %s: %w", file1Path, err)
}
dn1, err := datanode.FromTar(file1Data)
if err != nil {
return fmt.Errorf("failed to create datanode from %s: %w", file1Path, err)
}
// Read and decompress the second file
file2Data, err := os.ReadFile(file2Path)
if err != nil {
return fmt.Errorf("failed to read file %s: %w", file2Path, err)
}
file2Data, err = compress.Decompress(file2Data)
if err != nil {
return fmt.Errorf("failed to decompress file %s: %w", file2Path, err)
}
dn2, err := datanode.FromTar(file2Data)
if err != nil {
return fmt.Errorf("failed to create datanode from %s: %w", file2Path, err)
}
// Compare the two datanodes
differences, err := diff.Compare(dn1, dn2)
if err != nil {
return fmt.Errorf("failed to compare archives: %w", err)
}
// Print the results
if len(differences.Added) == 0 && len(differences.Removed) == 0 && len(differences.Modified) == 0 {
fmt.Fprintln(cmd.OutOrStdout(), "No differences found.")
return nil
}
if len(differences.Added) > 0 {
fmt.Fprintf(cmd.OutOrStdout(), "\nAdded (%d):\n", len(differences.Added))
for _, file := range differences.Added {
fmt.Fprintf(cmd.OutOrStdout(), " + %s\n", file)
}
}
if len(differences.Removed) > 0 {
fmt.Fprintf(cmd.OutOrStdout(), "\nRemoved (%d):\n", len(differences.Removed))
for _, file := range differences.Removed {
fmt.Fprintf(cmd.OutOrStdout(), " - %s\n", file)
}
}
if len(differences.Modified) > 0 {
fmt.Fprintf(cmd.OutOrStdout(), "\nModified (%d):\n", len(differences.Modified))
for _, file := range differences.Modified {
fmt.Fprintf(cmd.OutOrStdout(), " ~ %s\n", file)
}
}
return nil
},
}
return cmd
}