blob: 246ff121aa42b70720d1e0fb7af0d43680a52b0d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
/*
 * The tag page, which shows a single ref under refs/tags and is where the
 * branch and tag listings link. An annotated tag is a git object in its own
 * right, so its page carries the tagger, the date and the message, while a
 * lightweight tag is only a name for a commit and gets a shorter table. Both
 * forms link the object the tag points at and, when the repository enables
 * snapshots, offer the tree at that tag for download as an archive.
 */

#define USE_THE_REPOSITORY_VARIABLE

#include "cgit.h"
#include "filter.h"
#include "gpg-interface.h"
#include "html.h"
#include "parsing.h"
#include "shared.h"
#include "ui-shared.h"
#include "ui-tag.h"

static void print_name_row(const char *revname, const struct object_id *oid)
{
	html("<tr><th scope='row'>tag name</th><td>");
	html_txt(revname);
	if (oid)
		htmlf(" (<span class='oid'>%s</span>)", oid_to_hex(oid));
	html("</td></tr>\n");
}

static void print_date_row(struct taginfo *info)
{
	html("<tr><th scope='row'>tag date</th><td><time datetime='");
	html_attr(show_date(info->tagger_date, info->tagger_tz, cgit_date_mode(DATE_ISO8601_STRICT)));
	html("'>");
	html_txt(show_date(info->tagger_date, info->tagger_tz, cgit_date_mode(DATE_ISO8601)));
	html("</time></td></tr>\n");
}

static void print_tagger_row(struct taginfo *info)
{
	html("<tr><th scope='row'>tagged by</th><td>");
	// A tag can carry a tagger with no address, and a filter must not be
	// handed a NULL argument.
	cgit_open_filter(ctx.repo->email_filter, info->tagger_email ? info->tagger_email : "", "tag");
	html_txt(info->tagger);
	if (info->tagger_email && ctx.cfg.enable_plain_email) {
		html(" ");
		html_txt(info->tagger_email);
	}
	cgit_close_filter(ctx.repo->email_filter);
	html("</td></tr>\n");
}

static void print_object_row(struct object *obj)
{
	html("<tr><th scope='row'>tagged object</th><td class='oid'>");
	cgit_object_link(obj, 1);
	html("</td></tr>\n");
}

/*
 * A snapshot is cut from a commit, so a tag of anything else offers none, as in
 * the refs listing.
 */
static void print_download_row(const char *revname, const struct object *obj)
{
	if (!ctx.repo->snapshots || obj->type != OBJ_COMMIT)
		return;
	html("<tr><th scope='row'>download</th><td class='oid'>");
	cgit_print_snapshot_links(ctx.repo, revname, "<br>");
	html("</td></tr>\n");
}

/*
 * Cutting the subject and the signature out terminates the message in place,
 * which is safe only because the caller frees it straight after. The blank
 * lines between the subject and the body are skipped, as the commit page skips
 * them.
 */
static void print_message(char *msg)
{
	char *newline, *body, *signature = NULL;
	size_t len, start;

	if (!msg)
		return;

	// git finds the signature of any kind it can sign with, PGP, SSH or
	// X.509, and it goes in a fold after the message, since its armour
	// would otherwise run on for more lines than the message itself.
	len = strlen(msg);
	start = parse_signed_buffer(msg, len);
	if (start < len) {
		signature = xstrdup(msg + start);
		while (start > 0 && msg[start - 1] == '\n')
			start--;
		msg[start] = '\0';
	}

	html("<div class='commit-subject'>");
	newline = strchr(msg, '\n');
	if (newline)
		*newline = '\0';
	html_txt(msg);
	html("</div>");
	if (newline) {
		body = newline + 1;
		while (*body == '\n')
			body++;
		html("<div class='commit-msg'>");
		html_txt(body);
		html("</div>");
	}
	if (signature) {
		html("<details class='signature'><summary>Signature</summary><pre>");
		html_txt(signature);
		html("</pre></details>");
		free(signature);
	}
}

static void print_annotated_tag(const char *revname, const struct object_id *oid)
{
	struct tag *tag;
	struct taginfo *info;

	tag = lookup_tag(the_repository, oid);
	if (!tag || parse_tag(the_repository, tag) || !(info = cgit_parse_tag(tag))) {
		cgit_print_error_page(500, "Internal Server Error", "Bad tag object: %s", revname);
		return;
	}

	cgit_print_layout_start();
	html("<table class='commit-info'>\n");
	print_name_row(revname, oid);
	if (info->tagger_date > 0)
		print_date_row(info);
	if (info->tagger)
		print_tagger_row(info);
	print_object_row(tag->tagged);
	print_download_row(revname, tag->tagged);
	html("</table>\n");
	print_message(info->msg);
	cgit_print_layout_end();
	cgit_free_taginfo(info);
}

static void print_lightweight_tag(const char *revname, struct object *obj)
{
	cgit_print_layout_start();
	html("<table class='commit-info'>\n");
	print_name_row(revname, NULL);
	print_object_row(obj);
	print_download_row(revname, obj);
	html("</table>\n");
	cgit_print_layout_end();
}

/*
 * A tag pointing at another tag links the inner one by object id, since no ref
 * names it.
 */
static int tag_by_id(const char *name, struct object_id *oid)
{
	return !get_oid_hex(name, oid) &&
		odb_read_object_info(the_repository->objects, oid, NULL) == OBJ_TAG;
}

void cgit_print_tag(char *revname)
{
	struct strbuf fullref = STRBUF_INIT;
	struct object_id oid;
	struct object *obj;

	if (!revname)
		revname = ctx.qry.head;

	strbuf_addf(&fullref, "refs/tags/%s", revname);
	if (repo_get_oid(the_repository, fullref.buf, &oid) && !tag_by_id(revname, &oid)) {
		cgit_print_error_page(404, "Not Found", "Bad tag reference: %s", revname);
		goto cleanup;
	}
	obj = parse_object(the_repository, &oid);
	if (!obj) {
		cgit_print_error_page(500, "Internal Server Error", "Unable to read object %s",
			oid_to_hex(&oid));
		goto cleanup;
	}
	if (obj->type == OBJ_TAG)
		print_annotated_tag(revname, &oid);
	else
		print_lightweight_tag(revname, obj);

cleanup:
	strbuf_release(&fullref);
}