"GOT", but the "O" is a cute, smiling pufferfish. Index | Thread | Search

From:
Omar Polo <op@omarpolo.com>
Subject:
got-notify-http: use a UNIX timestamp for the date
To:
gameoftrees@openbsd.org
Date:
Tue, 23 Apr 2024 08:55:51 +0200

Download raw body.

Thread
This was suggested by Lucas some time ago.  I think it makes more sense
and is less error-prone to use a UNIX timestamp rather than a formatted
string for the date (javascript Date.toJSON() notwithstanding.)

The upside is that got-notify-email has to do some light parsing of the
content of the mail to pretty-print the timestamp.

There is also the assumption that a time_t is at least 64 bit wide, and
I don't know how to portably work around it.  strptime(%s) has timezone
issues and I don't know other ways to turn a stringified timestamp into
a time_t.  Suggestions are welcome!


diff /home/op/w/got
commit - 533c404ec30ef30690b8c41481cbdbbeeb8e2a5c
path + /home/op/w/got
blob - a346ab5981dac6f616103dfdb281f5c2785c60a2
file + gotd/gotd.conf.5
--- gotd/gotd.conf.5
+++ gotd/gotd.conf.5
@@ -402,14 +402,7 @@ Has the same fields as the
 .Dv committer
 but may be unset.
 .It Dv date
-String representation of the date as
-.Xr strftime 3
-.Sq %G-%m-%d
-if
-.Dv short
-is set or
-.Sq %a %b %e %X %Y UTC
-otherwise.
+Number, representing the number of seconds since the Epoch.
 .It Dv short_message
 The first line of the commit message.
 This field is always set.
@@ -474,7 +467,7 @@ field for the
 .Dv commit
 notification but with all the field guaranteed to be set.
 .It Dv date
-The tag date.
+Number, representing the number of seconds since the Epoch.
 .It Dv object
 The object being tagged.
 It contains the fields
blob - a0ff1393c1aa0d3ff4f44bd88d6ef8579db72717
file + gotd/libexec/got-notify-email/got-notify-email.c
--- gotd/libexec/got-notify-email/got-notify-email.c
+++ gotd/libexec/got-notify-email/got-notify-email.c
@@ -17,12 +17,14 @@
 #include <sys/types.h>
 #include <sys/socket.h>
 
+#include <ctype.h>
 #include <errno.h>
 #include <poll.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 #include <stdarg.h>
+#include <stdint.h>
 #include <syslog.h>
 #include <getopt.h>
 #include <err.h>
@@ -213,6 +215,39 @@ get_datestr(time_t *time, char *datebuf)
 	return s;
 }
 
+static const struct got_error *
+print_date(int s, char *date, int shortfmt)
+{
+	const struct got_error	*error;
+	struct tm		 tm;
+	char			*t, datebuf[26];
+	const char		*errstr;
+	time_t			 ts;
+
+	date[strcspn(date, " \n")] = '\0';
+
+	ts = strtonum(date, INT64_MIN, INT64_MAX, &errstr);
+	if (errstr)
+		return got_error_set_errno(EINVAL, errstr);
+	if (gmtime_r(&ts, &tm) == NULL)
+		return got_error_set_errno(EINVAL, "gmtime_r");
+
+	if (!shortfmt) {
+		t = asctime_r(&tm, datebuf);
+		if (t == NULL)
+			return got_error_set_errno(EINVAL, "invalid timestamp");
+		t[strcspn(t, "\n")] = '\0';
+		error = got_poll_write_full(s, t, strlen(t));
+		if (error)
+			return error;
+		return got_poll_write_full(s, " UTC\n", 5);
+	}
+
+	if (strftime(datebuf, sizeof(datebuf), "%G-%m-%d ", &tm) == 0)
+		return got_error_set_errno(EINVAL, "invalid timestamp");
+	return got_poll_write_full(s, datebuf, strlen(datebuf));
+}
+
 static void
 send_email(int s, const char *myfromaddr, const char *fromaddr,
     const char *recipient, const char *replytoaddr,
@@ -223,6 +258,7 @@ send_email(int s, const char *myfromaddr, const char *
 	size_t linesize = 0;
 	ssize_t linelen;
 	time_t now;
+	int firstline = 1, shortfmt = 0;
 	char datebuf[26];
 	char *datestr;
 
@@ -270,11 +306,39 @@ send_email(int s, const char *myfromaddr, const char *
 		fatalx("could not send body delimiter");
 
 	while ((linelen = getline(&line, &linesize, stdin)) != -1) {
+		if (firstline && isdigit((unsigned char)line[0]))
+			shortfmt = 1;
+		firstline = 0;
+
 		if (line[0] == '.') { /* dot stuffing */
 			error = got_poll_write_full(s, ".", 1);
 			if (error)
 				fatalx("write: %s", error->msg);
 		}
+
+		if (shortfmt) {
+			char *t;
+			t = strchr(line, ' ');
+			if (t != NULL) {
+				*t++ = '\0';
+				error = print_date(s, line, shortfmt);
+				if (error)
+					fatalx("write: %s", error->msg);
+				error = got_poll_write_full(s, t, strlen(t));
+				continue;
+			}
+		}
+
+		if (!shortfmt && !strncmp(line, "date: ", 6)) {
+			error = got_poll_write_full(s, line, 6);
+			if (error)
+				fatalx("write: %s", error->msg);
+			error = print_date(s, line + 6, shortfmt);
+			if (error)
+				fatalx("write: %s", error->msg);
+			continue;
+		}
+
 		error = got_poll_write_full(s, line, linelen);
 		if (error)
 			fatalx("write: %s", error->msg);
blob - 60f89ca0e57362d2951dc69102b742825ac30059
file + gotd/libexec/got-notify-http/got-notify-http.c
--- gotd/libexec/got-notify-http/got-notify-http.c
+++ gotd/libexec/got-notify-http/got-notify-http.c
@@ -29,6 +29,7 @@
 #include <stdlib.h>
 #include <string.h>
 #include <syslog.h>
+#include <time.h>
 #include <unistd.h>
 
 #include "got_opentemp.h"
@@ -153,6 +154,12 @@ json_field(FILE *fp, const char *key, const char *val,
 }
 
 static void
+json_date(FILE *fp, const char *key, const char *date, int comma)
+{
+	fprintf(fp, "\"%s\":%s%s", key, date, comma ? "," : "");
+}
+
+static void
 json_author(FILE *fp, const char *type, char *address, int comma)
 {
 	char	*gt, *lt, *at, *email, *endname;
@@ -247,7 +254,7 @@ jsonify_commit_short(FILE *fp, char *line, const char 
 	json_field(fp, "repo", repo, 1);
 	json_field(fp, "id", id, 1);
 	json_author(fp, "committer", author, 1);
-	json_field(fp, "date", date, 1);
+	json_date(fp, "date", date, 1);
 	json_field(fp, "short_message", message, 0);
 	fprintf(fp, "}");
 
@@ -331,7 +338,7 @@ jsonify_commit(FILE *fp, const char *repo, char **line
 			/* optional */
 			if (!strncmp(l, "date: ", 6)) {
 				l += 6;
-				json_field(fp, "date", l, 1);
+				json_date(fp, "date", l, 1);
 				phase = P_PARENT;
 				break;
 			}
@@ -598,7 +605,7 @@ jsonify_tag(FILE *fp, const char *repo, char **line, s
 			/* optional */
 			if (!strncmp(l, "date: ", 6)) {
 				l += 6;
-				json_field(fp, "date", l, 1);
+				json_date(fp, "date", l, 1);
 				phase = P_OBJECT;
 				break;
 			}
blob - abc10999b714c055c44b01f7d380e97962a222f8
file + gotd/notify.c
--- gotd/notify.c
+++ gotd/notify.c
@@ -242,7 +242,7 @@ notify_email(struct gotd_notification_target *target, 
 		argv[i++] = "-h";
 		argv[i++] = target->conf.email.hostname;
 	}
-	
+
 	if (target->conf.email.port) {
 		argv[i++] = "-p";
 		argv[i++] = target->conf.email.port;
blob - 787504ad4f6f0817c648d3f24b5d75d65da66010
file + gotd/repo_write.c
--- gotd/repo_write.c
+++ gotd/repo_write.c
@@ -1629,24 +1629,6 @@ receive_pack_idx(struct imsg *imsg, struct gotd_imsgev
 	return NULL;
 }
 
-static char *
-get_datestr(time_t *time, char *datebuf)
-{
-	struct tm mytm, *tm;
-	char *p, *s;
-
-	tm = gmtime_r(time, &mytm);
-	if (tm == NULL)
-		return NULL;
-	s = asctime_r(tm, datebuf);
-	if (s == NULL)
-		return NULL;
-	p = strchr(s, '\n');
-	if (p)
-		*p = '\0';
-	return s;
-}
-
 static const struct got_error *
 notify_removed_ref(const char *refname, uint8_t *sha1,
     struct gotd_imsgev *iev, int fd)
@@ -1688,8 +1670,6 @@ print_commit_oneline(struct got_commit_object *commit,
 	char *id_str = NULL, *logmsg0 = NULL;
 	char *s, *nl;
 	char *committer = NULL, *author = NULL;
-	char datebuf[12]; /* YYYY-MM-DD + SPACE + NUL */
-	struct tm tm;
 	time_t committer_time;
 
 	err = got_object_id_str(&id_str, id);
@@ -1697,14 +1677,6 @@ print_commit_oneline(struct got_commit_object *commit,
 		return err;
 
 	committer_time = got_object_commit_get_committer_time(commit);
-	if (gmtime_r(&committer_time, &tm) == NULL) {
-		err = got_error_from_errno("gmtime_r");
-		goto done;
-	}
-	if (strftime(datebuf, sizeof(datebuf), "%G-%m-%d ", &tm) == 0) {
-		err = got_error(GOT_ERR_NO_SPACE);
-		goto done;
-	}
 
 	err = got_object_commit_get_logmsg(&logmsg0, commit);
 	if (err)
@@ -1726,16 +1698,12 @@ print_commit_oneline(struct got_commit_object *commit,
 			err = got_error_from_errno("strdup");
 			goto done;
 		}
-		dprintf(fd, "%s%.7s %.8s %s\n", datebuf, id_str,
-		    format_author(author), s);
+		dprintf(fd, "%lld %.7s %.8s %s\n", (long long)committer_time,
+		    id_str, format_author(author), s);
 	} else {
 		committer = strdup(got_object_commit_get_committer(commit));
-		if (committer == NULL) {
-			err = got_error_from_errno("strdup");
-			goto done;
-		}
-		dprintf(fd, "%s%.7s %.8s %s\n", datebuf, id_str,
-		    format_author(committer), s);
+		dprintf(fd, "%lld %.7s %.8s %s\n", (long long)committer_time,
+		    id_str, format_author(committer), s);
 	}
 
 	if (fsync(fd) == -1 && err == NULL)
@@ -1775,8 +1743,7 @@ print_commit(struct got_commit_object *commit, struct 
     struct got_diffstat_cb_arg *diffstat, int fd)
 {
 	const struct got_error *err = NULL;
-	char *id_str, *datestr, *logmsg0, *logmsg, *line;
-	char datebuf[26];
+	char *id_str, *logmsg0, *logmsg, *line;
 	time_t committer_time;
 	const char *author, *committer;
 
@@ -1793,9 +1760,7 @@ print_commit(struct got_commit_object *commit, struct 
 	if (strcmp(author, committer) != 0)
 		dprintf(fd, "via: %s\n", committer);
 	committer_time = got_object_commit_get_committer_time(commit);
-	datestr = get_datestr(&committer_time, datebuf);
-	if (datestr)
-		dprintf(fd, "date: %s UTC\n", datestr);
+	dprintf(fd, "date: %lld\n", (long long)committer_time);
 	if (got_object_commit_get_nparents(commit) > 1) {
 		const struct got_object_id_queue *parent_ids;
 		struct got_object_qid *qid;
@@ -2004,8 +1969,7 @@ print_tag(struct got_object_id *id,
 	const struct got_error *err = NULL;
 	struct got_tag_object *tag = NULL;
 	const char *tagger = NULL;
-	char *id_str = NULL, *tagmsg0 = NULL, *tagmsg, *line, *datestr;
-	char datebuf[26];
+	char *id_str = NULL, *tagmsg0 = NULL, *tagmsg, *line;
 	time_t tagger_time;
 
 	err = got_object_open_as_tag(&tag, repo, id);
@@ -2021,9 +1985,7 @@ print_tag(struct got_object_id *id,
 
 	dprintf(fd, "tag %s\n", refname);
 	dprintf(fd, "from: %s\n", tagger);
-	datestr = get_datestr(&tagger_time, datebuf);
-	if (datestr)
-		dprintf(fd, "date: %s UTC\n", datestr);
+	dprintf(fd, "date: %lld\n", (long long)tagger_time);
 
 	switch (got_object_tag_get_object_type(tag)) {
 	case GOT_OBJ_TYPE_BLOB:
blob - 18726da0df985a0e71d65fb2408032c6677ffe00
file + regress/gotd/http_notification.sh
--- regress/gotd/http_notification.sh
+++ regress/gotd/http_notification.sh
@@ -57,8 +57,6 @@ test_file_changed() {
 
 	wait %1 # wait for the http "server"
 
-	d=`date -u -r $author_time +"%a %b %e %X %Y UTC"`
-
 	touch "$testroot/stdout.expected"
 	ed -s "$testroot/stdout.expected" <<-EOF
 	a
@@ -79,7 +77,7 @@ test_file_changed() {
 			"mail":"$GIT_AUTHOR_EMAIL",
 			"user":"$GOT_AUTHOR_11"
 		},
-		"date":"$d",
+		"date":$author_time,
 		"short_message":"make changes",
 		"message":"make changes\n",
 		"diffstat":{
@@ -150,8 +148,6 @@ test_bad_utf8() {
 
 	wait %1 # wait for the http "server"
 
-	d=`date -u -r $author_time +"%a %b %e %X %Y UTC"`
-
 	touch "$testroot/stdout.expected"
 	ed -s "$testroot/stdout.expected" <<-EOF
 	a
@@ -172,7 +168,7 @@ test_bad_utf8() {
 			"mail":"$GIT_AUTHOR_EMAIL",
 			"user":"$GOT_AUTHOR_11"
 		},
-		"date":"$d",
+		"date":$author_time,
 		"short_message":"make\uFFFD\uFFFDchanges",
 		"message":"make\uFFFD\uFFFDchanges\n",
 		"diffstat":{
@@ -228,8 +224,7 @@ test_many_commits_not_summarized() {
 		(cd $testroot/wt && got commit -m 'make changes' > /dev/null)
 		local commit_id=`git_show_head $testroot/repo-clone`
 		local author_time=`git_show_author_time $testroot/repo-clone`
-		d=`date -u -r $author_time +"%a %b %e %X %Y UTC"`
-		set -- "$@" "$commit_id $d"
+		set -- "$@" "$commit_id $author_time"
 	done
 
 	timeout 5 ./http-server -a $AUTH -p "$GOTD_TEST_HTTP_PORT" \
@@ -273,7 +268,7 @@ test_many_commits_not_summarized() {
 				"mail":"$GIT_AUTHOR_EMAIL",
 				"user":"$GOT_AUTHOR_11"
 			},
-			"date":"$commit_time",
+			"date":$commit_time,
 			"short_message":"make changes",
 			"message":"make changes\n",
 			"diffstat":{
@@ -333,8 +328,7 @@ test_many_commits_summarized() {
 		local commit_id=`git_show_head $testroot/repo-clone`
 		local short_commit_id=`trim_obj_id 33 $commit_id`
 		local author_time=`git_show_author_time $testroot/repo-clone`
-		d=`date -u -r $author_time +"%G-%m-%d"`
-		set -- "$@" "$short_commit_id $d"
+		set -- "$@" "$short_commit_id $author_time"
 	done
 
 	timeout 5 ./http-server -a $AUTH -p "$GOTD_TEST_HTTP_PORT" \
@@ -355,7 +349,7 @@ test_many_commits_summarized() {
 	for i in `seq 1 51`; do
 		s=`pop_idx $i "$@"`
 		commit_id=$(echo $s | cut -d' ' -f1)
-		commit_time=$(echo "$s" | sed -e "s/^$commit_id //g")
+		commit_time=$(echo "$s" | cut -d' ' -f2)
 
 		echo "$comma"
 		comma=','
@@ -369,7 +363,7 @@ test_many_commits_summarized() {
 			"committer":{
 				"user":"$GOT_AUTHOR_8"
 			},
-			"date":"$commit_time",
+			"date":$commit_time,
 			"short_message":"make changes"
 		}
 		EOF
@@ -430,8 +424,6 @@ test_branch_created() {
 
 	wait %1 # wait for the http "server"
 
-	d=`date -u -r $author_time +"%a %b %e %X %Y UTC"`
-
 	# in the future it should contain something like this too
 	# {
 	# 	"type":"new-branch",
@@ -460,7 +452,7 @@ test_branch_created() {
 			"mail":"$GIT_AUTHOR_EMAIL",
 			"user":"$GOT_AUTHOR_11"
 		},
-		"date":"$d",
+		"date":$author_time,
 		"short_message":"newbranch",
 		"message":"newbranch\n",
 		"diffstat":{
@@ -572,8 +564,6 @@ test_tag_created() {
 
 	wait %1 # wait for the http "server"
 
-	d=`date -u -r $tagger_time +"%a %b %e %X %Y UTC"`
-
 	touch "$testroot/stdout.expected"
 	ed -s "$testroot/stdout.expected" <<-EOF
 	a
@@ -587,7 +577,7 @@ test_tag_created() {
 			"mail":"$GIT_AUTHOR_EMAIL",
 			"user":"$GOT_AUTHOR_11"
 		},
-		"date":"$d",
+		"date":$tagger_time,
 		"object":{
 			"type":"commit",
 			"id":"$commit_id"
@@ -650,8 +640,6 @@ test_tag_changed() {
 
 	wait %1 # wait for the http "server"
 
-	d=`date -u -r $tagger_time +"%a %b %e %X %Y UTC"`
-
 	# XXX: at the moment this is exactly the same as the "new tag"
 	# notification
 
@@ -668,7 +656,7 @@ test_tag_changed() {
 			"mail":"$GIT_AUTHOR_EMAIL",
 			"user":"$GOT_AUTHOR_11"
 		},
-		"date":"$d",
+		"date":$tagger_time,
 		"object":{
 			"type":"commit",
 			"id":"$commit_id"