Borg/pkg/changelog/formatters.go
google-labs-jules[bot] ac4ad6ec3e feat: Add changelog command for archive comparison
This commit introduces a new `changelog` command to the `borg` CLI. This command generates a human-readable changelog between two archive versions, addressing a key user request for tracking changes over time.

Key features of the `changelog` command include:

- **File Comparison:** Detects added, modified, and removed files between two archives.
- **Diff Statistics:** Calculates line-level insertions and deletions for modified files.
- **Multiple Output Formats:** Supports plain text, Markdown, and JSON output, controlled by a `--format` flag.
- **Remote Source Comparison:** Allows comparing a local archive against a remote GitHub repository using the `--source` flag (e.g., `--source github:org/repo`).
- **Commit Message Extraction:** When comparing archives that contain a `.git` repository, the command extracts and displays the relevant commit messages for each modified file, providing valuable context for the changes.

To support this functionality, this commit also includes:

- A new `pkg/changelog` package containing the core logic for comparing archives and generating change reports.
- A bugfix in `pkg/datanode` to ensure `fs.WalkDir` functions correctly on the root of a `DataNode`, which was necessary for iterating through archive contents.
- A modification to the `pkg/vcs` Git cloner to include the `.git` directory in the created `DataNode`, enabling commit history analysis.

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

86 lines
2.4 KiB
Go

package changelog
import (
"encoding/json"
"fmt"
"strings"
)
// FormatAsText formats the ChangeReport as plain text.
func FormatAsText(report *ChangeReport) (string, error) {
var builder strings.Builder
if len(report.Added) > 0 {
builder.WriteString(fmt.Sprintf("Added (%d files):\n", len(report.Added)))
for _, file := range report.Added {
builder.WriteString(fmt.Sprintf("- %s\n", file))
}
builder.WriteString("\n")
}
if len(report.Modified) > 0 {
builder.WriteString(fmt.Sprintf("Modified (%d files):\n", len(report.Modified)))
for _, file := range report.Modified {
builder.WriteString(fmt.Sprintf("- %s (+%d -%d lines)\n", file.Path, file.Additions, file.Deletions))
for _, commit := range file.Commits {
builder.WriteString(fmt.Sprintf(" - %s\n", strings.Split(commit, "\n")[0]))
}
}
builder.WriteString("\n")
}
if len(report.Removed) > 0 {
builder.WriteString(fmt.Sprintf("Removed (%d files):\n", len(report.Removed)))
for _, file := range report.Removed {
builder.WriteString(fmt.Sprintf("- %s\n", file))
}
builder.WriteString("\n")
}
return builder.String(), nil
}
// FormatAsMarkdown formats the ChangeReport as Markdown.
func FormatAsMarkdown(report *ChangeReport) (string, error) {
var builder strings.Builder
builder.WriteString("# Changes\n\n")
if len(report.Added) > 0 {
builder.WriteString(fmt.Sprintf("## Added (%d files)\n", len(report.Added)))
for _, file := range report.Added {
builder.WriteString(fmt.Sprintf("- `%s`\n", file))
}
builder.WriteString("\n")
}
if len(report.Modified) > 0 {
builder.WriteString(fmt.Sprintf("## Modified (%d files)\n", len(report.Modified)))
for _, file := range report.Modified {
builder.WriteString(fmt.Sprintf("- `%s` (+%d -%d lines)\n", file.Path, file.Additions, file.Deletions))
for _, commit := range file.Commits {
builder.WriteString(fmt.Sprintf(" - *%s*\n", strings.Split(commit, "\n")[0]))
}
}
builder.WriteString("\n")
}
if len(report.Removed) > 0 {
builder.WriteString(fmt.Sprintf("## Removed (%d files)\n", len(report.Removed)))
for _, file := range report.Removed {
builder.WriteString(fmt.Sprintf("- `%s`\n", file))
}
builder.WriteString("\n")
}
return builder.String(), nil
}
// FormatAsJSON formats the ChangeReport as JSON.
func FormatAsJSON(report *ChangeReport) (string, error) {
data, err := json.MarshalIndent(report, "", " ")
if err != nil {
return "", err
}
return string(data), nil
}