如何使用libgit2执行‘git log filename’及获取指定文件修订版本?
Great question! Replicating git log filename with libgit2 requires a bit of manual work since the git_revwalk APIs don't include built-in filename filtering—let's break down how to do it step by step.
git log filename with libgit2 Core Idea Behind git log filename
When you run git log filename, Git traverses your commit history and shows only commits that modified the specified file (including adding, deleting, or renaming it). Libgit2's git_revwalk handles the commit traversal, but you'll need to add custom logic to check each commit for changes to your target file.
Step-by-Step Implementation
Here's a practical C example (the primary language for libgit2) that mirrors the behavior of git log filename:
1. Initialize Repository & Revwalk
First, open your target repository and set up the revision walker to traverse commits starting from HEAD:
#include <git2.h> #include <stdio.h> int main() { git_libgit2_init(); git_repository *repo = NULL; if (git_repository_open(&repo, "/path/to/your/repo") != 0) { fprintf(stderr, "Failed to open repository\n"); return 1; } git_revwalk *walker = NULL; git_revwalk_new(&walker, repo); git_revwalk_sorting(walker, GIT_SORT_TOPOLOGICAL | GIT_SORT_REVERSE); // Match git log's default order git_revwalk_push_head(walker);
2. Traverse Commits & Check File Changes
Loop through each commit, and for each one, check if the target file was modified:
const char *target_file = "path/to/your/file.txt"; // Relative to repo root git_oid commit_oid; while (git_revwalk_next(&commit_oid, walker) == 0) { git_commit *commit = NULL; git_commit_lookup(&commit, repo, &commit_oid); int file_modified = 0; size_t parent_count = git_commit_parentcount(commit); if (parent_count == 0) { // Root commit: Check if the file exists in this commit's tree git_tree *tree = NULL; git_commit_tree(&tree, commit); git_tree_entry entry; if (git_tree_entry_bypath(&entry, tree, target_file) == 0) { file_modified = 1; } git_tree_free(tree); } else { // Compare with the first parent to check for file changes git_commit *parent = NULL; git_commit_parent(&parent, commit, 0); git_tree *commit_tree = NULL, *parent_tree = NULL; git_commit_tree(&commit_tree, commit); git_commit_tree(&parent_tree, parent); // Set up diff options to filter only the target file git_diff_options diff_opts = GIT_DIFF_OPTIONS_INIT; diff_opts.pathspec.count = 1; diff_opts.pathspec.strings = &target_file; // Uncomment below to track renames (matches git log --follow) // diff_opts.flags |= GIT_DIFF_INCLUDE_RENAMES; git_diff *diff = NULL; if (git_diff_tree_to_tree(&diff, repo, parent_tree, commit_tree, &diff_opts) == 0) { if (git_diff_num_deltas(diff) > 0) { file_modified = 1; } git_diff_free(diff); } git_tree_free(parent_tree); git_tree_free(commit_tree); git_commit_free(parent); } // If the file was modified, print commit details if (file_modified) { printf("Commit: %s\n", git_oid_tostr_s(&commit_oid)); printf("Author: %s <%s>\n", git_commit_author(commit)->name, git_commit_author(commit)->email); printf("Date: %s\n", git_time_to_string(&git_commit_author(commit)->when)); printf("Message: %s\n\n", git_commit_message(commit)); } git_commit_free(commit); }
3. Cleanup Resources
Don't forget to free all allocated libgit2 objects:
git_revwalk_free(walker); git_repository_free(repo); git_libgit2_shutdown(); return 0; }
Key Details & Edge Cases
- Pathspec: Always use a path relative to the repository root (not an absolute path) when specifying the target file.
- Renames: To replicate
git log --follow filename(tracking renames), addGIT_DIFF_INCLUDE_RENAMESto the diff options flags. This will detect when the file was renamed and include those commits. - Merge Commits: The example checks only the first parent of a merge commit, which matches Git's default behavior for
git log filename. If you want to check all parents, you'll need to loop through each parent and run the diff check for each.
内容的提问来源于stack exchange,提问作者user3196254

