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 module libgit2_d.example.blame; 15 16 17 private static import core.stdc.stdio; 18 private static import core.stdc.stdlib; 19 private static import core.stdc..string; 20 private static import libgit2_d.blame; 21 private static import libgit2_d.blob; 22 private static import libgit2_d.example.common; 23 private static import libgit2_d.object; 24 private static import libgit2_d.oid; 25 private static import libgit2_d.revparse; 26 private static import libgit2_d.types; 27 private static import std.ascii; 28 29 package: 30 31 /** 32 * This example demonstrates how to invoke the libgit2 blame API to roughly 33 * simulate the output of `git blame` and a few of its command line arguments. 34 */ 35 36 public struct blame_opts 37 { 38 char* path; 39 char* commitspec; 40 int C; 41 int M; 42 int start_line; 43 int end_line; 44 int F; 45 } 46 47 extern (C) 48 nothrow @nogc 49 //int lg2_blame(libgit2_d.types.git_repository* repo, int argc, char*[] argv) 50 public int lg2_blame(libgit2_d.types.git_repository* repo, int argc, char** argv) 51 52 in 53 { 54 } 55 56 do 57 { 58 .blame_opts o = .blame_opts.init; 59 .parse_opts(&o, argc, argv); 60 61 libgit2_d.blame.git_blame_options blameopts = libgit2_d.blame.GIT_BLAME_OPTIONS_INIT(); 62 63 if (o.M) { 64 blameopts.flags |= libgit2_d.blame.git_blame_flag_t.GIT_BLAME_TRACK_COPIES_SAME_COMMIT_MOVES; 65 } 66 67 if (o.C) { 68 blameopts.flags |= libgit2_d.blame.git_blame_flag_t.GIT_BLAME_TRACK_COPIES_SAME_COMMIT_COPIES; 69 } 70 71 if (o.F) { 72 blameopts.flags |= libgit2_d.blame.git_blame_flag_t.GIT_BLAME_FIRST_PARENT; 73 } 74 75 libgit2_d.revparse.git_revspec revspec = libgit2_d.revparse.git_revspec.init; 76 77 /** 78 * The commit range comes in "commitish" form. Use the rev-parse API to 79 * nail down the end points. 80 */ 81 if (o.commitspec != null) { 82 libgit2_d.example.common.check_lg2(libgit2_d.revparse.git_revparse(&revspec, repo, o.commitspec), "Couldn't parse commit spec", null); 83 84 if (revspec.flags & libgit2_d.revparse.git_revparse_mode_t.GIT_REVPARSE_SINGLE) { 85 libgit2_d.oid.git_oid_cpy(&blameopts.newest_commit, libgit2_d.object.git_object_id(revspec.from)); 86 libgit2_d.object.git_object_free(revspec.from); 87 } else { 88 libgit2_d.oid.git_oid_cpy(&blameopts.oldest_commit, libgit2_d.object.git_object_id(revspec.from)); 89 libgit2_d.oid.git_oid_cpy(&blameopts.newest_commit, libgit2_d.object.git_object_id(revspec.to)); 90 libgit2_d.object.git_object_free(revspec.from); 91 libgit2_d.object.git_object_free(revspec.to); 92 } 93 } 94 95 /** Run the blame. */ 96 libgit2_d.blame.git_blame* blame = null; 97 libgit2_d.example.common.check_lg2(libgit2_d.blame.git_blame_file(&blame, repo, o.path, &blameopts), "Blame error", null); 98 99 char[1024] spec = '\0'; 100 101 /** 102 * Get the raw data inside the blob for output. We use the 103 * `commitish:path/to/file.txt` format to find it. 104 */ 105 if (libgit2_d.oid.git_oid_is_zero(&blameopts.newest_commit)) { 106 core.stdc..string.strcpy(&(spec[0]), "HEAD"); 107 } else { 108 libgit2_d.oid.git_oid_tostr(&(spec[0]), spec.length, &blameopts.newest_commit); 109 } 110 111 core.stdc..string.strcat(&(spec[0]), ":"); 112 core.stdc..string.strcat(&(spec[0]), o.path); 113 114 libgit2_d.types.git_object* obj; 115 libgit2_d.example.common.check_lg2(libgit2_d.revparse.git_revparse_single(&obj, repo, &(spec[0])), "Object lookup error", null); 116 libgit2_d.types.git_blob* blob; 117 libgit2_d.example.common.check_lg2(libgit2_d.blob.git_blob_lookup(&blob, repo, libgit2_d.object.git_object_id(obj)), "Blob lookup error", null); 118 libgit2_d.object.git_object_free(obj); 119 120 const char* rawdata = cast(const char*)(libgit2_d.blob.git_blob_rawcontent(blob)); 121 libgit2_d.types.git_object_size_t rawsize = libgit2_d.blob.git_blob_rawsize(blob); 122 123 /** Produce the output. */ 124 int line = 1; 125 libgit2_d.types.git_object_size_t i = 0; 126 int break_on_null_hunk = 0; 127 128 while (i < rawsize) { 129 const char* eol = cast(const char*)(core.stdc..string.memchr(rawdata + i, '\n', cast(size_t)(rawsize - i))); 130 char[10] oid = '\0'; 131 const (libgit2_d.blame.git_blame_hunk)* hunk = libgit2_d.blame.git_blame_get_hunk_byline(blame, line); 132 133 if ((break_on_null_hunk) && (!hunk)) { 134 break; 135 } 136 137 if (hunk != null) { 138 char[128] sig = '\0'; 139 break_on_null_hunk = 1; 140 141 libgit2_d.oid.git_oid_tostr(&(oid[0]), 10, &hunk.final_commit_id); 142 libgit2_d.example.common.snprintf(&(sig[0]), 30, "%s <%s>", hunk.final_signature.name, hunk.final_signature.email); 143 144 core.stdc.stdio.printf("%s ( %-30s %3d) %.*s\n", &(oid[0]), &(sig[0]), line, cast(int)(eol - rawdata - i), rawdata + i); 145 } 146 147 i = cast(int)(eol - rawdata + 1); 148 line++; 149 } 150 151 /** Cleanup. */ 152 libgit2_d.blob.git_blob_free(blob); 153 libgit2_d.blame.git_blame_free(blame); 154 155 return 0; 156 } 157 158 /** 159 * Tell the user how to make this thing work. 160 */ 161 nothrow @nogc 162 private void usage(const (char)* msg, const (char)* arg) 163 164 in 165 { 166 } 167 168 do 169 { 170 if ((msg != null) && (arg != null)) { 171 core.stdc.stdio.fprintf(core.stdc.stdio.stderr, "%s: %s\n", msg, arg); 172 } else if (msg != null) { 173 core.stdc.stdio.fprintf(core.stdc.stdio.stderr, "%s\n", msg); 174 } 175 176 core.stdc.stdio.fprintf(core.stdc.stdio.stderr, "usage: blame [options] [<commit range>] <path>\n"); 177 core.stdc.stdio.fprintf(core.stdc.stdio.stderr, "\n"); 178 core.stdc.stdio.fprintf(core.stdc.stdio.stderr, " <commit range> example: `HEAD~10..HEAD`, or `1234abcd`\n"); 179 core.stdc.stdio.fprintf(core.stdc.stdio.stderr, " -L <n,m> process only line range n-m, counting from 1\n"); 180 core.stdc.stdio.fprintf(core.stdc.stdio.stderr, " -M find line moves within and across files\n"); 181 core.stdc.stdio.fprintf(core.stdc.stdio.stderr, " -C find line copies within and across files\n"); 182 core.stdc.stdio.fprintf(core.stdc.stdio.stderr, " -F follow only the first parent commits\n"); 183 core.stdc.stdio.fprintf(core.stdc.stdio.stderr, "\n"); 184 core.stdc.stdlib.exit(1); 185 } 186 187 pragma(inline, true) 188 pure nothrow @trusted @nogc 189 private bool is_option(const char* input, char c1, char c2) 190 191 in 192 { 193 assert(input != null); 194 assert(c1 != c2); 195 assert(std.ascii.isUpper(c1)); 196 assert(std.ascii.isLower(c2)); 197 assert(c1 == std.ascii.toUpper(c2)); 198 } 199 200 do 201 { 202 return (*input == '-') && ((*(input + 1) == c1) || (*(input + 1) == c2)); 203 } 204 205 /** 206 * Parse the arguments. 207 */ 208 nothrow @nogc 209 private void parse_opts(.blame_opts* o, int argc, char** argv) 210 211 in 212 { 213 } 214 215 do 216 { 217 char*[3] bare_args = null; 218 219 if (argc < 2) { 220 .usage(null, null); 221 } 222 223 for (int i = 1; i < argc; i++) { 224 char* a = argv[i]; 225 226 if (a[0] != '-') { 227 i = 0; 228 229 while ((bare_args[i]) && (i < 3)) { 230 ++i; 231 } 232 233 if (i >= 3) { 234 .usage("Invalid argument set", null); 235 } 236 237 bare_args[i] = a; 238 } else if (!core.stdc..string.strcmp(a, "--")) { 239 continue; 240 } else if (.is_option(a, 'M', 'm')) { 241 o.M = 1; 242 } else if (.is_option(a, 'C', 'c')) { 243 o.C = 1; 244 } else if (.is_option(a, 'F', 'f')) { 245 o.F = 1; 246 } else if (.is_option(a, 'L', 'l')) { 247 i++; 248 a = argv[i]; 249 250 if (i >= argc) { 251 libgit2_d.example.common.fatal("Not enough arguments to -L", null); 252 } 253 254 libgit2_d.example.common.check_lg2(core.stdc.stdio.sscanf(a, "%d,%d", &o.start_line, &o.end_line) - 2, "-L format error", null); 255 } else { 256 /* commit range */ 257 if (o.commitspec) { 258 libgit2_d.example.common.fatal("Only one commit spec allowed", null); 259 } 260 261 o.commitspec = a; 262 } 263 } 264 265 /* Handle the bare arguments */ 266 if (!bare_args[0]) { 267 .usage("Please specify a path", null); 268 } 269 270 o.path = bare_args[0]; 271 272 if (bare_args[1]) { 273 /* <commitspec> <path> */ 274 o.path = bare_args[1]; 275 o.commitspec = bare_args[0]; 276 } 277 278 if (bare_args[2]) { 279 /* <oldcommit> <newcommit> <path> */ 280 char[128] spec = '\0'; 281 o.path = bare_args[2]; 282 core.stdc.stdio.sprintf(&(spec[0]), "%s..%s", bare_args[0], bare_args[1]); 283 o.commitspec = &(spec[0]); 284 } 285 }