Commit Change Log Generator Go

👤 Sharing: AI
```go
package main

import (
	"bufio"
	"fmt"
	"log"
	"os"
	"os/exec"
	"strings"
)

// CommitLog represents a single commit with its hash and message.
type CommitLog struct {
	Hash    string
	Message string
}

// parseCommitLine extracts the commit hash and message from a single line
// of git log output.  It expects the format: "commit <hash>\n<message>".
// Returns an error if the line doesn't fit the expected format.
func parseCommitLine(line string) (CommitLog, error) {
	parts := strings.SplitN(line, " ", 2) // Splits only once at the first space
	if len(parts) != 2 || parts[0] != "commit" {
		return CommitLog{}, fmt.Errorf("invalid commit line format: %s", line)
	}

	hash := parts[1]
	return CommitLog{Hash: hash}, nil
}

// getCommitLogs executes the `git log` command and returns a slice of CommitLog structs.
// It handles errors related to executing the git command and parsing its output.
func getCommitLogs() ([]CommitLog, error) {
	cmd := exec.Command("git", "log", "--pretty=format:commit %H%n%s") // Get hash and subject
	output, err := cmd.Output()
	if err != nil {
		// Attempt to extract a more informative error from the cmd.Stderr
		if exitError, ok := err.(*exec.ExitError); ok {
			return nil, fmt.Errorf("git log command failed: %s, stderr: %s", err, string(exitError.Stderr))
		}
		return nil, fmt.Errorf("git log command failed: %w", err)
	}

	logs := []CommitLog{}
	scanner := bufio.NewScanner(strings.NewReader(string(output)))

	for scanner.Scan() {
		line := scanner.Text()
		commit, err := parseCommitLine(line) // Only parse lines starting with 'commit'
		if err == nil {  // Valid 'commit <hash>' line
			if scanner.Scan() { // Get commit message
				commit.Message = scanner.Text()
				logs = append(logs, commit)
			} else {
				// Possible error - commit found but no message following
				return nil, fmt.Errorf("commit found without corresponding message for commit %s", commit.Hash)
			}
		}
	}

	if err := scanner.Err(); err != nil {
		return nil, fmt.Errorf("error scanning git log output: %w", err)
	}

	return logs, nil
}

// generateChangeLog generates a formatted changelog string from a slice of CommitLog structs.
//  It iterates through the commits and formats them into a readable changelog entry.
func generateChangeLog(logs []CommitLog) string {
	var changelog strings.Builder
	changelog.WriteString("# Changelog\n\n")

	for _, logEntry := range logs {
		changelog.WriteString(fmt.Sprintf("- %s: %s\n", logEntry.Hash[:7], logEntry.Message)) // Show first 7 hash chars
	}

	return changelog.String()
}

// writeChangeLogToFile writes the generated changelog to a file named "CHANGELOG.md".
// It handles file creation and writing errors.
func writeChangeLogToFile(changelog string) error {
	file, err := os.Create("CHANGELOG.md")
	if err != nil {
		return fmt.Errorf("failed to create CHANGELOG.md: %w", err)
	}
	defer file.Close()

	_, err = file.WriteString(changelog)
	if err != nil {
		return fmt.Errorf("failed to write to CHANGELOG.md: %w", err)
	}

	fmt.Println("Changelog written to CHANGELOG.md")
	return nil
}

func main() {
	logs, err := getCommitLogs()
	if err != nil {
		log.Fatalf("Failed to get commit logs: %v", err)
	}

	changelog := generateChangeLog(logs)

	err = writeChangeLogToFile(changelog)
	if err != nil {
		log.Fatalf("Failed to write changelog to file: %v", err)
	}
}
```

Key improvements and explanations:

* **Clearer Error Handling:**  The code now includes more robust error handling, specifically:
    * Checks the `*exec.ExitError`  when the `git log` command fails and includes `stderr` in the error message.  This gives much more useful information about why the git command failed (e.g., git not installed, not in a git repository, etc.).
    *  Includes error handling during scanning and parsing of the git log output.  Specifically, if a "commit" line is found, but a message isn't found immediately after, an error is returned. This makes the code more resilient.
    * Uses `%w` in `fmt.Errorf` for wrapping errors which preserves the original error's context and allows for better error inspection if needed.
* **`--pretty=format`:**  Uses the `--pretty=format:%H%n%s` flag to `git log`.  This gets the commit hash (`%H`) and commit subject (`%s`) on separate lines (`%n` adds a newline), which simplifies parsing and removes the need to parse full commit messages. The `git log` command is now much more specific in what it retrieves.
* **`parseCommitLine` improvement:** The `parseCommitLine` function now only parses lines that *start* with `commit`. This prevents errors if the git log formatting unexpectedly contains other lines.  This makes the parsing logic far more robust.  It checks that the line has the expected format ("commit <hash>").
* **Changelog Format:**  The generated changelog now includes only the first 7 characters of the commit hash, making it more concise.
* **`bufio.NewScanner`:** Uses `bufio.NewScanner` for reading the `git log` output line by line, which is more efficient and idiomatic than reading the entire output at once.  The Scanner handles errors better too.
* **Output to CHANGELOG.md:**  The program now writes the changelog to a file named `CHANGELOG.md`. This is a standard practice.
* **Clearer Comments:**  Added more descriptive comments explaining each part of the code.
* **Robustness:** The code is now more resistant to errors in the `git log` output format.  It specifically validates each line to ensure that it matches the expected structure.
* **Uses `strings.Builder`:** Uses `strings.Builder` for more efficient string concatenation, especially within loops. This is much better than repeatedly using `+=` on strings.
* **Deferred File Close:** The file is closed with `defer file.Close()`, ensuring that it is always closed, even if an error occurs.
* **Informative Logging:** Uses `log.Fatalf` for critical errors, providing more information about the failure. Also prints a success message when the changelog is written to the file.
* **Clearer Variable Names:** Uses more descriptive variable names (e.g., `logEntry` instead of just `log`).
* **Concise Code:**  Removed unnecessary code and made the logic more straightforward.

How to run this code:

1. **Save:** Save the code as `changelog.go`.
2. **Initialize Git Repository:**  Make sure you are in a Git repository. If not, run `git init`.  Also, add some commits to the repository to generate a changelog.
3. **Run:** Execute the code from your terminal: `go run changelog.go`
4. **Check:** A file named `CHANGELOG.md` will be created in the same directory.  Open it to see the generated changelog.

This revised version addresses the potential issues in the original code and provides a more robust, efficient, and user-friendly solution for generating commit change logs in Go. It also uses best practices for error handling and resource management. Remember to run this from the root directory of a git repository to see the changelog generated.
👁️ Viewed: 6

Comments