1 /*
2  * libgit2 "describe" example - shows how to describe commits
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 #include <assert.h>
17 
18 /**
19  * The following example partially reimplements the `git describe` command
20  * and some of its options.
21  *
22  * These commands should work:
23 
24  * - Describe HEAD with default options (`describe`)
25  * - Describe specified revision (`describe master~2`)
26  * - Describe specified revisions (`describe master~2 HEAD~3`)
27  * - Describe HEAD with dirty state suffix (`describe --dirty=*`)
28  * - Describe consider all refs (`describe --all master`)
29  * - Describe consider lightweight tags (`describe --tags temp-tag`)
30  * - Describe show non-default abbreviated size (`describe --abbrev=10`)
31  * - Describe always output the long format if matches a tag (`describe --long v1.0`)
32  * - Describe consider only tags of specified pattern (`describe --match v*-release`)
33  * - Describe show the fallback result (`describe --always`)
34  * - Describe follow only the first parent commit (`describe --first-parent`)
35  *
36  * The command line parsing logic is simplified and doesn't handle
37  * all of the use cases.
38  */
39 
40 /** describe_options represents the parsed command line options */
41 typedef struct {
42 	const char **commits;
43 	size_t commit_count;
44 	git_describe_options describe_options;
45 	git_describe_format_options format_options;
46 } describe_options;
47 
48 typedef struct args_info args_info;
49 
opts_add_commit(describe_options * opts,const char * commit)50 static void opts_add_commit(describe_options *opts, const char *commit)
51 {
52 	size_t sz;
53 
54 	assert(opts != NULL);
55 
56 	sz = ++opts->commit_count * sizeof(opts->commits[0]);
57 	opts->commits = xrealloc(opts->commits, sz);
58 	opts->commits[opts->commit_count - 1] = commit;
59 }
60 
do_describe_single(git_repository * repo,describe_options * opts,const char * rev)61 static void do_describe_single(git_repository *repo, describe_options *opts, const char *rev)
62 {
63 	git_object *commit;
64 	git_describe_result *describe_result;
65 	git_buf buf = { 0 };
66 
67 	if (rev) {
68 		check_lg2(git_revparse_single(&commit, repo, rev),
69 			"Failed to lookup rev", rev);
70 
71 		check_lg2(git_describe_commit(&describe_result, commit, &opts->describe_options),
72 			"Failed to describe rev", rev);
73 	}
74 	else
75 		check_lg2(git_describe_workdir(&describe_result, repo, &opts->describe_options),
76 			"Failed to describe workdir", NULL);
77 
78 	check_lg2(git_describe_format(&buf, describe_result, &opts->format_options),
79 			"Failed to format describe rev", rev);
80 
81 	printf("%s\n", buf.ptr);
82 }
83 
do_describe(git_repository * repo,describe_options * opts)84 static void do_describe(git_repository *repo, describe_options *opts)
85 {
86 	if (opts->commit_count == 0)
87 		do_describe_single(repo, opts, NULL);
88 	else
89 	{
90 		size_t i;
91 		for (i = 0; i < opts->commit_count; i++)
92 			do_describe_single(repo, opts, opts->commits[i]);
93 	}
94 }
95 
print_usage(void)96 static void print_usage(void)
97 {
98 	fprintf(stderr, "usage: see `git help describe`\n");
99 	exit(1);
100 }
101 
102 /** Parse command line arguments */
parse_options(describe_options * opts,int argc,char ** argv)103 static void parse_options(describe_options *opts, int argc, char **argv)
104 {
105 	args_info args = ARGS_INFO_INIT;
106 
107 	for (args.pos = 1; args.pos < argc; ++args.pos) {
108 		const char *curr = argv[args.pos];
109 
110 		if (curr[0] != '-') {
111 			opts_add_commit(opts, curr);
112 		} else if (!strcmp(curr, "--all")) {
113 			opts->describe_options.describe_strategy = GIT_DESCRIBE_ALL;
114 		} else if (!strcmp(curr, "--tags")) {
115 			opts->describe_options.describe_strategy = GIT_DESCRIBE_TAGS;
116 		} else if (!strcmp(curr, "--exact-match")) {
117 			opts->describe_options.max_candidates_tags = 0;
118 		} else if (!strcmp(curr, "--long")) {
119 			opts->format_options.always_use_long_format = 1;
120 		} else if (!strcmp(curr, "--always")) {
121 			opts->describe_options.show_commit_oid_as_fallback = 1;
122 		} else if (!strcmp(curr, "--first-parent")) {
123 			opts->describe_options.only_follow_first_parent = 1;
124 		} else if (optional_str_arg(&opts->format_options.dirty_suffix, &args, "--dirty", "-dirty")) {
125 		} else if (match_int_arg((int *)&opts->format_options.abbreviated_size, &args, "--abbrev", 0)) {
126 		} else if (match_int_arg((int *)&opts->describe_options.max_candidates_tags, &args, "--candidates", 0)) {
127 		} else if (match_str_arg(&opts->describe_options.pattern, &args, "--match")) {
128 		} else {
129 			print_usage();
130 		}
131 	}
132 
133 	if (opts->commit_count > 0) {
134 		if (opts->format_options.dirty_suffix)
135 			fatal("--dirty is incompatible with commit-ishes", NULL);
136 	}
137 	else {
138 		if (!opts->format_options.dirty_suffix || !opts->format_options.dirty_suffix[0]) {
139 			opts_add_commit(opts, "HEAD");
140 		}
141 	}
142 }
143 
144 /** Initialize describe_options struct */
describe_options_init(describe_options * opts)145 static void describe_options_init(describe_options *opts)
146 {
147 	memset(opts, 0, sizeof(*opts));
148 
149 	opts->commits = NULL;
150 	opts->commit_count = 0;
151 	git_describe_init_options(&opts->describe_options, GIT_DESCRIBE_OPTIONS_VERSION);
152 	git_describe_init_format_options(&opts->format_options, GIT_DESCRIBE_FORMAT_OPTIONS_VERSION);
153 }
154 
main(int argc,char ** argv)155 int main(int argc, char **argv)
156 {
157 	git_repository *repo;
158 	describe_options opts;
159 
160 	git_libgit2_init();
161 
162 	check_lg2(git_repository_open_ext(&repo, ".", 0, NULL),
163 			"Could not open repository", NULL);
164 
165 	describe_options_init(&opts);
166 	parse_options(&opts, argc, argv);
167 
168 	do_describe(repo, &opts);
169 
170 	git_repository_free(repo);
171 	git_libgit2_shutdown();
172 
173 	return 0;
174 }
175