1 /* 2 * libgit2 "tag" example - shows how to list, create and delete tags 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.tag; 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.buffer; 21 private static import libgit2_d.commit; 22 private static import libgit2_d.example.args; 23 private static import libgit2_d.example.common; 24 private static import libgit2_d.object; 25 private static import libgit2_d.oid; 26 private static import libgit2_d.revparse; 27 private static import libgit2_d.signature; 28 private static import libgit2_d.strarray; 29 private static import libgit2_d.tag; 30 private static import libgit2_d.types; 31 32 package: 33 34 /** 35 * The following example partially reimplements the `git tag` command 36 * and some of its options. 37 * 38 * These commands should work: 39 * 40 * - Tag name listing (`tag`) 41 * - Filtered tag listing with messages (`tag -n3 -l "v0.1*"`) 42 * - Lightweight tag creation (`tag test v0.18.0`) 43 * - Tag creation (`tag -a -m "Test message" test v0.18.0`) 44 * - Tag deletion (`tag -d test`) 45 * 46 * The command line parsing logic is simplified and doesn't handle 47 * all of the use cases. 48 */ 49 50 /** 51 * tag_options represents the parsed command line options 52 */ 53 public struct tag_options 54 { 55 const (char)* message; 56 const (char)* pattern; 57 const (char)* tag_name; 58 const (char)* target; 59 int num_lines; 60 int force; 61 } 62 63 /** 64 * tag_state represents the current program state for dragging around 65 */ 66 public struct tag_state 67 { 68 libgit2_d.types.git_repository* repo; 69 .tag_options* opts; 70 } 71 72 /** 73 * An action to execute based on the command line arguments 74 */ 75 public alias tag_action = nothrow @nogc void function(.tag_state* state); 76 77 nothrow @nogc 78 private void check(int result, const (char)* message) 79 80 in 81 { 82 } 83 84 do 85 { 86 if (result) { 87 libgit2_d.example.common.fatal(message, null); 88 } 89 } 90 91 /** 92 * Tag listing: Print individual message lines 93 */ 94 nothrow @nogc 95 private void print_list_lines(const (char)* message, const (.tag_state)* state) 96 97 in 98 { 99 } 100 101 do 102 { 103 const (char)* msg = message; 104 int num = state.opts.num_lines - 1; 105 106 if (msg == null) { 107 return; 108 } 109 110 /** first line - headline */ 111 while ((*msg) && (*msg != '\n')) { 112 core.stdc.stdio.printf("%c", *msg++); 113 } 114 115 /** skip over new lines */ 116 while ((*msg) && (*msg == '\n')) { 117 msg++; 118 } 119 120 core.stdc.stdio.printf("\n"); 121 122 /** print just headline? */ 123 if (num == 0) { 124 return; 125 } 126 127 if ((*msg) && (msg[1])) { 128 core.stdc.stdio.printf("\n"); 129 } 130 131 /** print individual commit/tag lines */ 132 while ((*msg) && (num-- >= 2)) { 133 core.stdc.stdio.printf(" "); 134 135 while ((*msg) && (*msg != '\n')) { 136 core.stdc.stdio.printf("%c", *msg++); 137 } 138 139 /** handle consecutive new lines */ 140 if ((*msg) && (*msg == '\n') && (msg[1] == '\n')) { 141 num--; 142 core.stdc.stdio.printf("\n"); 143 } 144 145 while ((*msg) && (*msg == '\n')) { 146 msg++; 147 } 148 149 core.stdc.stdio.printf("\n"); 150 } 151 } 152 153 /** 154 * Tag listing: Print an actual tag object 155 */ 156 nothrow @nogc 157 private void print_tag(libgit2_d.types.git_tag* tag, const (.tag_state)* state) 158 159 in 160 { 161 } 162 163 do 164 { 165 core.stdc.stdio.printf("%-16s", libgit2_d.tag.git_tag_name(tag)); 166 167 if (state.opts.num_lines) { 168 const (char)* msg = libgit2_d.tag.git_tag_message(tag); 169 .print_list_lines(msg, state); 170 } else { 171 core.stdc.stdio.printf("\n"); 172 } 173 } 174 175 /** 176 * Tag listing: Print a commit (target of a lightweight tag) 177 */ 178 nothrow @nogc 179 private void print_commit(libgit2_d.types.git_commit* commit, const (char)* name, const (.tag_state)* state) 180 181 in 182 { 183 } 184 185 do 186 { 187 core.stdc.stdio.printf("%-16s", name); 188 189 if (state.opts.num_lines) { 190 const (char)* msg = libgit2_d.commit.git_commit_message(commit); 191 .print_list_lines(msg, state); 192 } else { 193 core.stdc.stdio.printf("\n"); 194 } 195 } 196 197 /** 198 * Tag listing: Fallback, should not happen 199 */ 200 nothrow @nogc 201 private void print_name(const (char)* name) 202 203 in 204 { 205 } 206 207 do 208 { 209 core.stdc.stdio.printf("%s\n", name); 210 } 211 212 /** 213 * Tag listing: Lookup tags based on ref name and dispatch to print 214 */ 215 nothrow @nogc 216 private int each_tag(const (char)* name, .tag_state* state) 217 218 in 219 { 220 } 221 222 do 223 { 224 libgit2_d.types.git_repository* repo = state.repo; 225 libgit2_d.types.git_object* obj; 226 227 libgit2_d.example.common.check_lg2(libgit2_d.revparse.git_revparse_single(&obj, repo, name), "Failed to lookup rev", name); 228 229 switch (libgit2_d.object.git_object_type(obj)) { 230 case libgit2_d.types.git_object_t.GIT_OBJECT_TAG: 231 .print_tag(cast(libgit2_d.types.git_tag*)(obj), state); 232 233 break; 234 235 case libgit2_d.types.git_object_t.GIT_OBJECT_COMMIT: 236 .print_commit(cast(libgit2_d.types.git_commit*)(obj), name, state); 237 238 break; 239 240 default: 241 .print_name(name); 242 243 break; 244 } 245 246 libgit2_d.object.git_object_free(obj); 247 248 return 0; 249 } 250 251 nothrow @nogc 252 private void action_list_tags(.tag_state* state) 253 254 in 255 { 256 } 257 258 do 259 { 260 const (char)* pattern = state.opts.pattern; 261 libgit2_d.strarray.git_strarray tag_names = libgit2_d.strarray.git_strarray.init; 262 263 libgit2_d.example.common.check_lg2(libgit2_d.tag.git_tag_list_match(&tag_names, (pattern) ? (pattern) : ("*"), state.repo), "Unable to get list of tags", null); 264 265 for (size_t i = 0; i < tag_names.count; i++) { 266 .each_tag(tag_names.strings[i], state); 267 } 268 269 libgit2_d.strarray.git_strarray_dispose(&tag_names); 270 } 271 272 nothrow @nogc 273 private void action_delete_tag(.tag_state* state) 274 275 in 276 { 277 } 278 279 do 280 { 281 .tag_options* opts = state.opts; 282 libgit2_d.types.git_object* obj; 283 libgit2_d.buffer.git_buf abbrev_oid = libgit2_d.buffer.git_buf.init; 284 285 .check(!opts.tag_name, "Name required"); 286 287 libgit2_d.example.common.check_lg2(libgit2_d.revparse.git_revparse_single(&obj, state.repo, opts.tag_name), "Failed to lookup rev", opts.tag_name); 288 289 libgit2_d.example.common.check_lg2(libgit2_d.object.git_object_short_id(&abbrev_oid, obj), "Unable to get abbreviated OID", opts.tag_name); 290 291 libgit2_d.example.common.check_lg2(libgit2_d.tag.git_tag_delete(state.repo, opts.tag_name), "Unable to delete tag", opts.tag_name); 292 293 core.stdc.stdio.printf("Deleted tag '%s' (was %s)\n", opts.tag_name, abbrev_oid.ptr_); 294 295 libgit2_d.buffer.git_buf_dispose(&abbrev_oid); 296 libgit2_d.object.git_object_free(obj); 297 } 298 299 nothrow @nogc 300 private void action_create_lighweight_tag(.tag_state* state) 301 302 in 303 { 304 } 305 306 do 307 { 308 libgit2_d.types.git_repository* repo = state.repo; 309 .tag_options* opts = state.opts; 310 311 .check(!opts.tag_name, "Name required"); 312 313 if (opts.target == null) { 314 opts.target = "HEAD"; 315 } 316 317 .check(!opts.target, "Target required"); 318 319 libgit2_d.types.git_object* target; 320 libgit2_d.example.common.check_lg2(libgit2_d.revparse.git_revparse_single(&target, repo, opts.target), "Unable to resolve spec", opts.target); 321 322 libgit2_d.oid.git_oid oid; 323 libgit2_d.example.common.check_lg2(libgit2_d.tag.git_tag_create_lightweight(&oid, repo, opts.tag_name, target, opts.force), "Unable to create tag", null); 324 325 libgit2_d.object.git_object_free(target); 326 } 327 328 nothrow @nogc 329 private void action_create_tag(.tag_state* state) 330 331 in 332 { 333 } 334 335 do 336 { 337 libgit2_d.types.git_repository* repo = state.repo; 338 .tag_options* opts = state.opts; 339 340 .check(!opts.tag_name, "Name required"); 341 .check(!opts.message, "Message required"); 342 343 if (opts.target == null) { 344 opts.target = "HEAD"; 345 } 346 347 libgit2_d.types.git_object* target; 348 libgit2_d.example.common.check_lg2(libgit2_d.revparse.git_revparse_single(&target, repo, opts.target), "Unable to resolve spec", opts.target); 349 350 libgit2_d.types.git_signature* tagger; 351 libgit2_d.example.common.check_lg2(libgit2_d.signature.git_signature_default(&tagger, repo), "Unable to create signature", null); 352 353 libgit2_d.oid.git_oid oid; 354 libgit2_d.example.common.check_lg2(libgit2_d.tag.git_tag_create(&oid, repo, opts.tag_name, target, tagger, opts.message, opts.force), "Unable to create tag", null); 355 356 libgit2_d.object.git_object_free(target); 357 libgit2_d.signature.git_signature_free(tagger); 358 } 359 360 nothrow @nogc 361 private void print_usage() 362 363 in 364 { 365 } 366 367 do 368 { 369 core.stdc.stdio.fprintf(core.stdc.stdio.stderr, "usage: see `git help tag`\n"); 370 core.stdc.stdlib.exit(1); 371 } 372 373 /** 374 * Parse command line arguments and choose action to run when done 375 */ 376 nothrow @nogc 377 private void parse_options(.tag_action* action, .tag_options* opts, int argc, char** argv) 378 379 in 380 { 381 } 382 383 do 384 { 385 libgit2_d.example.args.args_info args = libgit2_d.example.args.ARGS_INFO_INIT(argc, argv); 386 *action = &.action_list_tags; 387 388 for (args.pos = 1; args.pos < argc; ++args.pos) { 389 const (char)* curr = argv[args.pos]; 390 391 if (curr[0] != '-') { 392 if (opts.tag_name == null) { 393 opts.tag_name = curr; 394 } else if (opts.target == null) { 395 opts.target = curr; 396 } else { 397 .print_usage(); 398 } 399 400 if (*action != &.action_create_tag) { 401 *action = &.action_create_lighweight_tag; 402 } 403 } else if (!core.stdc..string.strcmp(curr, "-n")) { 404 opts.num_lines = 1; 405 *action = &.action_list_tags; 406 } else if (!core.stdc..string.strcmp(curr, "-a")) { 407 *action = &.action_create_tag; 408 } else if (!core.stdc..string.strcmp(curr, "-f")) { 409 opts.force = 1; 410 } else if (libgit2_d.example.args.match_int_arg(&opts.num_lines, &args, "-n", 0)) { 411 *action = &.action_list_tags; 412 } else if (libgit2_d.example.args.match_str_arg(&opts.pattern, &args, "-l")) { 413 *action = &.action_list_tags; 414 } else if (libgit2_d.example.args.match_str_arg(&opts.tag_name, &args, "-d")) { 415 *action = &.action_delete_tag; 416 } else if (libgit2_d.example.args.match_str_arg(&opts.message, &args, "-m")) { 417 *action = &.action_create_tag; 418 } 419 } 420 } 421 422 /** 423 * Initialize tag_options struct 424 */ 425 nothrow @nogc 426 private void tag_options_init(.tag_options* opts) 427 428 in 429 { 430 } 431 432 do 433 { 434 core.stdc..string.memset(opts, 0, (*opts).sizeof); 435 436 opts.message = null; 437 opts.pattern = null; 438 opts.tag_name = null; 439 opts.target = null; 440 opts.num_lines = 0; 441 opts.force = 0; 442 } 443 444 extern (C) 445 nothrow @nogc 446 public int lg2_tag(libgit2_d.types.git_repository* repo, int argc, char** argv) 447 448 in 449 { 450 } 451 452 do 453 { 454 .tag_options opts; 455 .tag_options_init(&opts); 456 .tag_action action; 457 .parse_options(&action, &opts, argc, argv); 458 459 .tag_state state = 460 { 461 repo: repo, 462 opts: &opts, 463 }; 464 465 action(&state); 466 467 return 0; 468 }