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 * This example demonstrates how to invoke the libgit2 blame API to roughly
19 * simulate the output of `git blame` and a few of its command line arguments.
31 static void parse_opts(struct blame_opts
*o
, int argc
, char *argv
[]);
33 int lg2_blame(git_repository
*repo
, int argc
, char *argv
[])
35 int line
, break_on_null_hunk
;
36 git_object_size_t i
, rawsize
;
37 char spec
[1024] = {0};
38 struct blame_opts o
= {0};
40 git_revspec revspec
= {0};
41 git_blame_options blameopts
= GIT_BLAME_OPTIONS_INIT
;
42 git_blame
*blame
= NULL
;
46 parse_opts(&o
, argc
, argv
);
47 if (o
.M
) blameopts
.flags
|= GIT_BLAME_TRACK_COPIES_SAME_COMMIT_MOVES
;
48 if (o
.C
) blameopts
.flags
|= GIT_BLAME_TRACK_COPIES_SAME_COMMIT_COPIES
;
49 if (o
.F
) blameopts
.flags
|= GIT_BLAME_FIRST_PARENT
;
52 * The commit range comes in "committish" form. Use the rev-parse API to
53 * nail down the end points.
56 check_lg2(git_revparse(&revspec
, repo
, o
.commitspec
), "Couldn't parse commit spec", NULL
);
57 if (revspec
.flags
& GIT_REVSPEC_SINGLE
) {
58 git_oid_cpy(&blameopts
.newest_commit
, git_object_id(revspec
.from
));
59 git_object_free(revspec
.from
);
61 git_oid_cpy(&blameopts
.oldest_commit
, git_object_id(revspec
.from
));
62 git_oid_cpy(&blameopts
.newest_commit
, git_object_id(revspec
.to
));
63 git_object_free(revspec
.from
);
64 git_object_free(revspec
.to
);
69 check_lg2(git_blame_file(&blame
, repo
, o
.path
, &blameopts
), "Blame error", NULL
);
72 * Get the raw data inside the blob for output. We use the
73 * `committish:path/to/file.txt` format to find it.
75 if (git_oid_is_zero(&blameopts
.newest_commit
))
78 git_oid_tostr(spec
, sizeof(spec
), &blameopts
.newest_commit
);
82 check_lg2(git_revparse_single(&obj
, repo
, spec
), "Object lookup error", NULL
);
83 check_lg2(git_blob_lookup(&blob
, repo
, git_object_id(obj
)), "Blob lookup error", NULL
);
86 rawdata
= git_blob_rawcontent(blob
);
87 rawsize
= git_blob_rawsize(blob
);
89 /** Produce the output. */
92 break_on_null_hunk
= 0;
94 const char *eol
= memchr(rawdata
+ i
, '\n', (size_t)(rawsize
- i
));
96 const git_blame_hunk
*hunk
= git_blame_get_hunk_byline(blame
, line
);
98 if (break_on_null_hunk
&& !hunk
)
103 break_on_null_hunk
= 1;
105 git_oid_tostr(oid
, 10, &hunk
->final_commit_id
);
106 snprintf(sig
, 30, "%s <%s>", hunk
->final_signature
->name
, hunk
->final_signature
->email
);
108 printf("%s ( %-30s %3d) %.*s\n",
112 (int)(eol
- rawdata
- i
),
116 i
= (int)(eol
- rawdata
+ 1);
122 git_blame_free(blame
);
127 /** Tell the user how to make this thing work. */
128 static void usage(const char *msg
, const char *arg
)
131 fprintf(stderr
, "%s: %s\n", msg
, arg
);
133 fprintf(stderr
, "%s\n", msg
);
134 fprintf(stderr
, "usage: blame [options] [<commit range>] <path>\n");
135 fprintf(stderr
, "\n");
136 fprintf(stderr
, " <commit range> example: `HEAD~10..HEAD`, or `1234abcd`\n");
137 fprintf(stderr
, " -L <n,m> process only line range n-m, counting from 1\n");
138 fprintf(stderr
, " -M find line moves within and across files\n");
139 fprintf(stderr
, " -C find line copies within and across files\n");
140 fprintf(stderr
, " -F follow only the first parent commits\n");
141 fprintf(stderr
, "\n");
145 /** Parse the arguments. */
146 static void parse_opts(struct blame_opts
*o
, int argc
, char *argv
[])
149 char *bare_args
[3] = {0};
151 if (argc
< 2) usage(NULL
, NULL
);
153 for (i
=1; i
<argc
; i
++) {
158 while (bare_args
[i
] && i
< 3) ++i
;
160 usage("Invalid argument set", NULL
);
163 else if (!strcmp(a
, "--"))
165 else if (!strcasecmp(a
, "-M"))
167 else if (!strcasecmp(a
, "-C"))
169 else if (!strcasecmp(a
, "-F"))
171 else if (!strcasecmp(a
, "-L")) {
173 if (i
>= argc
) fatal("Not enough arguments to -L", NULL
);
174 check_lg2(sscanf(a
, "%d,%d", &o
->start_line
, &o
->end_line
)-2, "-L format error", NULL
);
178 if (o
->commitspec
) fatal("Only one commit spec allowed", NULL
);
183 /* Handle the bare arguments */
184 if (!bare_args
[0]) usage("Please specify a path", NULL
);
185 o
->path
= bare_args
[0];
187 /* <commitspec> <path> */
188 o
->path
= bare_args
[1];
189 o
->commitspec
= bare_args
[0];
192 /* <oldcommit> <newcommit> <path> */
193 char spec
[128] = {0};
194 o
->path
= bare_args
[2];
195 sprintf(spec
, "%s..%s", bare_args
[0], bare_args
[1]);
196 o
->commitspec
= spec
;