2 * libgit2 "blame" example - shows how to use the blame API
4 * Written by the libgit2 contributors
6 * To the extent possible under law, the author(s) have dedicated all copyright
7 * and related and neighboring rights to this software to the public domain
8 * worldwide. This software is distributed without any warranty.
10 * You should have received a copy of the CC0 Public Domain Dedication along
11 * with this software. If not, see
12 * <http://creativecommons.org/publicdomain/zero/1.0/>.
18 #define snprintf sprintf_s
19 #define strcasecmp strcmpi
23 * This example demonstrates how to invoke the libgit2 blame API to roughly
24 * simulate the output of `git blame` and a few of its command line arguments.
36 static void parse_opts(struct opts
*o
, int argc
, char *argv
[]);
38 int main(int argc
, char *argv
[])
40 int i
, line
, break_on_null_hunk
;
41 char spec
[1024] = {0};
44 git_repository
*repo
= NULL
;
45 git_revspec revspec
= {0};
46 git_blame_options blameopts
= GIT_BLAME_OPTIONS_INIT
;
47 git_blame
*blame
= NULL
;
53 parse_opts(&o
, argc
, argv
);
54 if (o
.M
) blameopts
.flags
|= GIT_BLAME_TRACK_COPIES_SAME_COMMIT_MOVES
;
55 if (o
.C
) blameopts
.flags
|= GIT_BLAME_TRACK_COPIES_SAME_COMMIT_COPIES
;
56 if (o
.F
) blameopts
.flags
|= GIT_BLAME_FIRST_PARENT
;
58 /** Open the repository. */
59 check_lg2(git_repository_open_ext(&repo
, ".", 0, NULL
), "Couldn't open repository", NULL
);
62 * The commit range comes in "commitish" form. Use the rev-parse API to
63 * nail down the end points.
66 check_lg2(git_revparse(&revspec
, repo
, o
.commitspec
), "Couldn't parse commit spec", NULL
);
67 if (revspec
.flags
& GIT_REVPARSE_SINGLE
) {
68 git_oid_cpy(&blameopts
.newest_commit
, git_object_id(revspec
.from
));
69 git_object_free(revspec
.from
);
71 git_oid_cpy(&blameopts
.oldest_commit
, git_object_id(revspec
.from
));
72 git_oid_cpy(&blameopts
.newest_commit
, git_object_id(revspec
.to
));
73 git_object_free(revspec
.from
);
74 git_object_free(revspec
.to
);
79 check_lg2(git_blame_file(&blame
, repo
, o
.path
, &blameopts
), "Blame error", NULL
);
82 * Get the raw data inside the blob for output. We use the
83 * `commitish:path/to/file.txt` format to find it.
85 if (git_oid_iszero(&blameopts
.newest_commit
))
88 git_oid_tostr(spec
, sizeof(spec
), &blameopts
.newest_commit
);
92 check_lg2(git_revparse_single(&obj
, repo
, spec
), "Object lookup error", NULL
);
93 check_lg2(git_blob_lookup(&blob
, repo
, git_object_id(obj
)), "Blob lookup error", NULL
);
96 rawdata
= git_blob_rawcontent(blob
);
98 /** Produce the output. */
101 break_on_null_hunk
= 0;
102 while (i
< git_blob_rawsize(blob
)) {
103 const char *eol
= strchr(rawdata
+i
, '\n');
105 const git_blame_hunk
*hunk
= git_blame_get_hunk_byline(blame
, line
);
107 if (break_on_null_hunk
&& !hunk
) break;
111 break_on_null_hunk
= 1;
114 git_oid_tostr(oid
, 10, &hunk
->final_commit_id
);
115 snprintf(sig
, 30, "%s <%s>", hunk
->final_signature
->name
, hunk
->final_signature
->email
);
117 printf("%s ( %-30s %3d) %.*s\n",
121 (int)(eol
-rawdata
-i
),
125 i
= (int)(eol
- rawdata
+ 1);
131 git_blame_free(blame
);
132 git_repository_free(repo
);
134 git_threads_shutdown();
139 /** Tell the user how to make this thing work. */
140 static void usage(const char *msg
, const char *arg
)
143 fprintf(stderr
, "%s: %s\n", msg
, arg
);
145 fprintf(stderr
, "%s\n", msg
);
146 fprintf(stderr
, "usage: blame [options] [<commit range>] <path>\n");
147 fprintf(stderr
, "\n");
148 fprintf(stderr
, " <commit range> example: `HEAD~10..HEAD`, or `1234abcd`\n");
149 fprintf(stderr
, " -L <n,m> process only line range n-m, counting from 1\n");
150 fprintf(stderr
, " -M find line moves within and across files\n");
151 fprintf(stderr
, " -C find line copies within and across files\n");
152 fprintf(stderr
, " -F follow only the first parent commits\n");
153 fprintf(stderr
, "\n");
157 /** Parse the arguments. */
158 static void parse_opts(struct opts
*o
, int argc
, char *argv
[])
161 char *bare_args
[3] = {0};
163 if (argc
< 2) usage(NULL
, NULL
);
165 for (i
=1; i
<argc
; i
++) {
170 while (bare_args
[i
] && i
< 3) ++i
;
172 usage("Invalid argument set", NULL
);
175 else if (!strcmp(a
, "--"))
177 else if (!strcasecmp(a
, "-M"))
179 else if (!strcasecmp(a
, "-C"))
181 else if (!strcasecmp(a
, "-F"))
183 else if (!strcasecmp(a
, "-L")) {
185 if (i
>= argc
) fatal("Not enough arguments to -L", NULL
);
186 check_lg2(sscanf(a
, "%d,%d", &o
->start_line
, &o
->end_line
)-2, "-L format error", NULL
);
190 if (o
->commitspec
) fatal("Only one commit spec allowed", NULL
);
195 /* Handle the bare arguments */
196 if (!bare_args
[0]) usage("Please specify a path", NULL
);
197 o
->path
= bare_args
[0];
199 /* <commitspec> <path> */
200 o
->path
= bare_args
[1];
201 o
->commitspec
= bare_args
[0];
204 /* <oldcommit> <newcommit> <path> */
205 char spec
[128] = {0};
206 o
->path
= bare_args
[2];
207 sprintf(spec
, "%s..%s", bare_args
[0], bare_args
[1]);
208 o
->commitspec
= spec
;