Commit Diff


commit - 389a68d86df4adffefba4050e86cd6a8fe6de36d
commit + 298f95fb39537cc43237be56aeb86ffbc21e38f0
blob - d7d581b830dd549f2e3d19757290d8539f83618d
blob + c83793f9a486da3fff129c76d2b915edd05b7c40
--- gotwebd/files/htdocs/gotwebd/gotweb.css
+++ gotwebd/files/htdocs/gotwebd/gotweb.css
@@ -527,36 +527,36 @@ body {
 	white-space: pre-wrap;
 }
 
-#blame_title_wrapper {
+#blame_title_wrapper, #blob_title_wrapper {
 	clear: left;
 	float: left;
 	width: 100%;
 	background-color: LightSlateGray;
 	color: #ffffff;
 }
-#blame_title {
+#blame_title, #blob_title_wrapper {
 	padding-left: 10px;
 	padding-top: 5px;
 	padding-bottom: 5px;
 }
-#blame_content {
+#blame_content, #blob_content {
 	clear: left;
 	float: left;
 	width: 100%;
 }
-#blame_header_wrapper {
+#blame_header_wrapper, #blob_header_wrapper {
 	float: left;
 	background-color: #f5fcfb;
 	width: 100%;
 }
-#blame_header {
+#blame_header, #blob_header {
 	float: left;
 	padding-left: 10px;
 	padding-top: 5px;
 	padding-bottom: 2px;
 	width: 80%;
 }
-#blame {
+#blame, #blob {
 	clear: left;
 	float: left;
 	margin-left: 20px;
@@ -565,12 +565,15 @@ body {
 	white-space: pre;
 	overflow: auto;
 }
-.blame_wrapper {
+.blame_wrapper, .blob_line {
 	clear: left;
 	float: left;
 	width: 100%;
 }
-.blame_number {
+.blame_wrapper:target, .blob_line:target {
+	background-color: Khaki;
+}
+.blame_number, .blob_number {
 	float: left;
 	width: 6em;
 	overflow: hidden;
@@ -590,7 +593,7 @@ body {
 	width: 6em;
 	overflow: hidden;
 }
-.blame_code {
+.blame_code, .blob_code {
 	float:left;
 	width: 50%;
 	overflow: visible;
blob - d4196649f19af83bb7448f6007b5db724a9b3866
blob + 9d187ce1e16a66d113377842fd55ccbd016fd2e3
--- gotwebd/got_operations.c
+++ gotwebd/got_operations.c
@@ -960,7 +960,8 @@ done:
 }
 
 const struct got_error *
-got_output_file_blob(struct request *c)
+got_open_blob_for_output(struct got_blob_object **blob, int *fd,
+    int *binary, struct request *c)
 {
 	const struct got_error *error = NULL;
 	struct transport *t = c->t;
@@ -969,14 +970,15 @@ got_output_file_blob(struct request *c)
 	struct got_commit_object *commit = NULL;
 	struct got_object_id *commit_id = NULL;
 	struct got_reflist_head refs;
-	struct got_blob_object *blob = NULL;
 	char *path = NULL, *in_repo_path = NULL;
-	int bin, obj_type, fd = -1;
-	size_t len;
-	const uint8_t *buf;
+	int obj_type;
 
 	TAILQ_INIT(&refs);
 
+	*blob = NULL;
+	*fd = -1;
+	*binary = 0;
+
 	if (asprintf(&path, "%s%s%s", qs->folder ? qs->folder : "",
 	    qs->folder ? "/" : "", qs->file) == -1) {
 		error = got_error_from_errno("asprintf");
@@ -1014,19 +1016,52 @@ got_output_file_blob(struct request *c)
 		goto done;
 	}
 
-	error = got_gotweb_dupfd(&c->priv_fd[BLOB_FD_1], &fd);
+	error = got_gotweb_dupfd(&c->priv_fd[BLOB_FD_1], fd);
 	if (error)
 		goto done;
 
-	error = got_object_open_as_blob(&blob, repo, commit_id, BUF, fd);
+	error = got_object_open_as_blob(blob, repo, commit_id, BUF, *fd);
 	if (error)
 		goto done;
 
-	error = got_object_blob_is_binary(&bin, blob);
+	error = got_object_blob_is_binary(binary, *blob);
 	if (error)
 		goto done;
 
-	if (bin)
+ done:
+	if (commit)
+		got_object_commit_close(commit);
+
+	if (error) {
+		if (*fd != -1)
+			close(*fd);
+		if (*blob)
+			got_object_blob_close(*blob);
+		*fd = -1;
+		*blob = NULL;
+	}
+
+	free(in_repo_path);
+	free(commit_id);
+	free(path);
+	return error;
+}
+
+const struct got_error *
+got_output_file_blob(struct request *c)
+{
+	const struct got_error *error = NULL;
+	struct querystring *qs = c->t->qs;
+	struct got_blob_object *blob = NULL;
+	size_t len;
+	int binary, fd = -1;
+	const uint8_t *buf;
+
+	error = got_open_blob_for_output(&blob, &fd, &binary, c);
+	if (error)
+		return error;
+
+	if (binary)
 		error = gotweb_render_content_type_file(c,
 		    "application/octet-stream", qs->file, NULL);
 	else
@@ -1046,17 +1081,42 @@ got_output_file_blob(struct request *c)
 		buf = got_object_blob_get_read_buf(blob);
 		fcgi_gen_binary_response(c, buf, len);
 	}
-done:
-	if (commit)
-		got_object_commit_close(commit);
-	if (fd != -1 && close(fd) == -1 && error == NULL)
+ done:
+	if (close(fd) == -1 && error == NULL)
 		error = got_error_from_errno("close");
 	if (blob)
 		got_object_blob_close(blob);
-	free(in_repo_path);
-	free(commit_id);
-	free(path);
 	return error;
+}
+
+int
+got_output_blob_by_lines(struct template *tp, struct got_blob_object *blob,
+    int (*cb)(struct template *, const char *, size_t))
+{
+	const struct got_error	*err;
+	char			*line = NULL;
+	size_t			 linesize = 0;
+	size_t			 lineno = 0;
+	ssize_t			 linelen = 0;
+
+	for (;;) {
+		err = got_object_blob_getline(&line, &linelen, &linesize,
+		    blob);
+		if (err || linelen == -1)
+			break;
+		lineno++;
+		if (cb(tp, line, lineno) == -1)
+			break;
+	}
+
+	free(line);
+
+	if (err) {
+		log_warnx("%s: got_object_blob_getline failed: %s",
+		    __func__, err->msg);
+		return -1;
+	}
+	return 0;
 }
 
 struct blame_line {
blob - 39453efb256d54ee495702a9fef915782dec76ef
blob + 37d8624ec792d91e8f7438d5cc3682be3a54d179
--- gotwebd/gotweb.c
+++ gotwebd/gotweb.c
@@ -66,6 +66,7 @@ static const struct querystring_keys querystring_keys[
 static const struct action_keys action_keys[] = {
 	{ "blame",	BLAME },
 	{ "blob",	BLOB },
+	{ "blobraw",	BLOBRAW },
 	{ "briefs",	BRIEFS },
 	{ "commits",	COMMITS },
 	{ "diff",	DIFF },
@@ -109,11 +110,12 @@ void
 gotweb_process_request(struct request *c)
 {
 	const struct got_error *error = NULL, *error2 = NULL;
+	struct got_blob_object *blob = NULL;
 	struct server *srv = NULL;
 	struct querystring *qs = NULL;
 	struct repo_dir *repo_dir = NULL;
 	uint8_t err[] = "gotwebd experienced an error: ";
-	int r, html = 0;
+	int r, html = 0, fd = -1;
 
 	/* init the transport */
 	error = gotweb_init_transport(&c->t);
@@ -150,10 +152,12 @@ gotweb_process_request(struct request *c)
 	 * querystring.
 	 */
 
-	if (qs->commit == NULL && (qs->action == BLAME || qs->action == BLOB ||
-	    qs->action == DIFF)) {
-		error2 = got_error(GOT_ERR_QUERYSTRING);
-		goto render;
+	if (qs->action == BLAME || qs->action == BLOB ||
+	    qs->action == BLOBRAW || qs->action == DIFF) {
+		if (qs->commit == NULL) {
+			error2 = got_error(GOT_ERR_QUERYSTRING);
+			goto render;
+		}
 	}
 
 	if (qs->action != INDEX) {
@@ -166,7 +170,7 @@ gotweb_process_request(struct request *c)
 			goto err;
 	}
 
-	if (qs->action == BLOB) {
+	if (qs->action == BLOBRAW) {
 		error = got_get_repo_commits(c, 1);
 		if (error)
 			goto done;
@@ -176,6 +180,34 @@ gotweb_process_request(struct request *c)
 			goto err;
 		}
 		goto done;
+	}
+
+	if (qs->action == BLOB) {
+		int binary;
+		struct gotweb_url url = {
+			.index_page = -1,
+			.page = -1,
+			.action = BLOBRAW,
+			.path = qs->path,
+			.commit = qs->commit,
+			.folder = qs->folder,
+			.file = qs->file,
+		};
+
+		error = got_get_repo_commits(c, 1);
+		if (error)
+			goto done;
+
+		error2 = got_open_blob_for_output(&blob, &fd, &binary, c);
+		if (error2)
+			goto render;
+		if (binary) {
+			fcgi_puts(c->tp, "Status: 302\r\n");
+			fcgi_puts(c->tp, "Location: ");
+			gotweb_render_url(c, &url);
+			fcgi_puts(c->tp, "\r\n\r\n");
+			goto done;
+		}
 	}
 
 	if (qs->action == RSS) {
@@ -220,6 +252,10 @@ render:
 			log_warnx("%s: %s", __func__, error->msg);
 			goto err;
 		}
+		break;
+	case BLOB:
+		if (gotweb_render_blob(c->tp, blob) == -1)
+			goto err;
 		break;
 	case BRIEFS:
 		if (gotweb_render_briefs(c->tp) == -1)
@@ -301,6 +337,10 @@ err:
 	if (html && fcgi_printf(c, "</div>\n") == -1)
 		return;
 done:
+	if (blob)
+		got_object_blob_close(blob);
+	if (fd != -1)
+		close(fd);
 	if (html && srv != NULL)
 		gotweb_render_footer(c->tp);
 }
@@ -1626,6 +1666,8 @@ gotweb_action_name(int action)
 		return "blame";
 	case BLOB:
 		return "blob";
+	case BLOBRAW:
+		return "blobraw";
 	case BRIEFS:
 		return "briefs";
 	case COMMITS:
blob - f3c092bd9402f47b404b596072606dfc3196fce8
blob + e4e23bf9c198a25c2338660068796cd385a8498c
--- gotwebd/gotwebd.h
+++ gotwebd/gotwebd.h
@@ -115,6 +115,9 @@
 
 #define GOTWEB_PACK_NUM_TEMPFILES     32
 
+/* Forward declaration */
+struct got_blob_object;
+
 enum imsg_type {
 	IMSG_CFG_SRV = IMSG_PROC_MAX,
 	IMSG_CFG_SOCK,
@@ -404,6 +407,7 @@ enum querystring_elements {
 enum query_actions {
 	BLAME,
 	BLOB,
+	BLOBRAW,
 	BRIEFS,
 	COMMITS,
 	DIFF,
@@ -462,6 +466,7 @@ int	gotweb_render_repo_fragment(struct template *, str
 int	gotweb_render_briefs(struct template *);
 int	gotweb_render_navs(struct template *);
 int	gotweb_render_commits(struct template *);
+int	gotweb_render_blob(struct template *, struct got_blob_object *);
 int	gotweb_render_rss(struct template *);
 
 /* parse.y */
@@ -491,7 +496,11 @@ const struct got_error *got_get_repo_tags(struct reque
 const struct got_error *got_get_repo_heads(struct request *);
 const struct got_error *got_output_repo_diff(struct request *);
 const struct got_error *got_output_repo_tree(struct request *);
+const struct got_error *got_open_blob_for_output(struct got_blob_object **,
+    int *, int *, struct request *);
 const struct got_error *got_output_file_blob(struct request *);
+int got_output_blob_by_lines(struct template *, struct got_blob_object *,
+    int (*)(struct template *, const char *, size_t));
 const struct got_error *got_output_file_blame(struct request *);
 
 /* config.c */
blob - 31d9c2b76e13b67d88a33db6d31ba3ce49398422
blob + 5dac668e8a6c3b5de586bc8484746a6397e29dfb
--- gotwebd/pages.tmpl
+++ gotwebd/pages.tmpl
@@ -31,6 +31,8 @@
 #include "gotwebd.h"
 #include "tmpl.h"
 
+static int gotweb_render_blob_line(struct template *, const char *, size_t);
+
 static inline int rss_tag_item(struct template *, struct repo_tag *);
 static inline int rss_author(struct template *, char *);
 
@@ -403,9 +405,57 @@ gotweb_render_age(struct template *tp, time_t time, in
   {{ if t->next_id || t->prev_id }}
     {{ render gotweb_render_navs(tp) }}
   {{ end }}
+</div>
+{{ end }}
+
+{{ define gotweb_render_blob(struct template *tp,
+    struct got_blob_object *blob) }}
+{!
+	struct request		*c = tp->tp_arg;
+	struct transport	*t = c->t;
+	struct repo_commit	*rc = TAILQ_FIRST(&t->repo_commits);
+!}
+<div id="blob_title_wrapper">
+  <div id="blob_title">Blob</div>
+</div>
+<div id="blob_content">
+  <div id="blob_header_wrapper">
+    <div id="blob_header">
+      <div class="header_age_title">Date:</div>
+      <div class="header_age">
+        {{ render gotweb_render_age(tp, rc->committer_time, TM_LONG) }}
+      </div>
+      <div id="header_commit_msg_title">Message:</div>
+      <div id="header_commit_msg">{{ rc->commit_msg }}</div>
+    </div>
+  </div>
+  <div class="dotted_line"></div>
+  <div id="blob">
+    <pre>
+      {{ render got_output_blob_by_lines(tp, blob, gotweb_render_blob_line) }}
+    </pre>
+  </div>
 </div>
 {{ end }}
 
+{{ define gotweb_render_blob_line(struct template *tp, const char *line,
+    size_t no) }}
+{!
+	char		 lineno[16];
+	int		 r;
+
+	r = snprintf(lineno, sizeof(lineno), "%zu", no);
+	if (r < 0 || (size_t)r >= sizeof(lineno))
+		return -1;
+!}
+<div class="blob_line" id="line{{ lineno }}">
+  <div class="blob_number">
+    <a href="#line{{ lineno }}">{{ lineno }}</a>
+  </div>
+  <div class="blob_code">{{ line }}</div>
+</div>
+{{ end }}
+
 {{ define gotweb_render_rss(struct template *tp) }}
 {!
 	struct request		*c = tp->tp_arg;