]> git.proxmox.com Git - libgit2.git/blob - examples/blame.c
Standardize layout of blame sample
[libgit2.git] / examples / blame.c
1 /*
2 * libgit2 "blame" example - shows how to use the blame API
3 *
4 * Written by the libgit2 contributors
5 *
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.
9 *
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/>.
13 */
14
15 #include "common.h"
16
17 /**
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.
20 */
21
22 static void usage(const char *msg, const char *arg);
23
24 int main(int argc, char *argv[])
25 {
26 int i, line, break_on_null_hunk;
27 const char *path = NULL, *a;
28 const char *rawdata, *commitspec=NULL, *bare_args[3] = {0};
29 char spec[1024] = {0};
30 git_repository *repo = NULL;
31 git_revspec revspec = {0};
32 git_blame_options opts = GIT_BLAME_OPTIONS_INIT;
33 git_blame *blame = NULL;
34 git_blob *blob;
35
36 git_threads_init();
37
38 if (argc < 2) usage(NULL, NULL);
39
40 for (i=1; i<argc; i++) {
41 a = argv[i];
42
43 if (a[0] != '-') {
44 int i=0;
45 while (bare_args[i] && i < 3) ++i;
46 if (i >= 3)
47 usage("Invalid argument set", NULL);
48 bare_args[i] = a;
49 }
50 else if (!strcmp(a, "--"))
51 continue;
52 else if (!strcasecmp(a, "-M"))
53 opts.flags |= GIT_BLAME_TRACK_COPIES_SAME_COMMIT_MOVES;
54 else if (!strcasecmp(a, "-C"))
55 opts.flags |= GIT_BLAME_TRACK_COPIES_SAME_COMMIT_COPIES;
56 else if (!strcasecmp(a, "-L")) {
57 i++; a = argv[i];
58 if (i >= argc) fatal("Not enough arguments to -L", NULL);
59 check_lg2(sscanf(a, "%d,%d", &opts.min_line, &opts.max_line)-2, "-L format error", NULL);
60 }
61 else {
62 /* commit range */
63 if (commitspec) fatal("Only one commit spec allowed", NULL);
64 commitspec = a;
65 }
66 }
67
68 /* Handle the bare arguments */
69 if (!bare_args[0]) usage("Please specify a path", NULL);
70 path = bare_args[0];
71 if (bare_args[1]) {
72 /* <commitspec> <path> */
73 path = bare_args[1];
74 commitspec = bare_args[0];
75 }
76 if (bare_args[2]) {
77 /* <oldcommit> <newcommit> <path> */
78 path = bare_args[2];
79 sprintf(spec, "%s..%s", bare_args[0], bare_args[1]);
80 commitspec = spec;
81 }
82
83 /* Open the repo */
84 check_lg2(git_repository_open_ext(&repo, ".", 0, NULL), "Couldn't open repository", NULL);
85
86 /* Parse the end points */
87 if (commitspec) {
88 check_lg2(git_revparse(&revspec, repo, commitspec), "Couldn't parse commit spec", NULL);
89 if (revspec.flags & GIT_REVPARSE_SINGLE) {
90 git_oid_cpy(&opts.newest_commit, git_object_id(revspec.from));
91 git_object_free(revspec.from);
92 } else {
93 git_oid_cpy(&opts.oldest_commit, git_object_id(revspec.from));
94 git_oid_cpy(&opts.newest_commit, git_object_id(revspec.to));
95 git_object_free(revspec.from);
96 git_object_free(revspec.to);
97 }
98 }
99
100 /* Run the blame */
101 check_lg2(git_blame_file(&blame, repo, path, &opts), "Blame error", NULL);
102
103 /* Get the raw data for output */
104 if (git_oid_iszero(&opts.newest_commit))
105 strcpy(spec, "HEAD");
106 else
107 git_oid_tostr(spec, sizeof(spec), &opts.newest_commit);
108 strcat(spec, ":");
109 strcat(spec, path);
110
111 {
112 git_object *obj;
113 check_lg2(git_revparse_single(&obj, repo, spec), "Object lookup error", NULL);
114 check_lg2(git_blob_lookup(&blob, repo, git_object_id(obj)), "Blob lookup error", NULL);
115 git_object_free(obj);
116 }
117 rawdata = git_blob_rawcontent(blob);
118
119 /* Produce the output */
120 line = 1;
121 i = 0;
122 break_on_null_hunk = 0;
123 while (i < git_blob_rawsize(blob)) {
124 const char *eol = strchr(rawdata+i, '\n');
125 char oid[10] = {0};
126 const git_blame_hunk *hunk = git_blame_get_hunk_byline(blame, line);
127
128 if (break_on_null_hunk && !hunk) break;
129
130 if (hunk) {
131 break_on_null_hunk = 1;
132 char sig[128] = {0};
133
134 git_oid_tostr(oid, 10, &hunk->final_commit_id);
135 snprintf(sig, 30, "%s <%s>", hunk->final_signature->name, hunk->final_signature->email);
136
137 printf("%s ( %-30s %3d) %.*s\n",
138 oid,
139 sig,
140 line,
141 (int)(eol-rawdata-i),
142 rawdata+i);
143 }
144
145 i = eol - rawdata + 1;
146 line++;
147 }
148
149 /* Cleanup */
150 git_blob_free(blob);
151 git_blame_free(blame);
152 git_repository_free(repo);
153 git_threads_shutdown();
154 }
155
156 static void usage(const char *msg, const char *arg)
157 {
158 if (msg && arg)
159 fprintf(stderr, "%s: %s\n", msg, arg);
160 else if (msg)
161 fprintf(stderr, "%s\n", msg);
162 fprintf(stderr, "usage: blame [options] [<commit range>] <path>\n");
163 fprintf(stderr, "\n");
164 fprintf(stderr, " <commit range> example: `HEAD~10..HEAD`, or `1234abcd`\n");
165 fprintf(stderr, " -L <n,m> process only line range n-m, counting from 1\n");
166 fprintf(stderr, " -M find line moves within and across files\n");
167 fprintf(stderr, " -C find line copies within and across files\n");
168 fprintf(stderr, "\n");
169 exit(1);
170 }
171