blob: aa9237a42c4002f8cc1815a75c66e60d930c8e32 (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
/*
 * The Atom feed for a repository, which lists recent commits so a reader can
 * follow the project from a feed reader instead of the browsable pages. The
 * response is XML rather than a page, so this file writes its own HTTP headers
 * and never goes through the shared HTML layout.
 */

#define USE_THE_REPOSITORY_VARIABLE

#include "cgit.h"
#include "html.h"
#include "parsing.h"
#include "shared.h"
#include "ui-atom.h"
#include "ui-shared.h"

/*
 * Atom timestamps have to be RFC 3339, so a feed ignores the date-format and
 * local-time settings the browsable pages honour. The zero is the timezone
 * offset, which pins every feed date to UTC.
 */
static const char *feed_date(timestamp_t when)
{
	return show_date(when, 0, date_mode_from_type(DATE_ISO8601_STRICT));
}

/*
 * cgit keeps an address with the angle brackets it was written with, while
 * Atom wants the bare address.
 */
static void print_email(const char *email)
{
	char *copy = xstrdup(email);
	char *start, *end;

	start = strchr(copy, '<');
	if (start)
		start++;
	else
		start = copy;
	end = strchr(start, '>');
	if (end)
		*end = '\0';

	html("<email>");
	html_txt(start);
	html("</email>\n");
	free(copy);
}

static void print_entry(struct commit *commit, const char *host)
{
	struct commitinfo *info;
	char *hex;

	info = cgit_parse_commit(commit);
	hex = oid_to_hex(&commit->object.oid);
	html("<entry>\n");
	html("<title>");
	html_txt(info->subject);
	html("</title>\n");
	html("<updated>");
	html_txt(feed_date(info->committer_date));
	html("</updated>\n");
	html("<author>\n");
	if (info->author) {
		html("<name>");
		html_txt(info->author);
		html("</name>\n");
	}
	if (info->author_email && !ctx.cfg.noplainemail)
		print_email(info->author_email);
	html("</author>\n");
	html("<published>");
	html_txt(feed_date(info->author_date));
	html("</published>\n");
	if (host) {
		char *pageurl;
		char delim = '&';

		html("<link rel='alternate' type='text/html' href='");
		html(cgit_httpscheme());
		html_attr(host);
		pageurl = cgit_pageurl(ctx.repo->url, "commit", NULL);
		html_attr(pageurl);
		// Without a virtual root the page url is already a query
		// string, so the commit id continues it instead of opening
		// one.
		if (ctx.cfg.virtual_root)
			delim = '?';
		html_attrf("%cid=%s", delim, hex);
		html("'/>\n");
		free(pageurl);
	}
	html("<id>");
	html_txtf("urn:%s:%s", the_hash_algo->name, hex);
	html("</id>\n");
	html("<content type='text'>\n");
	html_txt(info->msg);
	html("</content>\n");
	html("</entry>\n");
	cgit_free_commitinfo(info);
}

void cgit_print_atom(char *tip, const char *path, int max_count)
{
	char *host;
	// setup_revisions reads a command line, so the first slot is the
	// unused program name and parsing starts at the second.
	const char *argv[] = {NULL, tip, NULL, NULL, NULL};
	struct commit *commit;
	struct rev_info rev;
	int argc = 2;
	bool need_updated = true;

	if (ctx.qry.show_all)
		argv[1] = "--all";
	else if (!tip)
		argv[1] = ctx.qry.head;

	if (path) {
		argv[argc++] = "--";
		argv[argc++] = path;
	}

	repo_init_revisions(the_repository, &rev, NULL);
	rev.abbrev = DEFAULT_ABBREV;
	rev.commit_format = CMIT_FMT_DEFAULT;
	rev.verbose_header = 1;
	rev.show_root_diff = 0;
	rev.max_count = max_count;
	setup_revisions(argc, argv, &rev, NULL);
	prepare_revision_walk(&rev);

	host = cgit_hosturl();
	ctx.page.mimetype = "text/xml";
	ctx.page.charset = "utf-8";
	cgit_print_http_headers();
	html("<feed xmlns='http://www.w3.org/2005/Atom'>\n");
	html("<title>");
	html_txt(ctx.repo->name);
	if (path) {
		html("/");
		html_txt(path);
	}
	if (tip && !ctx.qry.show_all) {
		html(", branch ");
		html_txt(tip);
	}
	html("</title>\n");
	html("<subtitle>");
	html_txt(ctx.repo->desc);
	html("</subtitle>\n");
	if (host) {
		char *fullurl = cgit_currentfullurl();
		char *repourl = cgit_repourl(ctx.repo->url);
		html("<id>");
		html_txtf("%s%s%s", cgit_httpscheme(), host, fullurl);
		html("</id>\n");
		html("<link rel='self' href='");
		html_attrf("%s%s%s", cgit_httpscheme(), host, fullurl);
		html("'/>\n");
		html("<link rel='alternate' type='text/html' href='");
		html_attrf("%s%s%s", cgit_httpscheme(), host, repourl);
		html("'/>\n");
		free(fullurl);
		free(repourl);
	}
	while ((commit = get_revision(&rev)) != NULL) {
		if (need_updated) {
			html("<updated>");
			html_txt(feed_date(commit->date));
			html("</updated>\n");
			need_updated = false;
		}
		print_entry(commit, host);
		// release_commit_memory frees the parent list without clearing
		// the pointer to it, so drop the dangling reference here.
		release_commit_memory(the_repository->parsed_objects, commit);
		commit->parents = NULL;
	}
	if (need_updated) {
		// Atom makes a feed level updated mandatory, and an empty feed
		// has no commit to take one from.
		html("<updated>");
		html_txt(feed_date(time(NULL)));
		html("</updated>\n");
	}
	html("</feed>\n");
	free(host);
}