Merge branch 'rs/status-code-clean-up'
[git] / builtin / tag.c
1 /*
2  * Builtin "git tag"
3  *
4  * Copyright (c) 2007 Kristian Høgsberg <krh@redhat.com>,
5  *                    Carlos Rica <jasampler@gmail.com>
6  * Based on git-tag.sh and mktag.c by Linus Torvalds.
7  */
8
9 #include "cache.h"
10 #include "builtin.h"
11 #include "refs.h"
12 #include "tag.h"
13 #include "run-command.h"
14 #include "parse-options.h"
15 #include "diff.h"
16 #include "revision.h"
17 #include "gpg-interface.h"
18 #include "sha1-array.h"
19 #include "column.h"
20
21 static const char * const git_tag_usage[] = {
22         N_("git tag [-a|-s|-u <key-id>] [-f] [-m <msg>|-F <file>] <tagname> [<head>]"),
23         N_("git tag -d <tagname>..."),
24         N_("git tag -l [-n[<num>]] [--contains <commit>] [--points-at <object>] "
25                 "\n\t\t[<pattern>...]"),
26         N_("git tag -v <tagname>..."),
27         NULL
28 };
29
30 #define STRCMP_SORT     0       /* must be zero */
31 #define VERCMP_SORT     1
32 #define SORT_MASK       0x7fff
33 #define REVERSE_SORT    0x8000
34
35 struct tag_filter {
36         const char **patterns;
37         int lines;
38         int sort;
39         struct string_list tags;
40         struct commit_list *with_commit;
41 };
42
43 static struct sha1_array points_at;
44 static unsigned int colopts;
45
46 static int match_pattern(const char **patterns, const char *ref)
47 {
48         /* no pattern means match everything */
49         if (!*patterns)
50                 return 1;
51         for (; *patterns; patterns++)
52                 if (!wildmatch(*patterns, ref, 0, NULL))
53                         return 1;
54         return 0;
55 }
56
57 static const unsigned char *match_points_at(const char *refname,
58                                             const unsigned char *sha1)
59 {
60         const unsigned char *tagged_sha1 = NULL;
61         struct object *obj;
62
63         if (sha1_array_lookup(&points_at, sha1) >= 0)
64                 return sha1;
65         obj = parse_object(sha1);
66         if (!obj)
67                 die(_("malformed object at '%s'"), refname);
68         if (obj->type == OBJ_TAG)
69                 tagged_sha1 = ((struct tag *)obj)->tagged->sha1;
70         if (tagged_sha1 && sha1_array_lookup(&points_at, tagged_sha1) >= 0)
71                 return tagged_sha1;
72         return NULL;
73 }
74
75 static int in_commit_list(const struct commit_list *want, struct commit *c)
76 {
77         for (; want; want = want->next)
78                 if (!hashcmp(want->item->object.sha1, c->object.sha1))
79                         return 1;
80         return 0;
81 }
82
83 enum contains_result {
84         CONTAINS_UNKNOWN = -1,
85         CONTAINS_NO = 0,
86         CONTAINS_YES = 1
87 };
88
89 /*
90  * Test whether the candidate or one of its parents is contained in the list.
91  * Do not recurse to find out, though, but return -1 if inconclusive.
92  */
93 static enum contains_result contains_test(struct commit *candidate,
94                             const struct commit_list *want)
95 {
96         /* was it previously marked as containing a want commit? */
97         if (candidate->object.flags & TMP_MARK)
98                 return 1;
99         /* or marked as not possibly containing a want commit? */
100         if (candidate->object.flags & UNINTERESTING)
101                 return 0;
102         /* or are we it? */
103         if (in_commit_list(want, candidate)) {
104                 candidate->object.flags |= TMP_MARK;
105                 return 1;
106         }
107
108         if (parse_commit(candidate) < 0)
109                 return 0;
110
111         return -1;
112 }
113
114 /*
115  * Mimicking the real stack, this stack lives on the heap, avoiding stack
116  * overflows.
117  *
118  * At each recursion step, the stack items points to the commits whose
119  * ancestors are to be inspected.
120  */
121 struct stack {
122         int nr, alloc;
123         struct stack_entry {
124                 struct commit *commit;
125                 struct commit_list *parents;
126         } *stack;
127 };
128
129 static void push_to_stack(struct commit *candidate, struct stack *stack)
130 {
131         int index = stack->nr++;
132         ALLOC_GROW(stack->stack, stack->nr, stack->alloc);
133         stack->stack[index].commit = candidate;
134         stack->stack[index].parents = candidate->parents;
135 }
136
137 static enum contains_result contains(struct commit *candidate,
138                 const struct commit_list *want)
139 {
140         struct stack stack = { 0, 0, NULL };
141         int result = contains_test(candidate, want);
142
143         if (result != CONTAINS_UNKNOWN)
144                 return result;
145
146         push_to_stack(candidate, &stack);
147         while (stack.nr) {
148                 struct stack_entry *entry = &stack.stack[stack.nr - 1];
149                 struct commit *commit = entry->commit;
150                 struct commit_list *parents = entry->parents;
151
152                 if (!parents) {
153                         commit->object.flags |= UNINTERESTING;
154                         stack.nr--;
155                 }
156                 /*
157                  * If we just popped the stack, parents->item has been marked,
158                  * therefore contains_test will return a meaningful 0 or 1.
159                  */
160                 else switch (contains_test(parents->item, want)) {
161                 case CONTAINS_YES:
162                         commit->object.flags |= TMP_MARK;
163                         stack.nr--;
164                         break;
165                 case CONTAINS_NO:
166                         entry->parents = parents->next;
167                         break;
168                 case CONTAINS_UNKNOWN:
169                         push_to_stack(parents->item, &stack);
170                         break;
171                 }
172         }
173         free(stack.stack);
174         return contains_test(candidate, want);
175 }
176
177 static void show_tag_lines(const unsigned char *sha1, int lines)
178 {
179         int i;
180         unsigned long size;
181         enum object_type type;
182         char *buf, *sp, *eol;
183         size_t len;
184
185         buf = read_sha1_file(sha1, &type, &size);
186         if (!buf)
187                 die_errno("unable to read object %s", sha1_to_hex(sha1));
188         if (type != OBJ_COMMIT && type != OBJ_TAG)
189                 goto free_return;
190         if (!size)
191                 die("an empty %s object %s?",
192                     typename(type), sha1_to_hex(sha1));
193
194         /* skip header */
195         sp = strstr(buf, "\n\n");
196         if (!sp)
197                 goto free_return;
198
199         /* only take up to "lines" lines, and strip the signature from a tag */
200         if (type == OBJ_TAG)
201                 size = parse_signature(buf, size);
202         for (i = 0, sp += 2; i < lines && sp < buf + size; i++) {
203                 if (i)
204                         printf("\n    ");
205                 eol = memchr(sp, '\n', size - (sp - buf));
206                 len = eol ? eol - sp : size - (sp - buf);
207                 fwrite(sp, len, 1, stdout);
208                 if (!eol)
209                         break;
210                 sp = eol + 1;
211         }
212 free_return:
213         free(buf);
214 }
215
216 static int show_reference(const char *refname, const unsigned char *sha1,
217                           int flag, void *cb_data)
218 {
219         struct tag_filter *filter = cb_data;
220
221         if (match_pattern(filter->patterns, refname)) {
222                 if (filter->with_commit) {
223                         struct commit *commit;
224
225                         commit = lookup_commit_reference_gently(sha1, 1);
226                         if (!commit)
227                                 return 0;
228                         if (!contains(commit, filter->with_commit))
229                                 return 0;
230                 }
231
232                 if (points_at.nr && !match_points_at(refname, sha1))
233                         return 0;
234
235                 if (!filter->lines) {
236                         if (filter->sort)
237                                 string_list_append(&filter->tags, refname);
238                         else
239                                 printf("%s\n", refname);
240                         return 0;
241                 }
242                 printf("%-15s ", refname);
243                 show_tag_lines(sha1, filter->lines);
244                 putchar('\n');
245         }
246
247         return 0;
248 }
249
250 static int sort_by_version(const void *a_, const void *b_)
251 {
252         const struct string_list_item *a = a_;
253         const struct string_list_item *b = b_;
254         return versioncmp(a->string, b->string);
255 }
256
257 static int list_tags(const char **patterns, int lines,
258                      struct commit_list *with_commit, int sort)
259 {
260         struct tag_filter filter;
261
262         filter.patterns = patterns;
263         filter.lines = lines;
264         filter.sort = sort;
265         filter.with_commit = with_commit;
266         memset(&filter.tags, 0, sizeof(filter.tags));
267         filter.tags.strdup_strings = 1;
268
269         for_each_tag_ref(show_reference, (void *) &filter);
270         if (sort) {
271                 int i;
272                 if ((sort & SORT_MASK) == VERCMP_SORT)
273                         qsort(filter.tags.items, filter.tags.nr,
274                               sizeof(struct string_list_item), sort_by_version);
275                 if (sort & REVERSE_SORT)
276                         for (i = filter.tags.nr - 1; i >= 0; i--)
277                                 printf("%s\n", filter.tags.items[i].string);
278                 else
279                         for (i = 0; i < filter.tags.nr; i++)
280                                 printf("%s\n", filter.tags.items[i].string);
281                 string_list_clear(&filter.tags, 0);
282         }
283         return 0;
284 }
285
286 typedef int (*each_tag_name_fn)(const char *name, const char *ref,
287                                 const unsigned char *sha1);
288
289 static int for_each_tag_name(const char **argv, each_tag_name_fn fn)
290 {
291         const char **p;
292         char ref[PATH_MAX];
293         int had_error = 0;
294         unsigned char sha1[20];
295
296         for (p = argv; *p; p++) {
297                 if (snprintf(ref, sizeof(ref), "refs/tags/%s", *p)
298                                         >= sizeof(ref)) {
299                         error(_("tag name too long: %.*s..."), 50, *p);
300                         had_error = 1;
301                         continue;
302                 }
303                 if (read_ref(ref, sha1)) {
304                         error(_("tag '%s' not found."), *p);
305                         had_error = 1;
306                         continue;
307                 }
308                 if (fn(*p, ref, sha1))
309                         had_error = 1;
310         }
311         return had_error;
312 }
313
314 static int delete_tag(const char *name, const char *ref,
315                                 const unsigned char *sha1)
316 {
317         if (delete_ref(ref, sha1, 0))
318                 return 1;
319         printf(_("Deleted tag '%s' (was %s)\n"), name, find_unique_abbrev(sha1, DEFAULT_ABBREV));
320         return 0;
321 }
322
323 static int verify_tag(const char *name, const char *ref,
324                                 const unsigned char *sha1)
325 {
326         const char *argv_verify_tag[] = {"verify-tag",
327                                         "-v", "SHA1_HEX", NULL};
328         argv_verify_tag[2] = sha1_to_hex(sha1);
329
330         if (run_command_v_opt(argv_verify_tag, RUN_GIT_CMD))
331                 return error(_("could not verify the tag '%s'"), name);
332         return 0;
333 }
334
335 static int do_sign(struct strbuf *buffer)
336 {
337         return sign_buffer(buffer, buffer, get_signing_key());
338 }
339
340 static const char tag_template[] =
341         N_("\nWrite a message for tag:\n  %s\n"
342         "Lines starting with '%c' will be ignored.\n");
343
344 static const char tag_template_nocleanup[] =
345         N_("\nWrite a message for tag:\n  %s\n"
346         "Lines starting with '%c' will be kept; you may remove them"
347         " yourself if you want to.\n");
348
349 static int git_tag_config(const char *var, const char *value, void *cb)
350 {
351         int status = git_gpg_config(var, value, cb);
352         if (status)
353                 return status;
354         if (starts_with(var, "column."))
355                 return git_column_config(var, value, "tag", &colopts);
356         return git_default_config(var, value, cb);
357 }
358
359 static void write_tag_body(int fd, const unsigned char *sha1)
360 {
361         unsigned long size;
362         enum object_type type;
363         char *buf, *sp;
364
365         buf = read_sha1_file(sha1, &type, &size);
366         if (!buf)
367                 return;
368         /* skip header */
369         sp = strstr(buf, "\n\n");
370
371         if (!sp || !size || type != OBJ_TAG) {
372                 free(buf);
373                 return;
374         }
375         sp += 2; /* skip the 2 LFs */
376         write_or_die(fd, sp, parse_signature(sp, buf + size - sp));
377
378         free(buf);
379 }
380
381 static int build_tag_object(struct strbuf *buf, int sign, unsigned char *result)
382 {
383         if (sign && do_sign(buf) < 0)
384                 return error(_("unable to sign the tag"));
385         if (write_sha1_file(buf->buf, buf->len, tag_type, result) < 0)
386                 return error(_("unable to write tag file"));
387         return 0;
388 }
389
390 struct create_tag_options {
391         unsigned int message_given:1;
392         unsigned int sign;
393         enum {
394                 CLEANUP_NONE,
395                 CLEANUP_SPACE,
396                 CLEANUP_ALL
397         } cleanup_mode;
398 };
399
400 static void create_tag(const unsigned char *object, const char *tag,
401                        struct strbuf *buf, struct create_tag_options *opt,
402                        unsigned char *prev, unsigned char *result)
403 {
404         enum object_type type;
405         char header_buf[1024];
406         int header_len;
407         char *path = NULL;
408
409         type = sha1_object_info(object, NULL);
410         if (type <= OBJ_NONE)
411             die(_("bad object type."));
412
413         header_len = snprintf(header_buf, sizeof(header_buf),
414                           "object %s\n"
415                           "type %s\n"
416                           "tag %s\n"
417                           "tagger %s\n\n",
418                           sha1_to_hex(object),
419                           typename(type),
420                           tag,
421                           git_committer_info(IDENT_STRICT));
422
423         if (header_len > sizeof(header_buf) - 1)
424                 die(_("tag header too big."));
425
426         if (!opt->message_given) {
427                 int fd;
428
429                 /* write the template message before editing: */
430                 path = git_pathdup("TAG_EDITMSG");
431                 fd = open(path, O_CREAT | O_TRUNC | O_WRONLY, 0600);
432                 if (fd < 0)
433                         die_errno(_("could not create file '%s'"), path);
434
435                 if (!is_null_sha1(prev)) {
436                         write_tag_body(fd, prev);
437                 } else {
438                         struct strbuf buf = STRBUF_INIT;
439                         strbuf_addch(&buf, '\n');
440                         if (opt->cleanup_mode == CLEANUP_ALL)
441                                 strbuf_commented_addf(&buf, _(tag_template), tag, comment_line_char);
442                         else
443                                 strbuf_commented_addf(&buf, _(tag_template_nocleanup), tag, comment_line_char);
444                         write_or_die(fd, buf.buf, buf.len);
445                         strbuf_release(&buf);
446                 }
447                 close(fd);
448
449                 if (launch_editor(path, buf, NULL)) {
450                         fprintf(stderr,
451                         _("Please supply the message using either -m or -F option.\n"));
452                         exit(1);
453                 }
454         }
455
456         if (opt->cleanup_mode != CLEANUP_NONE)
457                 stripspace(buf, opt->cleanup_mode == CLEANUP_ALL);
458
459         if (!opt->message_given && !buf->len)
460                 die(_("no tag message?"));
461
462         strbuf_insert(buf, 0, header_buf, header_len);
463
464         if (build_tag_object(buf, opt->sign, result) < 0) {
465                 if (path)
466                         fprintf(stderr, _("The tag message has been left in %s\n"),
467                                 path);
468                 exit(128);
469         }
470         if (path) {
471                 unlink_or_warn(path);
472                 free(path);
473         }
474 }
475
476 struct msg_arg {
477         int given;
478         struct strbuf buf;
479 };
480
481 static int parse_msg_arg(const struct option *opt, const char *arg, int unset)
482 {
483         struct msg_arg *msg = opt->value;
484
485         if (!arg)
486                 return -1;
487         if (msg->buf.len)
488                 strbuf_addstr(&(msg->buf), "\n\n");
489         strbuf_addstr(&(msg->buf), arg);
490         msg->given = 1;
491         return 0;
492 }
493
494 static int strbuf_check_tag_ref(struct strbuf *sb, const char *name)
495 {
496         if (name[0] == '-')
497                 return -1;
498
499         strbuf_reset(sb);
500         strbuf_addf(sb, "refs/tags/%s", name);
501
502         return check_refname_format(sb->buf, 0);
503 }
504
505 static int parse_opt_points_at(const struct option *opt __attribute__((unused)),
506                         const char *arg, int unset)
507 {
508         unsigned char sha1[20];
509
510         if (unset) {
511                 sha1_array_clear(&points_at);
512                 return 0;
513         }
514         if (!arg)
515                 return error(_("switch 'points-at' requires an object"));
516         if (get_sha1(arg, sha1))
517                 return error(_("malformed object name '%s'"), arg);
518         sha1_array_append(&points_at, sha1);
519         return 0;
520 }
521
522 static int parse_opt_sort(const struct option *opt, const char *arg, int unset)
523 {
524         int *sort = opt->value;
525         int flags = 0;
526
527         if (*arg == '-') {
528                 flags |= REVERSE_SORT;
529                 arg++;
530         }
531         if (starts_with(arg, "version:")) {
532                 *sort = VERCMP_SORT;
533                 arg += 8;
534         } else if (starts_with(arg, "v:")) {
535                 *sort = VERCMP_SORT;
536                 arg += 2;
537         } else
538                 *sort = STRCMP_SORT;
539         if (strcmp(arg, "refname"))
540                 die(_("unsupported sort specification %s"), arg);
541         *sort |= flags;
542         return 0;
543 }
544
545 int cmd_tag(int argc, const char **argv, const char *prefix)
546 {
547         struct strbuf buf = STRBUF_INIT;
548         struct strbuf ref = STRBUF_INIT;
549         unsigned char object[20], prev[20];
550         const char *object_ref, *tag;
551         struct ref_lock *lock;
552         struct create_tag_options opt;
553         char *cleanup_arg = NULL;
554         int annotate = 0, force = 0, lines = -1;
555         int cmdmode = 0, sort = 0;
556         const char *msgfile = NULL, *keyid = NULL;
557         struct msg_arg msg = { 0, STRBUF_INIT };
558         struct commit_list *with_commit = NULL;
559         struct option options[] = {
560                 OPT_CMDMODE('l', "list", &cmdmode, N_("list tag names"), 'l'),
561                 { OPTION_INTEGER, 'n', NULL, &lines, N_("n"),
562                                 N_("print <n> lines of each tag message"),
563                                 PARSE_OPT_OPTARG, NULL, 1 },
564                 OPT_CMDMODE('d', "delete", &cmdmode, N_("delete tags"), 'd'),
565                 OPT_CMDMODE('v', "verify", &cmdmode, N_("verify tags"), 'v'),
566
567                 OPT_GROUP(N_("Tag creation options")),
568                 OPT_BOOL('a', "annotate", &annotate,
569                                         N_("annotated tag, needs a message")),
570                 OPT_CALLBACK('m', "message", &msg, N_("message"),
571                              N_("tag message"), parse_msg_arg),
572                 OPT_FILENAME('F', "file", &msgfile, N_("read message from file")),
573                 OPT_BOOL('s', "sign", &opt.sign, N_("annotated and GPG-signed tag")),
574                 OPT_STRING(0, "cleanup", &cleanup_arg, N_("mode"),
575                         N_("how to strip spaces and #comments from message")),
576                 OPT_STRING('u', "local-user", &keyid, N_("key-id"),
577                                         N_("use another key to sign the tag")),
578                 OPT__FORCE(&force, N_("replace the tag if exists")),
579                 OPT_COLUMN(0, "column", &colopts, N_("show tag list in columns")),
580                 {
581                         OPTION_CALLBACK, 0, "sort", &sort, N_("type"), N_("sort tags"),
582                         PARSE_OPT_NONEG, parse_opt_sort
583                 },
584
585                 OPT_GROUP(N_("Tag listing options")),
586                 {
587                         OPTION_CALLBACK, 0, "contains", &with_commit, N_("commit"),
588                         N_("print only tags that contain the commit"),
589                         PARSE_OPT_LASTARG_DEFAULT,
590                         parse_opt_with_commit, (intptr_t)"HEAD",
591                 },
592                 {
593                         OPTION_CALLBACK, 0, "with", &with_commit, N_("commit"),
594                         N_("print only tags that contain the commit"),
595                         PARSE_OPT_HIDDEN | PARSE_OPT_LASTARG_DEFAULT,
596                         parse_opt_with_commit, (intptr_t)"HEAD",
597                 },
598                 {
599                         OPTION_CALLBACK, 0, "points-at", NULL, N_("object"),
600                         N_("print only tags of the object"), 0, parse_opt_points_at
601                 },
602                 OPT_END()
603         };
604
605         git_config(git_tag_config, NULL);
606
607         memset(&opt, 0, sizeof(opt));
608
609         argc = parse_options(argc, argv, prefix, options, git_tag_usage, 0);
610
611         if (keyid) {
612                 opt.sign = 1;
613                 set_signing_key(keyid);
614         }
615         if (opt.sign)
616                 annotate = 1;
617         if (argc == 0 && !cmdmode)
618                 cmdmode = 'l';
619
620         if ((annotate || msg.given || msgfile || force) && (cmdmode != 0))
621                 usage_with_options(git_tag_usage, options);
622
623         finalize_colopts(&colopts, -1);
624         if (cmdmode == 'l' && lines != -1) {
625                 if (explicitly_enable_column(colopts))
626                         die(_("--column and -n are incompatible"));
627                 colopts = 0;
628         }
629         if (cmdmode == 'l') {
630                 int ret;
631                 if (column_active(colopts)) {
632                         struct column_options copts;
633                         memset(&copts, 0, sizeof(copts));
634                         copts.padding = 2;
635                         run_column_filter(colopts, &copts);
636                 }
637                 if (lines != -1 && sort)
638                         die(_("--sort and -n are incompatible"));
639                 ret = list_tags(argv, lines == -1 ? 0 : lines, with_commit, sort);
640                 if (column_active(colopts))
641                         stop_column_filter();
642                 return ret;
643         }
644         if (lines != -1)
645                 die(_("-n option is only allowed with -l."));
646         if (with_commit)
647                 die(_("--contains option is only allowed with -l."));
648         if (points_at.nr)
649                 die(_("--points-at option is only allowed with -l."));
650         if (cmdmode == 'd')
651                 return for_each_tag_name(argv, delete_tag);
652         if (cmdmode == 'v')
653                 return for_each_tag_name(argv, verify_tag);
654
655         if (msg.given || msgfile) {
656                 if (msg.given && msgfile)
657                         die(_("only one -F or -m option is allowed."));
658                 annotate = 1;
659                 if (msg.given)
660                         strbuf_addbuf(&buf, &(msg.buf));
661                 else {
662                         if (!strcmp(msgfile, "-")) {
663                                 if (strbuf_read(&buf, 0, 1024) < 0)
664                                         die_errno(_("cannot read '%s'"), msgfile);
665                         } else {
666                                 if (strbuf_read_file(&buf, msgfile, 1024) < 0)
667                                         die_errno(_("could not open or read '%s'"),
668                                                 msgfile);
669                         }
670                 }
671         }
672
673         tag = argv[0];
674
675         object_ref = argc == 2 ? argv[1] : "HEAD";
676         if (argc > 2)
677                 die(_("too many params"));
678
679         if (get_sha1(object_ref, object))
680                 die(_("Failed to resolve '%s' as a valid ref."), object_ref);
681
682         if (strbuf_check_tag_ref(&ref, tag))
683                 die(_("'%s' is not a valid tag name."), tag);
684
685         if (read_ref(ref.buf, prev))
686                 hashclr(prev);
687         else if (!force)
688                 die(_("tag '%s' already exists"), tag);
689
690         opt.message_given = msg.given || msgfile;
691
692         if (!cleanup_arg || !strcmp(cleanup_arg, "strip"))
693                 opt.cleanup_mode = CLEANUP_ALL;
694         else if (!strcmp(cleanup_arg, "verbatim"))
695                 opt.cleanup_mode = CLEANUP_NONE;
696         else if (!strcmp(cleanup_arg, "whitespace"))
697                 opt.cleanup_mode = CLEANUP_SPACE;
698         else
699                 die(_("Invalid cleanup mode %s"), cleanup_arg);
700
701         if (annotate)
702                 create_tag(object, tag, &buf, &opt, prev, object);
703
704         lock = lock_any_ref_for_update(ref.buf, prev, 0, NULL);
705         if (!lock)
706                 die(_("%s: cannot lock the ref"), ref.buf);
707         if (write_ref_sha1(lock, object, NULL) < 0)
708                 die(_("%s: cannot update the ref"), ref.buf);
709         if (force && !is_null_sha1(prev) && hashcmp(prev, object))
710                 printf(_("Updated tag '%s' (was %s)\n"), tag, find_unique_abbrev(prev, DEFAULT_ABBREV));
711
712         strbuf_release(&buf);
713         strbuf_release(&ref);
714         return 0;
715 }