]>
Commit | Line | Data |
---|---|---|
a53b8584 JP |
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 | ||
15 | #include "common.h" | |
16 | ||
17 | /** | |
18 | * The following example partially reimplements the `git tag` command | |
19 | * and some of its options. | |
20 | * | |
21 | * These commands should work: | |
22 | ||
23 | * - Tag name listing (`tag`) | |
24 | * - Filtered tag listing with messages (`tag -n3 -l "v0.1*"`) | |
25 | * - Lightweight tag creation (`tag test v0.18.0`) | |
26 | * - Tag creation (`tag -a -m "Test message" test v0.18.0`) | |
27 | * - Tag deletion (`tag -d test`) | |
28 | * | |
29 | * The command line parsing logic is simplified and doesn't handle | |
30 | * all of the use cases. | |
31 | */ | |
32 | ||
33 | /** tag_options represents the parsed command line options */ | |
0c9c969a | 34 | struct tag_options { |
a53b8584 JP |
35 | const char *message; |
36 | const char *pattern; | |
37 | const char *tag_name; | |
38 | const char *target; | |
39 | int num_lines; | |
40 | int force; | |
0c9c969a | 41 | }; |
a53b8584 JP |
42 | |
43 | /** tag_state represents the current program state for dragging around */ | |
44 | typedef struct { | |
45 | git_repository *repo; | |
0c9c969a | 46 | struct tag_options *opts; |
a53b8584 JP |
47 | } tag_state; |
48 | ||
49 | /** An action to execute based on the command line arguments */ | |
50 | typedef void (*tag_action)(tag_state *state); | |
51 | typedef struct args_info args_info; | |
52 | ||
53 | static void check(int result, const char *message) | |
54 | { | |
55 | if (result) fatal(message, NULL); | |
56 | } | |
57 | ||
58 | /** Tag listing: Print individual message lines */ | |
59 | static void print_list_lines(const char *message, const tag_state *state) | |
60 | { | |
61 | const char *msg = message; | |
62 | int num = state->opts->num_lines - 1; | |
63 | ||
64 | if (!msg) return; | |
65 | ||
66 | /** first line - headline */ | |
67 | while(*msg && *msg != '\n') printf("%c", *msg++); | |
68 | ||
69 | /** skip over new lines */ | |
70 | while(*msg && *msg == '\n') msg++; | |
71 | ||
72 | printf("\n"); | |
73 | ||
74 | /** print just headline? */ | |
75 | if (num == 0) return; | |
76 | if (*msg && msg[1]) printf("\n"); | |
77 | ||
78 | /** print individual commit/tag lines */ | |
79 | while (*msg && num-- >= 2) { | |
80 | printf(" "); | |
81 | ||
82 | while (*msg && *msg != '\n') printf("%c", *msg++); | |
83 | ||
84 | /** handle consecutive new lines */ | |
85 | if (*msg && *msg == '\n' && msg[1] == '\n') { | |
86 | num--; | |
87 | printf("\n"); | |
88 | } | |
89 | while(*msg && *msg == '\n') msg++; | |
90 | ||
91 | printf("\n"); | |
92 | } | |
93 | } | |
94 | ||
95 | /** Tag listing: Print an actual tag object */ | |
96 | static void print_tag(git_tag *tag, const tag_state *state) | |
97 | { | |
98 | printf("%-16s", git_tag_name(tag)); | |
99 | ||
100 | if (state->opts->num_lines) { | |
101 | const char *msg = git_tag_message(tag); | |
102 | print_list_lines(msg, state); | |
103 | } else { | |
104 | printf("\n"); | |
105 | } | |
106 | } | |
107 | ||
108 | /** Tag listing: Print a commit (target of a lightweight tag) */ | |
109 | static void print_commit(git_commit *commit, const char *name, | |
110 | const tag_state *state) | |
111 | { | |
112 | printf("%-16s", name); | |
113 | ||
114 | if (state->opts->num_lines) { | |
115 | const char *msg = git_commit_message(commit); | |
116 | print_list_lines(msg, state); | |
117 | } else { | |
118 | printf("\n"); | |
119 | } | |
120 | } | |
121 | ||
122 | /** Tag listing: Fallback, should not happen */ | |
123 | static void print_name(const char *name) | |
124 | { | |
125 | printf("%s\n", name); | |
126 | } | |
127 | ||
128 | /** Tag listing: Lookup tags based on ref name and dispatch to print */ | |
129 | static int each_tag(const char *name, tag_state *state) | |
130 | { | |
131 | git_repository *repo = state->repo; | |
132 | git_object *obj; | |
133 | ||
134 | check_lg2(git_revparse_single(&obj, repo, name), | |
135 | "Failed to lookup rev", name); | |
136 | ||
137 | switch (git_object_type(obj)) { | |
ac3d33df | 138 | case GIT_OBJECT_TAG: |
a53b8584 JP |
139 | print_tag((git_tag *) obj, state); |
140 | break; | |
ac3d33df | 141 | case GIT_OBJECT_COMMIT: |
a53b8584 JP |
142 | print_commit((git_commit *) obj, name, state); |
143 | break; | |
144 | default: | |
145 | print_name(name); | |
146 | } | |
147 | ||
148 | git_object_free(obj); | |
149 | return 0; | |
150 | } | |
151 | ||
152 | static void action_list_tags(tag_state *state) | |
153 | { | |
154 | const char *pattern = state->opts->pattern; | |
155 | git_strarray tag_names = {0}; | |
156 | size_t i; | |
157 | ||
158 | check_lg2(git_tag_list_match(&tag_names, pattern ? pattern : "*", state->repo), | |
159 | "Unable to get list of tags", NULL); | |
160 | ||
161 | for(i = 0; i < tag_names.count; i++) { | |
162 | each_tag(tag_names.strings[i], state); | |
163 | } | |
164 | ||
165 | git_strarray_free(&tag_names); | |
166 | } | |
167 | ||
168 | static void action_delete_tag(tag_state *state) | |
169 | { | |
0c9c969a | 170 | struct tag_options *opts = state->opts; |
a53b8584 | 171 | git_object *obj; |
6ad59ef1 | 172 | git_buf abbrev_oid = {0}; |
a53b8584 JP |
173 | |
174 | check(!opts->tag_name, "Name required"); | |
175 | ||
176 | check_lg2(git_revparse_single(&obj, state->repo, opts->tag_name), | |
177 | "Failed to lookup rev", opts->tag_name); | |
178 | ||
6ad59ef1 JP |
179 | check_lg2(git_object_short_id(&abbrev_oid, obj), |
180 | "Unable to get abbreviated OID", opts->tag_name); | |
181 | ||
a53b8584 JP |
182 | check_lg2(git_tag_delete(state->repo, opts->tag_name), |
183 | "Unable to delete tag", opts->tag_name); | |
184 | ||
6ad59ef1 | 185 | printf("Deleted tag '%s' (was %s)\n", opts->tag_name, abbrev_oid.ptr); |
a53b8584 | 186 | |
ac3d33df | 187 | git_buf_dispose(&abbrev_oid); |
a53b8584 JP |
188 | git_object_free(obj); |
189 | } | |
190 | ||
191 | static void action_create_lighweight_tag(tag_state *state) | |
192 | { | |
193 | git_repository *repo = state->repo; | |
0c9c969a | 194 | struct tag_options *opts = state->opts; |
a53b8584 JP |
195 | git_oid oid; |
196 | git_object *target; | |
197 | ||
198 | check(!opts->tag_name, "Name required"); | |
199 | ||
200 | if (!opts->target) opts->target = "HEAD"; | |
201 | ||
202 | check(!opts->target, "Target required"); | |
203 | ||
204 | check_lg2(git_revparse_single(&target, repo, opts->target), | |
205 | "Unable to resolve spec", opts->target); | |
206 | ||
207 | check_lg2(git_tag_create_lightweight(&oid, repo, opts->tag_name, | |
208 | target, opts->force), "Unable to create tag", NULL); | |
209 | ||
210 | git_object_free(target); | |
211 | } | |
212 | ||
213 | static void action_create_tag(tag_state *state) | |
214 | { | |
215 | git_repository *repo = state->repo; | |
0c9c969a | 216 | struct tag_options *opts = state->opts; |
a53b8584 JP |
217 | git_signature *tagger; |
218 | git_oid oid; | |
219 | git_object *target; | |
220 | ||
221 | check(!opts->tag_name, "Name required"); | |
222 | check(!opts->message, "Message required"); | |
223 | ||
224 | if (!opts->target) opts->target = "HEAD"; | |
225 | ||
226 | check_lg2(git_revparse_single(&target, repo, opts->target), | |
227 | "Unable to resolve spec", opts->target); | |
228 | ||
229 | check_lg2(git_signature_default(&tagger, repo), | |
230 | "Unable to create signature", NULL); | |
231 | ||
232 | check_lg2(git_tag_create(&oid, repo, opts->tag_name, | |
233 | target, tagger, opts->message, opts->force), "Unable to create tag", NULL); | |
234 | ||
235 | git_object_free(target); | |
236 | git_signature_free(tagger); | |
237 | } | |
238 | ||
bd465f9c | 239 | static void print_usage(void) |
a53b8584 JP |
240 | { |
241 | fprintf(stderr, "usage: see `git help tag`\n"); | |
242 | exit(1); | |
243 | } | |
244 | ||
245 | /** Parse command line arguments and choose action to run when done */ | |
0c9c969a | 246 | static void parse_options(tag_action *action, struct tag_options *opts, int argc, char **argv) |
a53b8584 JP |
247 | { |
248 | args_info args = ARGS_INFO_INIT; | |
249 | *action = &action_list_tags; | |
250 | ||
251 | for (args.pos = 1; args.pos < argc; ++args.pos) { | |
252 | const char *curr = argv[args.pos]; | |
253 | ||
254 | if (curr[0] != '-') { | |
255 | if (!opts->tag_name) | |
256 | opts->tag_name = curr; | |
257 | else if (!opts->target) | |
258 | opts->target = curr; | |
259 | else | |
260 | print_usage(); | |
261 | ||
262 | if (*action != &action_create_tag) | |
263 | *action = &action_create_lighweight_tag; | |
264 | } else if (!strcmp(curr, "-n")) { | |
265 | opts->num_lines = 1; | |
266 | *action = &action_list_tags; | |
267 | } else if (!strcmp(curr, "-a")) { | |
268 | *action = &action_create_tag; | |
269 | } else if (!strcmp(curr, "-f")) { | |
270 | opts->force = 1; | |
271 | } else if (match_int_arg(&opts->num_lines, &args, "-n", 0)) { | |
272 | *action = &action_list_tags; | |
273 | } else if (match_str_arg(&opts->pattern, &args, "-l")) { | |
274 | *action = &action_list_tags; | |
275 | } else if (match_str_arg(&opts->tag_name, &args, "-d")) { | |
276 | *action = &action_delete_tag; | |
277 | } else if (match_str_arg(&opts->message, &args, "-m")) { | |
278 | *action = &action_create_tag; | |
279 | } | |
280 | } | |
281 | } | |
282 | ||
283 | /** Initialize tag_options struct */ | |
0c9c969a | 284 | static void tag_options_init(struct tag_options *opts) |
a53b8584 JP |
285 | { |
286 | memset(opts, 0, sizeof(*opts)); | |
287 | ||
288 | opts->message = NULL; | |
289 | opts->pattern = NULL; | |
290 | opts->tag_name = NULL; | |
291 | opts->target = NULL; | |
292 | opts->num_lines = 0; | |
293 | opts->force = 0; | |
294 | } | |
295 | ||
0c9c969a | 296 | int lg2_tag(git_repository *repo, int argc, char **argv) |
a53b8584 | 297 | { |
0c9c969a | 298 | struct tag_options opts; |
feebe615 JP |
299 | tag_action action; |
300 | tag_state state; | |
301 | ||
a53b8584 | 302 | tag_options_init(&opts); |
a53b8584 JP |
303 | parse_options(&action, &opts, argc, argv); |
304 | ||
feebe615 JP |
305 | state.repo = repo; |
306 | state.opts = &opts; | |
a53b8584 JP |
307 | action(&state); |
308 | ||
a53b8584 JP |
309 | return 0; |
310 | } |