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.
35 static void parse_opts(struct opts
*o
, int argc
, char *argv
[]);
37 int main(int argc
, char *argv
[])
39 int i
, line
, break_on_null_hunk
;
40 char spec
[1024] = {0};
43 git_repository
*repo
= NULL
;
44 git_revspec revspec
= {0};
45 git_blame_options blameopts
= GIT_BLAME_OPTIONS_INIT
;
46 git_blame
*blame
= NULL
;
52 parse_opts(&o
, argc
, argv
);
53 if (o
.M
) blameopts
.flags
|= GIT_BLAME_TRACK_COPIES_SAME_COMMIT_MOVES
;
54 if (o
.C
) blameopts
.flags
|= GIT_BLAME_TRACK_COPIES_SAME_COMMIT_COPIES
;
56 /** Open the repository. */
57 check_lg2(git_repository_open_ext(&repo
, ".", 0, NULL
), "Couldn't open repository", NULL
);
60 * The commit range comes in "commitish" form. Use the rev-parse API to
61 * nail down the end points.
64 check_lg2(git_revparse(&revspec
, repo
, o
.commitspec
), "Couldn't parse commit spec", NULL
);
65 if (revspec
.flags
& GIT_REVPARSE_SINGLE
) {
66 git_oid_cpy(&blameopts
.newest_commit
, git_object_id(revspec
.from
));
67 git_object_free(revspec
.from
);
69 git_oid_cpy(&blameopts
.oldest_commit
, git_object_id(revspec
.from
));
70 git_oid_cpy(&blameopts
.newest_commit
, git_object_id(revspec
.to
));
71 git_object_free(revspec
.from
);
72 git_object_free(revspec
.to
);
77 check_lg2(git_blame_file(&blame
, repo
, o
.path
, &blameopts
), "Blame error", NULL
);
80 * Get the raw data inside the blob for output. We use the
81 * `commitish:path/to/file.txt` format to find it.
83 if (git_oid_iszero(&blameopts
.newest_commit
))
86 git_oid_tostr(spec
, sizeof(spec
), &blameopts
.newest_commit
);
90 check_lg2(git_revparse_single(&obj
, repo
, spec
), "Object lookup error", NULL
);
91 check_lg2(git_blob_lookup(&blob
, repo
, git_object_id(obj
)), "Blob lookup error", NULL
);
94 rawdata
= git_blob_rawcontent(blob
);
96 /** Produce the output. */
99 break_on_null_hunk
= 0;
100 while (i
< git_blob_rawsize(blob
)) {
101 const char *eol
= strchr(rawdata
+i
, '\n');
103 const git_blame_hunk
*hunk
= git_blame_get_hunk_byline(blame
, line
);
105 if (break_on_null_hunk
&& !hunk
) break;
108 break_on_null_hunk
= 1;
111 git_oid_tostr(oid
, 10, &hunk
->final_commit_id
);
112 snprintf(sig
, 30, "%s <%s>", hunk
->final_signature
->name
, hunk
->final_signature
->email
);
114 printf("%s ( %-30s %3d) %.*s\n",
118 (int)(eol
-rawdata
-i
),
122 i
= (int)(eol
- rawdata
+ 1);
128 git_blame_free(blame
);
129 git_repository_free(repo
);
131 git_threads_shutdown();
136 /** Tell the user how to make this thing work. */
137 static void usage(const char *msg
, const char *arg
)
140 fprintf(stderr
, "%s: %s\n", msg
, arg
);
142 fprintf(stderr
, "%s\n", msg
);
143 fprintf(stderr
, "usage: blame [options] [<commit range>] <path>\n");
144 fprintf(stderr
, "\n");
145 fprintf(stderr
, " <commit range> example: `HEAD~10..HEAD`, or `1234abcd`\n");
146 fprintf(stderr
, " -L <n,m> process only line range n-m, counting from 1\n");
147 fprintf(stderr
, " -M find line moves within and across files\n");
148 fprintf(stderr
, " -C find line copies within and across files\n");
149 fprintf(stderr
, "\n");
153 /** Parse the arguments. */
154 static void parse_opts(struct opts
*o
, int argc
, char *argv
[])
157 char *bare_args
[3] = {0};
159 if (argc
< 2) usage(NULL
, NULL
);
161 for (i
=1; i
<argc
; i
++) {
166 while (bare_args
[i
] && i
< 3) ++i
;
168 usage("Invalid argument set", NULL
);
171 else if (!strcmp(a
, "--"))
173 else if (!strcasecmp(a
, "-M"))
175 else if (!strcasecmp(a
, "-C"))
177 else if (!strcasecmp(a
, "-L")) {
179 if (i
>= argc
) fatal("Not enough arguments to -L", NULL
);
180 check_lg2(sscanf(a
, "%d,%d", &o
->start_line
, &o
->end_line
)-2, "-L format error", NULL
);
184 if (o
->commitspec
) fatal("Only one commit spec allowed", NULL
);
189 /* Handle the bare arguments */
190 if (!bare_args
[0]) usage("Please specify a path", NULL
);
191 o
->path
= bare_args
[0];
193 /* <commitspec> <path> */
194 o
->path
= bare_args
[1];
195 o
->commitspec
= bare_args
[0];
198 /* <oldcommit> <newcommit> <path> */
199 char spec
[128] = {0};
200 o
->path
= bare_args
[2];
201 sprintf(spec
, "%s..%s", bare_args
[0], bare_args
[1]);
202 o
->commitspec
= spec
;