]>
Commit | Line | Data |
---|---|---|
f8758044 | 1 | /* |
359fc2d2 | 2 | * Copyright (C) the libgit2 contributors. All rights reserved. |
f8758044 | 3 | * |
bb742ede VM |
4 | * This file is part of libgit2, distributed under the GNU GPL v2 with |
5 | * a Linking Exception. For full terms see the included COPYING file. | |
f8758044 VM |
6 | */ |
7 | ||
8 | #include "common.h" | |
9 | #include "commit.h" | |
10 | #include "tag.h" | |
638c2ca4 | 11 | #include "signature.h" |
458b9450 | 12 | #include "message.h" |
44908fe7 VM |
13 | #include "git2/object.h" |
14 | #include "git2/repository.h" | |
638c2ca4 | 15 | #include "git2/signature.h" |
83cc70d9 | 16 | #include "git2/odb_backend.h" |
f8758044 VM |
17 | |
18 | void git_tag__free(git_tag *tag) | |
19 | { | |
638c2ca4 | 20 | git_signature_free(tag->tagger); |
3286c408 VM |
21 | git__free(tag->message); |
22 | git__free(tag->tag_name); | |
23 | git__free(tag); | |
f8758044 VM |
24 | } |
25 | ||
d9023dbe | 26 | const git_oid *git_tag_id(const git_tag *c) |
d45b4a9a | 27 | { |
d9023dbe | 28 | return git_object_id((const git_object *)c); |
f8758044 VM |
29 | } |
30 | ||
d9023dbe | 31 | int git_tag_target(git_object **target, const git_tag *t) |
f8758044 | 32 | { |
58519018 | 33 | assert(t); |
6b2a1941 | 34 | return git_object_lookup(target, t->object.repo, &t->target, t->type); |
f8758044 VM |
35 | } |
36 | ||
d9023dbe | 37 | const git_oid *git_tag_target_id(const git_tag *t) |
ec25391d | 38 | { |
6b2a1941 VM |
39 | assert(t); |
40 | return &t->target; | |
41 | } | |
42 | ||
d9023dbe | 43 | git_otype git_tag_target_type(const git_tag *t) |
f8758044 | 44 | { |
58519018 | 45 | assert(t); |
f8758044 VM |
46 | return t->type; |
47 | } | |
48 | ||
d9023dbe | 49 | const char *git_tag_name(const git_tag *t) |
f8758044 | 50 | { |
58519018 | 51 | assert(t); |
f8758044 VM |
52 | return t->tag_name; |
53 | } | |
54 | ||
d9023dbe | 55 | const git_signature *git_tag_tagger(const git_tag *t) |
f8758044 | 56 | { |
f8758044 VM |
57 | return t->tagger; |
58 | } | |
59 | ||
d9023dbe | 60 | const char *git_tag_message(const git_tag *t) |
f8758044 | 61 | { |
58519018 | 62 | assert(t); |
f8758044 VM |
63 | return t->message; |
64 | } | |
65 | ||
3aa351ea CMN |
66 | static int tag_error(const char *str) |
67 | { | |
68 | giterr_set(GITERR_TAG, "Failed to parse tag. %s", str); | |
69 | return -1; | |
70 | } | |
71 | ||
b60deb02 | 72 | int git_tag__parse_buffer(git_tag *tag, const char *buffer, size_t length) |
f8758044 VM |
73 | { |
74 | static const char *tag_types[] = { | |
75 | NULL, "commit\n", "tree\n", "blob\n", "tag\n" | |
76 | }; | |
77 | ||
44ef8b1b RB |
78 | unsigned int i; |
79 | size_t text_len; | |
f8758044 VM |
80 | char *search; |
81 | ||
b60deb02 JF |
82 | const char *buffer_end = buffer + length; |
83 | ||
3aa351ea CMN |
84 | if (git_oid__parse(&tag->target, &buffer, buffer_end, "object ") < 0) |
85 | return tag_error("Object field invalid"); | |
f8758044 VM |
86 | |
87 | if (buffer + 5 >= buffer_end) | |
3aa351ea | 88 | return tag_error("Object too short"); |
f8758044 VM |
89 | |
90 | if (memcmp(buffer, "type ", 5) != 0) | |
3aa351ea | 91 | return tag_error("Type field not found"); |
f8758044 VM |
92 | buffer += 5; |
93 | ||
94 | tag->type = GIT_OBJ_BAD; | |
95 | ||
96 | for (i = 1; i < ARRAY_SIZE(tag_types); ++i) { | |
97 | size_t type_length = strlen(tag_types[i]); | |
98 | ||
99 | if (buffer + type_length >= buffer_end) | |
3aa351ea | 100 | return tag_error("Object too short"); |
f8758044 VM |
101 | |
102 | if (memcmp(buffer, tag_types[i], type_length) == 0) { | |
103 | tag->type = i; | |
104 | buffer += type_length; | |
105 | break; | |
106 | } | |
107 | } | |
108 | ||
109 | if (tag->type == GIT_OBJ_BAD) | |
3aa351ea | 110 | return tag_error("Invalid object type"); |
f8758044 | 111 | |
f8758044 | 112 | if (buffer + 4 >= buffer_end) |
3aa351ea | 113 | return tag_error("Object too short"); |
f8758044 VM |
114 | |
115 | if (memcmp(buffer, "tag ", 4) != 0) | |
3aa351ea | 116 | return tag_error("Tag field not found"); |
44dc0d26 | 117 | |
f8758044 VM |
118 | buffer += 4; |
119 | ||
120 | search = memchr(buffer, '\n', buffer_end - buffer); | |
121 | if (search == NULL) | |
3aa351ea | 122 | return tag_error("Object too short"); |
f8758044 VM |
123 | |
124 | text_len = search - buffer; | |
125 | ||
f8758044 | 126 | tag->tag_name = git__malloc(text_len + 1); |
3aa351ea | 127 | GITERR_CHECK_ALLOC(tag->tag_name); |
076141a1 | 128 | |
f8758044 VM |
129 | memcpy(tag->tag_name, buffer, text_len); |
130 | tag->tag_name[text_len] = '\0'; | |
131 | ||
132 | buffer = search + 1; | |
133 | ||
15b0bed2 | 134 | tag->tagger = NULL; |
24cb87e2 | 135 | if (buffer < buffer_end && *buffer != '\n') { |
15b0bed2 | 136 | tag->tagger = git__malloc(sizeof(git_signature)); |
3aa351ea | 137 | GITERR_CHECK_ALLOC(tag->tagger); |
15b0bed2 | 138 | |
3aa351ea CMN |
139 | if (git_signature__parse(tag->tagger, &buffer, buffer_end, "tagger ", '\n') < 0) |
140 | return -1; | |
15b0bed2 | 141 | } |
f8758044 | 142 | |
6bb9fea1 EZ |
143 | tag->message = NULL; |
144 | if (buffer < buffer_end) { | |
145 | if( *buffer != '\n' ) | |
146 | return tag_error("No new line before message"); | |
fbfc7580 | 147 | |
6bb9fea1 | 148 | text_len = buffer_end - ++buffer; |
f8758044 | 149 | |
6bb9fea1 EZ |
150 | tag->message = git__malloc(text_len + 1); |
151 | GITERR_CHECK_ALLOC(tag->message); | |
076141a1 | 152 | |
6bb9fea1 EZ |
153 | memcpy(tag->message, buffer, text_len); |
154 | tag->message[text_len] = '\0'; | |
155 | } | |
f8758044 | 156 | |
3aa351ea | 157 | return 0; |
f8758044 VM |
158 | } |
159 | ||
97769280 RB |
160 | static int retrieve_tag_reference( |
161 | git_reference **tag_reference_out, | |
162 | git_buf *ref_name_out, | |
163 | git_repository *repo, | |
164 | const char *tag_name) | |
ec25391d | 165 | { |
9e680bcc | 166 | git_reference *tag_ref; |
167 | int error; | |
168 | ||
75abd2b9 MS |
169 | *tag_reference_out = NULL; |
170 | ||
3aa351ea CMN |
171 | if (git_buf_joinpath(ref_name_out, GIT_REFS_TAGS_DIR, tag_name) < 0) |
172 | return -1; | |
97769280 RB |
173 | |
174 | error = git_reference_lookup(&tag_ref, repo, ref_name_out->ptr); | |
e172cf08 | 175 | if (error < 0) |
3aa351ea | 176 | return error; /* Be it not foundo or corrupted */ |
9e680bcc | 177 | |
3e3e4631 | 178 | *tag_reference_out = tag_ref; |
9e680bcc | 179 | |
3aa351ea CMN |
180 | return 0; |
181 | } | |
182 | ||
183 | static int retrieve_tag_reference_oid( | |
184 | git_oid *oid, | |
185 | git_buf *ref_name_out, | |
186 | git_repository *repo, | |
187 | const char *tag_name) | |
188 | { | |
189 | if (git_buf_joinpath(ref_name_out, GIT_REFS_TAGS_DIR, tag_name) < 0) | |
190 | return -1; | |
191 | ||
2508cc66 | 192 | return git_reference_name_to_id(oid, repo, ref_name_out->ptr); |
72a3fe42 | 193 | } |
ec25391d | 194 | |
bfbb5562 | 195 | static int write_tag_annotation( |
196 | git_oid *oid, | |
197 | git_repository *repo, | |
198 | const char *tag_name, | |
199 | const git_object *target, | |
200 | const git_signature *tagger, | |
201 | const char *message) | |
202 | { | |
743a4b3b | 203 | git_buf tag = GIT_BUF_INIT; |
9462c471 | 204 | git_odb *odb; |
bfbb5562 | 205 | |
206 | git_oid__writebuf(&tag, "object ", git_object_id(target)); | |
207 | git_buf_printf(&tag, "type %s\n", git_object_type2string(git_object_type(target))); | |
208 | git_buf_printf(&tag, "tag %s\n", tag_name); | |
209 | git_signature__writebuf(&tag, "tagger ", tagger); | |
210 | git_buf_putc(&tag, '\n'); | |
bfbb5562 | 211 | |
743a4b3b | 212 | if (git_buf_puts(&tag, message) < 0) |
3aa351ea | 213 | goto on_error; |
bfbb5562 | 214 | |
3aa351ea CMN |
215 | if (git_repository_odb__weakptr(&odb, repo) < 0) |
216 | goto on_error; | |
9462c471 | 217 | |
3aa351ea CMN |
218 | if (git_odb_write(oid, odb, tag.ptr, tag.size, GIT_OBJ_TAG) < 0) |
219 | goto on_error; | |
bfbb5562 | 220 | |
3aa351ea CMN |
221 | git_buf_free(&tag); |
222 | return 0; | |
458b9450 | 223 | |
3aa351ea CMN |
224 | on_error: |
225 | git_buf_free(&tag); | |
458b9450 | 226 | giterr_set(GITERR_OBJECT, "Failed to create tag annotation."); |
3aa351ea | 227 | return -1; |
bfbb5562 | 228 | } |
229 | ||
230 | static int git_tag_create__internal( | |
72a3fe42 VM |
231 | git_oid *oid, |
232 | git_repository *repo, | |
233 | const char *tag_name, | |
d5afc039 | 234 | const git_object *target, |
72a3fe42 | 235 | const git_signature *tagger, |
a50c1458 | 236 | const char *message, |
bfbb5562 | 237 | int allow_ref_overwrite, |
238 | int create_tag_annotation) | |
72a3fe42 | 239 | { |
d5afc039 | 240 | git_reference *new_ref = NULL; |
97769280 | 241 | git_buf ref_name = GIT_BUF_INIT; |
ec25391d | 242 | |
3aa351ea | 243 | int error; |
72a3fe42 | 244 | |
bfbb5562 | 245 | assert(repo && tag_name && target); |
246 | assert(!create_tag_annotation || (tagger && message)); | |
247 | ||
3aa351ea CMN |
248 | if (git_object_owner(target) != repo) { |
249 | giterr_set(GITERR_INVALID, "The given target does not belong to this repository"); | |
250 | return -1; | |
251 | } | |
d5afc039 | 252 | |
3aa351ea | 253 | error = retrieve_tag_reference_oid(oid, &ref_name, repo, tag_name); |
904b67e6 | 254 | if (error < 0 && error != GIT_ENOTFOUND) |
18d6f120 | 255 | goto cleanup; |
d5afc039 | 256 | |
932d1baf | 257 | /** Ensure the tag name doesn't conflict with an already existing |
87d9869f | 258 | * reference unless overwriting has explictly been requested **/ |
3aa351ea CMN |
259 | if (error == 0 && !allow_ref_overwrite) { |
260 | git_buf_free(&ref_name); | |
261 | giterr_set(GITERR_TAG, "Tag already exists"); | |
904b67e6 | 262 | return GIT_EEXISTS; |
81234673 CMN |
263 | } |
264 | ||
bfbb5562 | 265 | if (create_tag_annotation) { |
3aa351ea CMN |
266 | if (write_tag_annotation(oid, repo, tag_name, target, tagger, message) < 0) |
267 | return -1; | |
bfbb5562 | 268 | } else |
269 | git_oid_cpy(oid, git_object_id(target)); | |
72a3fe42 | 270 | |
2508cc66 | 271 | error = git_reference_create(&new_ref, repo, ref_name.ptr, oid, allow_ref_overwrite); |
932d1baf | 272 | |
18d6f120 | 273 | cleanup: |
75abd2b9 | 274 | git_reference_free(new_ref); |
97769280 | 275 | git_buf_free(&ref_name); |
97769280 | 276 | return error; |
ec25391d VM |
277 | } |
278 | ||
bfbb5562 | 279 | int git_tag_create( |
280 | git_oid *oid, | |
281 | git_repository *repo, | |
282 | const char *tag_name, | |
283 | const git_object *target, | |
284 | const git_signature *tagger, | |
285 | const char *message, | |
286 | int allow_ref_overwrite) | |
287 | { | |
288 | return git_tag_create__internal(oid, repo, tag_name, target, tagger, message, allow_ref_overwrite, 1); | |
289 | } | |
290 | ||
291 | int git_tag_create_lightweight( | |
292 | git_oid *oid, | |
293 | git_repository *repo, | |
294 | const char *tag_name, | |
295 | const git_object *target, | |
296 | int allow_ref_overwrite) | |
297 | { | |
298 | return git_tag_create__internal(oid, repo, tag_name, target, NULL, NULL, allow_ref_overwrite, 0); | |
299 | } | |
300 | ||
d5afc039 | 301 | int git_tag_create_frombuffer(git_oid *oid, git_repository *repo, const char *buffer, int allow_ref_overwrite) |
7b4a16e2 CMN |
302 | { |
303 | git_tag tag; | |
3aa351ea | 304 | int error; |
9462c471 | 305 | git_odb *odb; |
ac4fcf17 | 306 | git_odb_stream *stream; |
d5afc039 | 307 | git_odb_object *target_obj; |
932d1baf | 308 | |
75abd2b9 | 309 | git_reference *new_ref = NULL; |
97769280 | 310 | git_buf ref_name = GIT_BUF_INIT; |
932d1baf | 311 | |
7b4a16e2 | 312 | assert(oid && buffer); |
932d1baf | 313 | |
7b4a16e2 | 314 | memset(&tag, 0, sizeof(tag)); |
932d1baf | 315 | |
3aa351ea CMN |
316 | if (git_repository_odb__weakptr(&odb, repo) < 0) |
317 | return -1; | |
9462c471 | 318 | |
ac4fcf17 | 319 | /* validate the buffer */ |
3aa351ea CMN |
320 | if (git_tag__parse_buffer(&tag, buffer, strlen(buffer)) < 0) |
321 | return -1; | |
d5afc039 VM |
322 | |
323 | /* validate the target */ | |
3aa351ea CMN |
324 | if (git_odb_read(&target_obj, odb, &tag.target) < 0) |
325 | goto on_error; | |
d5afc039 | 326 | |
8842c75f | 327 | if (tag.type != target_obj->cached.type) { |
3aa351ea CMN |
328 | giterr_set(GITERR_TAG, "The type for the given target is invalid"); |
329 | goto on_error; | |
97769280 | 330 | } |
d5afc039 | 331 | |
3aa351ea | 332 | error = retrieve_tag_reference_oid(oid, &ref_name, repo, tag.tag_name); |
904b67e6 | 333 | if (error < 0 && error != GIT_ENOTFOUND) |
3aa351ea CMN |
334 | goto on_error; |
335 | ||
336 | /* We don't need these objects after this */ | |
337 | git_signature_free(tag.tagger); | |
338 | git__free(tag.tag_name); | |
339 | git__free(tag.message); | |
340 | git_odb_object_free(target_obj); | |
d5afc039 VM |
341 | |
342 | /** Ensure the tag name doesn't conflict with an already existing | |
87d9869f | 343 | * reference unless overwriting has explictly been requested **/ |
3aa351ea CMN |
344 | if (error == 0 && !allow_ref_overwrite) { |
345 | giterr_set(GITERR_TAG, "Tag already exists"); | |
904b67e6 | 346 | return GIT_EEXISTS; |
ac4fcf17 | 347 | } |
932d1baf | 348 | |
ac4fcf17 | 349 | /* write the buffer */ |
3aa351ea CMN |
350 | if (git_odb_open_wstream(&stream, odb, strlen(buffer), GIT_OBJ_TAG) < 0) |
351 | return -1; | |
932d1baf | 352 | |
ac4fcf17 | 353 | stream->write(stream, buffer, strlen(buffer)); |
932d1baf | 354 | |
ac4fcf17 DG |
355 | error = stream->finalize_write(oid, stream); |
356 | stream->free(stream); | |
932d1baf | 357 | |
3aa351ea CMN |
358 | if (error < 0) { |
359 | git_buf_free(&ref_name); | |
360 | return -1; | |
361 | } | |
d5afc039 | 362 | |
2508cc66 | 363 | error = git_reference_create(&new_ref, repo, ref_name.ptr, oid, allow_ref_overwrite); |
932d1baf | 364 | |
75abd2b9 | 365 | git_reference_free(new_ref); |
97769280 RB |
366 | git_buf_free(&ref_name); |
367 | ||
97769280 | 368 | return error; |
3aa351ea CMN |
369 | |
370 | on_error: | |
371 | git_signature_free(tag.tagger); | |
372 | git__free(tag.tag_name); | |
373 | git__free(tag.message); | |
374 | git_odb_object_free(target_obj); | |
375 | return -1; | |
7b4a16e2 CMN |
376 | } |
377 | ||
9e680bcc | 378 | int git_tag_delete(git_repository *repo, const char *tag_name) |
379 | { | |
9e680bcc | 380 | git_reference *tag_ref; |
97769280 | 381 | git_buf ref_name = GIT_BUF_INIT; |
d00d5464 | 382 | int error; |
97769280 RB |
383 | |
384 | error = retrieve_tag_reference(&tag_ref, &ref_name, repo, tag_name); | |
385 | ||
386 | git_buf_free(&ref_name); | |
9e680bcc | 387 | |
3aa351ea | 388 | if (error < 0) |
18d6f120 | 389 | return error; |
9e680bcc | 390 | |
d00d5464 ET |
391 | if ((error = git_reference_delete(tag_ref)) == 0) |
392 | git_reference_free(tag_ref); | |
393 | ||
394 | return error; | |
9e680bcc | 395 | } |
396 | ||
72a3fe42 | 397 | int git_tag__parse(git_tag *tag, git_odb_object *obj) |
f8758044 | 398 | { |
72a3fe42 | 399 | assert(tag); |
badd85a6 RB |
400 | return git_tag__parse_buffer( |
401 | tag, git_odb_object_data(obj), git_odb_object_size(obj)); | |
f8758044 VM |
402 | } |
403 | ||
2b5af615 | 404 | typedef struct { |
2f05339e MS |
405 | git_repository *repo; |
406 | git_tag_foreach_cb cb; | |
407 | void *cb_data; | |
408 | } tag_cb_data; | |
409 | ||
410 | static int tags_cb(const char *ref, void *data) | |
411 | { | |
412 | git_oid oid; | |
413 | tag_cb_data *d = (tag_cb_data *)data; | |
414 | ||
415 | if (git__prefixcmp(ref, GIT_REFS_TAGS_DIR) != 0) | |
416 | return 0; /* no tag */ | |
417 | ||
2508cc66 | 418 | if (git_reference_name_to_id(&oid, d->repo, ref) < 0) |
2f05339e MS |
419 | return -1; |
420 | ||
421 | return d->cb(ref, &oid, d->cb_data); | |
422 | } | |
423 | ||
424 | int git_tag_foreach(git_repository *repo, git_tag_foreach_cb cb, void *cb_data) | |
425 | { | |
426 | tag_cb_data data; | |
427 | ||
428 | assert(repo && cb); | |
429 | ||
430 | data.cb = cb; | |
431 | data.cb_data = cb_data; | |
432 | data.repo = repo; | |
433 | ||
d00d5464 | 434 | return git_reference_foreach(repo, GIT_REF_OID, &tags_cb, &data); |
2f05339e MS |
435 | } |
436 | ||
437 | typedef struct { | |
438 | git_vector *taglist; | |
439 | const char *pattern; | |
2b5af615 | 440 | } tag_filter_data; |
441 | ||
932669b8 | 442 | #define GIT_REFS_TAGS_DIR_LEN strlen(GIT_REFS_TAGS_DIR) |
2b5af615 | 443 | |
2f05339e | 444 | static int tag_list_cb(const char *tag_name, git_oid *oid, void *data) |
def3fef1 | 445 | { |
2f05339e MS |
446 | tag_filter_data *filter = (tag_filter_data *)data; |
447 | GIT_UNUSED(oid); | |
2b5af615 | 448 | |
e172cf08 | 449 | if (!*filter->pattern || p_fnmatch(filter->pattern, tag_name + GIT_REFS_TAGS_DIR_LEN, 0) == 0) |
3af06254 | 450 | return git_vector_insert(filter->taglist, git__strdup(tag_name + GIT_REFS_TAGS_DIR_LEN)); |
def3fef1 | 451 | |
3aa351ea | 452 | return 0; |
def3fef1 VM |
453 | } |
454 | ||
2b5af615 | 455 | int git_tag_list_match(git_strarray *tag_names, const char *pattern, git_repository *repo) |
def3fef1 VM |
456 | { |
457 | int error; | |
2b5af615 | 458 | tag_filter_data filter; |
def3fef1 VM |
459 | git_vector taglist; |
460 | ||
2b5af615 | 461 | assert(tag_names && repo && pattern); |
462 | ||
e172cf08 | 463 | if (git_vector_init(&taglist, 8, NULL) < 0) |
3fbcac89 | 464 | return -1; |
def3fef1 | 465 | |
2b5af615 | 466 | filter.taglist = &taglist; |
467 | filter.pattern = pattern; | |
468 | ||
2f05339e | 469 | error = git_tag_foreach(repo, &tag_list_cb, (void *)&filter); |
3aa351ea | 470 | if (error < 0) { |
def3fef1 | 471 | git_vector_free(&taglist); |
3aa351ea | 472 | return -1; |
def3fef1 VM |
473 | } |
474 | ||
475 | tag_names->strings = (char **)taglist.contents; | |
476 | tag_names->count = taglist.length; | |
3aa351ea | 477 | return 0; |
def3fef1 | 478 | } |
2b5af615 | 479 | |
480 | int git_tag_list(git_strarray *tag_names, git_repository *repo) | |
481 | { | |
482 | return git_tag_list_match(tag_names, "", repo); | |
d483a911 | 483 | } |
3f46f313 | 484 | |
d9023dbe | 485 | int git_tag_peel(git_object **tag_target, const git_tag *tag) |
3f46f313 | 486 | { |
d9023dbe | 487 | return git_object_peel(tag_target, (const git_object *)tag, GIT_OBJ_ANY); |
3f46f313 | 488 | } |