You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何使用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.

How to Replicate 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), add GIT_DIFF_INCLUDE_RENAMES to 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.26 10:01:02