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