/*
* 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("");
html_txt(start);
html("\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("\n");
html("");
html_txt(info->subject);
html("\n");
html("");
html_txt(feed_date(info->committer_date));
html("\n");
html("\n");
if (info->author) {
html("");
html_txt(info->author);
html("\n");
}
if (info->author_email && !ctx.cfg.noplainemail)
print_email(info->author_email);
html("\n");
html("");
html_txt(feed_date(info->author_date));
html("\n");
if (host) {
char *pageurl;
char delim = '&';
html("\n");
free(pageurl);
}
html("");
html_txtf("urn:%s:%s", the_hash_algo->name, hex);
html("\n");
html("\n");
html_txt(info->msg);
html("\n");
html("\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("\n");
html("");
html_txt(ctx.repo->name);
if (path) {
html("/");
html_txt(path);
}
if (tip && !ctx.qry.show_all) {
html(", branch ");
html_txt(tip);
}
html("\n");
html("");
html_txt(ctx.repo->desc);
html("\n");
if (host) {
char *fullurl = cgit_currentfullurl();
char *repourl = cgit_repourl(ctx.repo->url);
html("");
html_txtf("%s%s%s", cgit_httpscheme(), host, fullurl);
html("\n");
html("\n");
html("\n");
free(fullurl);
free(repourl);
}
while ((commit = get_revision(&rev)) != NULL) {
if (need_updated) {
html("");
html_txt(feed_date(commit->date));
html("\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("");
html_txt(feed_date(time(NULL)));
html("\n");
}
html("\n");
free(host);
}