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