Commit Diff


commit - c945ec50da8b849eeb8e82b3836fc106bc041419
commit + f4a881ce8b200e5283247799c39abd2b578b8e75
blob - 6ba2563737d96877e4e57392d5dd8bef4a272705
blob + c24e7e0fd473d1327a88bc5c25fd0ed0963c815e
--- include/got_object.h
+++ include/got_object.h
@@ -18,6 +18,7 @@ struct got_object_id;
 
 struct got_blob_object;
 struct got_tree_object;
+struct got_tag_object;
 
 struct got_tree_entry {
 	SIMPLEQ_ENTRY(got_tree_entry) entry;
@@ -224,6 +225,17 @@ const struct got_error *got_object_blob_read_block(siz
 const struct got_error *got_object_blob_dump_to_file(size_t *, size_t *,
     FILE *, struct got_blob_object *);
 
+/*
+ * Attempt to open a tag object in a repository.
+ * The provided object must be of type GOT_OBJ_TYPE_TAG.
+ * The caller must dispose of the tree with got_object_tag_close().
+ */
+const struct got_error *got_object_tag_open(struct got_tag_object **,
+    struct got_repository *, struct got_object *);
+
+/* Dispose of a tag object. */
+void got_object_tag_close(struct got_tag_object *);
+
 const struct got_error *
 got_object_open_as_commit(struct got_commit_object **,
     struct got_repository *, struct got_object_id *);
@@ -233,6 +245,8 @@ got_object_open_as_tree(struct got_tree_object **,
 const struct got_error *
 got_object_open_as_blob(struct got_blob_object **,
     struct got_repository *, struct got_object_id *, size_t);
+const struct got_error *got_object_open_as_tag(struct got_tag_object **,
+    struct got_repository *, struct got_object_id *);
 
 const struct got_error *got_object_commit_add_parent(struct got_commit_object *,
     const char *);
blob - fc8b2eef010a4a07760e360d7e9102d16791ddbd
blob + c2619193e47de7f03830953b4c47623aafbbd7fb
--- lib/got_lib_object.h
+++ lib/got_lib_object.h
@@ -48,3 +48,14 @@ struct got_blob_object {
 	uint8_t *read_buf;
 	struct got_object_id id;
 };
+
+struct got_tag_object {
+	struct got_object_id id;
+	int obj_type;
+	char *tag;
+	time_t tagger_time;
+	time_t tagger_gmtoff;
+	char *tagger;
+	char *tagmsg;
+	int refcnt;		/* > 0 if open and/or cached */
+};
blob - 067f6f0703d20cfe605969655056f62b9b24771b
blob + a10ff39474235e35fee9cc7454d03acf7f84bb72
--- lib/got_lib_object_cache.h
+++ lib/got_lib_object_cache.h
@@ -18,6 +18,7 @@ enum got_object_cache_type {
 	GOT_OBJECT_CACHE_TYPE_OBJ,
 	GOT_OBJECT_CACHE_TYPE_TREE,
 	GOT_OBJECT_CACHE_TYPE_COMMIT,
+	GOT_OBJECT_CACHE_TYPE_TAG,
 };
 
 struct got_object_cache_entry {
@@ -26,6 +27,7 @@ struct got_object_cache_entry {
 		struct got_object *obj;
 		struct got_tree_object *tree;
 		struct got_commit_object *commit;
+		struct got_tag_object *tag;
 	} data;
 };
 
blob - f53705294442da0455ee9f988a17bdd3966aaf58
blob + dcc76200ee994833fb286f365a9b936071eb118e
--- lib/got_lib_object_parse.h
+++ lib/got_lib_object_parse.h
@@ -26,11 +26,15 @@ const struct got_error *got_object_read_commit_privsep
     struct got_repository *);
 const struct got_error *got_object_read_tree_privsep(struct got_tree_object **,
     struct got_object *, int, struct got_repository *);
+const struct got_error *got_object_read_tag_privsep(struct got_tag_object **,
+    struct got_object *, int, struct got_repository *);
 
 const struct got_error *got_object_parse_commit(struct got_commit_object **,
     char *, size_t);
 const struct got_error *got_object_parse_tree(struct got_tree_object **,
     uint8_t *, size_t);
+const struct got_error *got_object_parse_tag(struct got_tag_object **,
+    uint8_t *, size_t);
 const struct got_error *got_read_file_to_mem(uint8_t **, size_t *, FILE *);
 
 void got_object_tree_entry_close(struct got_tree_entry *);
@@ -45,3 +49,5 @@ const struct got_error *got_object_read_packed_commit_
     struct got_commit_object **, struct got_object *, struct got_pack *);
 const struct got_error *got_object_read_packed_tree_privsep(
     struct got_tree_object **, struct got_object *, struct got_pack *);
+const struct got_error *got_object_read_packed_tag_privsep(
+    struct got_tag_object **, struct got_object *, struct got_pack *);
blob - a396e2c12db9ff8b8e8881e7c68a292bbab832e0
blob + 871454f5b88c501bfe8560503f16b0d734fafba9
--- lib/got_lib_privsep.h
+++ lib/got_lib_privsep.h
@@ -39,6 +39,7 @@
 #define GOT_PROG_READ_TREE	got-read-tree
 #define GOT_PROG_READ_COMMIT	got-read-commit
 #define GOT_PROG_READ_BLOB	got-read-blob
+#define GOT_PROG_READ_TAG	got-read-tag
 #define GOT_PROG_READ_PACK	got-read-pack
 
 #define GOT_STRINGIFY(x) #x
@@ -53,6 +54,8 @@
 	GOT_STRINGVAL(GOT_LIBEXECDIR) "/" GOT_STRINGVAL(GOT_PROG_READ_COMMIT)
 #define GOT_PATH_PROG_READ_BLOB \
 	GOT_STRINGVAL(GOT_LIBEXECDIR) "/" GOT_STRINGVAL(GOT_PROG_READ_BLOB)
+#define GOT_PATH_PROG_READ_TAG \
+	GOT_STRINGVAL(GOT_LIBEXECDIR) "/" GOT_STRINGVAL(GOT_PROG_READ_TAG)
 #define GOT_PATH_PROG_READ_PACK \
 	GOT_STRINGVAL(GOT_LIBEXECDIR) "/" GOT_STRINGVAL(GOT_PROG_READ_PACK)
 
@@ -88,6 +91,9 @@ enum got_imsg_type {
 	GOT_IMSG_BLOB_REQUEST,
 	GOT_IMSG_BLOB_OUTFD,
 	GOT_IMSG_BLOB,
+	GOT_IMSG_TAG_REQUEST,
+	GOT_IMSG_TAG,
+	GOT_IMSG_TAG_TAGMSG,
 
 	/* Messages related to pack files. */
 	GOT_IMSG_PACKIDX,
@@ -162,6 +168,26 @@ struct got_imsg_blob {
 	size_t size;
 };
 
+/* Structure for GOT_IMSG_TAG data. */
+struct got_imsg_tag_object {
+	uint8_t id[SHA1_DIGEST_LENGTH];
+	int obj_type;
+	size_t tag_len;
+	size_t tagger_len;
+	time_t tagger_time;
+	time_t tagger_gmtoff;
+	size_t tagmsg_len;
+
+	/*
+	 * Followed by tag_len + tagger_len data bytes
+	 */
+
+	/*
+	 * Followed by 'tagmsg_len' bytes of tag message data in
+	 * one or more GOT_IMSG_TAG_TAGMSG messages.
+	 */
+} __attribute__((__packed__));
+
 /* Structure for GOT_IMSG_PACKIDX. */
 struct got_imsg_packidx {
 	size_t len;
@@ -212,6 +238,10 @@ const struct got_error *got_privsep_send_tree(struct i
     struct got_tree_object *);
 const struct got_error *got_privsep_send_blob(struct imsgbuf *, size_t);
 const struct got_error *got_privsep_recv_blob(size_t *, struct imsgbuf *);
+const struct got_error *got_privsep_send_tag(struct imsgbuf *,
+    struct got_tag_object *);
+const struct got_error *got_privsep_recv_tag(struct got_tag_object **,
+    struct imsgbuf *);
 const struct got_error *got_privsep_init_pack_child(struct imsgbuf *,
     struct got_pack *, struct got_packidx *);
 const struct got_error *got_privsep_send_packed_obj_req(struct imsgbuf *, int,
blob - feabca184d20dfd1ca4c681d9ccb24e0c7717d12
blob + 4379222e6238860a7965b41526f817c2614aa6e5
--- lib/got_lib_repository.h
+++ lib/got_lib_repository.h
@@ -28,16 +28,18 @@ struct got_repository {
 	struct got_pack packs[GOT_PACK_CACHE_SIZE];
 
 	/* Handles to child processes for reading loose objects. */
-	 struct got_privsep_child privsep_children[4];
+	 struct got_privsep_child privsep_children[5];
 #define GOT_REPO_PRIVSEP_CHILD_OBJECT	0
 #define GOT_REPO_PRIVSEP_CHILD_COMMIT	1
 #define GOT_REPO_PRIVSEP_CHILD_TREE	2
 #define GOT_REPO_PRIVSEP_CHILD_BLOB	3
+#define GOT_REPO_PRIVSEP_CHILD_TAG	4
 
 	/* Caches for open objects. */
 	struct got_object_cache objcache;
 	struct got_object_cache treecache;
 	struct got_object_cache commitcache;
+	struct got_object_cache tagcache;
 };
 
 const struct got_error*got_repo_cache_object(struct got_repository *,
@@ -52,6 +54,10 @@ const struct got_error*got_repo_cache_commit(struct go
     struct got_object_id *, struct got_commit_object *);
 struct got_commit_object *got_repo_get_cached_commit(struct got_repository *,
     struct got_object_id *);
+const struct got_error*got_repo_cache_tag(struct got_repository *,
+    struct got_object_id *, struct got_tag_object *);
+struct got_tag_object *got_repo_get_cached_tag(struct got_repository *,
+    struct got_object_id *);
 const struct got_error *got_repo_cache_packidx(struct got_repository *,
     struct got_packidx *);
 const struct got_error *got_repo_search_packidx(struct got_packidx **, int *,
blob - 6bb2eb4489271559e6ddb7579f548ba80e7d7ff0
blob + 641b24fbfac455d82e70678c29699656df2e9b22
--- lib/object.c
+++ lib/object.c
@@ -700,6 +700,85 @@ got_object_blob_dump_to_file(size_t *total_len, size_t
 	rewind(outfile);
 
 	return NULL;
+}
+
+static const struct got_error *
+open_tag(struct got_tag_object **tag,
+    struct got_repository *repo, struct got_object *obj, int check_cache)
+{
+	const struct got_error *err = NULL;
+
+	if (check_cache) {
+		*tag = got_repo_get_cached_tag(repo, &obj->id);
+		if (*tag != NULL) {
+			(*tag)->refcnt++;
+			return NULL;
+		}
+	} else
+		*tag = NULL;
+
+	if (obj->type != GOT_OBJ_TYPE_TAG)
+		return got_error(GOT_ERR_OBJ_TYPE);
+
+	if (obj->flags & GOT_OBJ_FLAG_PACKED) {
+		struct got_pack *pack;
+		pack = got_repo_get_cached_pack(repo, obj->path_packfile);
+		if (pack == NULL) {
+			err = got_repo_cache_pack(&pack, repo,
+			    obj->path_packfile, NULL);
+			if (err)
+				return err;
+		}
+		err = got_object_read_packed_tag_privsep(tag, obj, pack);
+	} else {
+		int fd;
+		err = open_loose_object(&fd, obj, repo);
+		if (err)
+			return err;
+		err = got_object_read_tag_privsep(tag, obj, fd, repo);
+		close(fd);
+	}
+
+	if (err == NULL) {
+		(*tag)->refcnt++;
+		err = got_repo_cache_tag(repo, &obj->id, *tag);
+	}
+
+	return err;
+}
+
+const struct got_error *
+got_object_open_as_tag(struct got_tag_object **tag,
+    struct got_repository *repo, struct got_object_id *id)
+{
+	const struct got_error *err;
+	struct got_object *obj;
+
+	*tag = got_repo_get_cached_tag(repo, id);
+	if (*tag != NULL) {
+		(*tag)->refcnt++;
+		return NULL;
+	}
+
+	err = got_object_open(&obj, repo, id);
+	if (err)
+		return err;
+	if (got_object_get_type(obj) != GOT_OBJ_TYPE_COMMIT) {
+		err = got_error(GOT_ERR_OBJ_TYPE);
+		goto done;
+	}
+
+	err = open_tag(tag, repo, obj, 0);
+done:
+	got_object_close(obj);
+	return err;
+}
+
+const struct got_error *
+got_object_tag_open(struct got_tag_object **tag,
+    struct got_repository *repo, struct got_object *obj)
+{
+	return open_tag(tag, repo, obj, 1);
 }
 
 static struct got_tree_entry *
@@ -1074,7 +1153,6 @@ done:
 	return err;
 }
 
-
 static const struct got_error *
 request_commit(struct got_commit_object **commit, struct got_repository *repo,
     struct got_object *obj, int fd)
@@ -1276,4 +1354,70 @@ got_object_read_blob_privsep(size_t *size, int outfd, 
 	repo->privsep_children[GOT_REPO_PRIVSEP_CHILD_BLOB].ibuf = ibuf;
 
 	return request_blob(size, outfd, infd, ibuf);
+}
+
+static const struct got_error *
+request_tag(struct got_tag_object **tag, struct got_repository *repo,
+    struct got_object *obj, int fd)
+{
+	const struct got_error *err = NULL;
+	struct imsgbuf *ibuf;
+
+	ibuf = repo->privsep_children[GOT_REPO_PRIVSEP_CHILD_TAG].ibuf;
+
+	err = got_privsep_send_obj_req(ibuf, fd, obj);
+	if (err)
+		return err;
+
+	return got_privsep_recv_tag(tag, ibuf);
+}
+
+const struct got_error *
+got_object_read_packed_tag_privsep(struct got_tag_object **tag,
+    struct got_object *obj, struct got_pack *pack)
+{
+	const struct got_error *err = NULL;
+
+	err = got_privsep_send_obj_req(pack->privsep_child->ibuf, -1, obj);
+	if (err)
+		return err;
+
+	return got_privsep_recv_tag(tag, pack->privsep_child->ibuf);
+}
+
+const struct got_error *
+got_object_read_tag_privsep(struct got_tag_object **tag,
+    struct got_object *obj, int obj_fd, struct got_repository *repo)
+{
+	int imsg_fds[2];
+	pid_t pid;
+	struct imsgbuf *ibuf;
+
+	if (repo->privsep_children[GOT_REPO_PRIVSEP_CHILD_TAG].imsg_fd != -1)
+		return request_tag(tag, repo, obj, obj_fd);
+
+	ibuf = calloc(1, sizeof(*ibuf));
+	if (ibuf == NULL)
+		return got_error_from_errno();
+
+	if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, imsg_fds) == -1)
+		return got_error_from_errno();
+
+	pid = fork();
+	if (pid == -1)
+		return got_error_from_errno();
+	else if (pid == 0) {
+		exec_privsep_child(imsg_fds, GOT_PATH_PROG_READ_TAG,
+		    repo->path);
+		/* not reached */
+	}
+
+	close(imsg_fds[1]);
+	repo->privsep_children[GOT_REPO_PRIVSEP_CHILD_TAG].imsg_fd =
+	    imsg_fds[0];
+	repo->privsep_children[GOT_REPO_PRIVSEP_CHILD_TAG].pid = pid;
+	imsg_init(ibuf, imsg_fds[0]);
+	repo->privsep_children[GOT_REPO_PRIVSEP_CHILD_TAG].ibuf = ibuf;
+
+	return request_tag(tag, repo, obj, obj_fd);
 }
blob - 3325c9fb156df0f75d84e0b7075b5827a4cf25cc
blob + 63b2ca2a42f301ea74c82bfe070daf1cf71508ab
--- lib/object_cache.c
+++ lib/object_cache.c
@@ -35,6 +35,7 @@
 #define GOT_OBJECT_CACHE_SIZE_OBJ	256
 #define GOT_OBJECT_CACHE_SIZE_TREE	256
 #define GOT_OBJECT_CACHE_SIZE_COMMIT	64
+#define GOT_OBJECT_CACHE_SIZE_TAG	32
 
 const struct got_error *
 got_object_cache_init(struct got_object_cache *cache,
@@ -57,6 +58,9 @@ got_object_cache_init(struct got_object_cache *cache,
 	case GOT_OBJECT_CACHE_TYPE_COMMIT:
 		cache->size = GOT_OBJECT_CACHE_SIZE_COMMIT;
 		break;
+	case GOT_OBJECT_CACHE_TYPE_TAG:
+		cache->size = GOT_OBJECT_CACHE_SIZE_TAG;
+		break;
 	}
 	return NULL;
 }
@@ -84,6 +88,9 @@ got_object_cache_add(struct got_object_cache *cache, s
 		case GOT_OBJECT_CACHE_TYPE_COMMIT:
 			got_object_commit_close(ce->data.commit);
 			break;
+		case GOT_OBJECT_CACHE_TYPE_TAG:
+			got_object_tag_close(ce->data.tag);
+			break;
 		}
 		free(ce);
 		cache->cache_evict++;
@@ -103,6 +110,9 @@ got_object_cache_add(struct got_object_cache *cache, s
 	case GOT_OBJECT_CACHE_TYPE_COMMIT:
 		ce->data.commit = (struct got_commit_object *)item;
 		break;
+	case GOT_OBJECT_CACHE_TYPE_TAG:
+		ce->data.tag = (struct got_tag_object *)item;
+		break;
 	}
 
 	err = got_object_idset_add(cache->idset, id, ce);
@@ -131,6 +141,8 @@ got_object_cache_get(struct got_object_cache *cache, s
 			return ce->data.tree;
 		case GOT_OBJECT_CACHE_TYPE_COMMIT:
 			return ce->data.commit;
+		case GOT_OBJECT_CACHE_TYPE_TAG:
+			return ce->data.tag;
 		}
 	}
 
@@ -157,6 +169,7 @@ check_refcount(struct got_object_id *id, void *data, v
 	struct got_object *obj;
 	struct got_tree_object *tree;
 	struct got_commit_object *commit;
+	struct got_tag_object *tag;
 	char *id_str;
 
 	if (got_object_id_str(&id_str, id) != NULL)
@@ -184,6 +197,13 @@ check_refcount(struct got_object_id *id, void *data, v
 		fprintf(stderr, "commit %s has %d unclaimed references\n",
 		    id_str, commit->refcnt - 1);
 		break;
+	case GOT_OBJECT_CACHE_TYPE_TAG:
+		tag = ce->data.tag;
+		if (tag->refcnt == 1)
+			break;
+		fprintf(stderr, "tag %s has %d unclaimed references\n",
+		    id_str, tag->refcnt - 1);
+		break;
 	}
 	free(id_str);
 	return NULL;
blob - 0c8445c525924957e8cc178b0f1f868028a3699d
blob + 433a6598256a2714af7dc3ab5859a6231a30150d
--- lib/object_parse.c
+++ lib/object_parse.c
@@ -53,11 +53,21 @@
 #define nitems(_a) (sizeof(_a) / sizeof((_a)[0]))
 #endif
 
+#define GOT_OBJ_TAG_COMMIT	"commit"
+#define GOT_OBJ_TAG_TREE	"tree"
+#define GOT_OBJ_TAG_BLOB	"blob"
+#define GOT_OBJ_TAG_TAG		"tag"
+
 #define GOT_COMMIT_TAG_TREE		"tree "
 #define GOT_COMMIT_TAG_PARENT		"parent "
 #define GOT_COMMIT_TAG_AUTHOR		"author "
 #define GOT_COMMIT_TAG_COMMITTER	"committer "
 
+#define GOT_TAG_TAG_OBJECT		"object "
+#define GOT_TAG_TAG_TYPE		"type "
+#define GOT_TAG_TAG_TAG			"tag "
+#define GOT_TAG_TAG_TAGGER		"tagger "
+
 int
 got_object_id_cmp(const struct got_object_id *id1,
     const struct got_object_id *id2)
@@ -518,7 +528,188 @@ got_object_parse_tree(struct got_tree_object **tree, u
 	return NULL;
 }
 
+void
+got_object_tag_close(struct got_tag_object *tag)
+{
+	free(tag->tag);
+	free(tag->tagger);
+	free(tag->tagmsg);
+	free(tag);
+}
+
 const struct got_error *
+got_object_parse_tag(struct got_tag_object **tag, uint8_t *buf, size_t len)
+{
+	const struct got_error *err = NULL;
+	size_t remain = len;
+	char *s = buf;
+	size_t tlen;
+
+	*tag = calloc(1, sizeof(**tag));
+	if (*tag == NULL)
+		return got_error_from_errno();
+
+	tlen = strlen(GOT_TAG_TAG_OBJECT);
+	if (strncmp(s, GOT_TAG_TAG_OBJECT, tlen) == 0) {
+		remain -= tlen;
+		if (remain < SHA1_DIGEST_STRING_LENGTH) {
+			err = got_error(GOT_ERR_BAD_OBJ_DATA);
+			goto done;
+		}
+		s += tlen;
+		if (!got_parse_sha1_digest((*tag)->id.sha1, s)) {
+			err = got_error(GOT_ERR_BAD_OBJ_DATA);
+			goto done;
+		}
+		remain -= SHA1_DIGEST_STRING_LENGTH;
+		s += SHA1_DIGEST_STRING_LENGTH;
+	} else {
+		err = got_error(GOT_ERR_BAD_OBJ_DATA);
+		goto done;
+	}
+
+	if (remain <= 0) {
+		err = got_error(GOT_ERR_BAD_OBJ_DATA);
+		goto done;
+	}
+
+	tlen = strlen(GOT_TAG_TAG_TYPE);
+	if (strncmp(s, GOT_TAG_TAG_TYPE, tlen) == 0) {
+		remain -= tlen;
+		if (remain <= 0) {
+			err = got_error(GOT_ERR_BAD_OBJ_DATA);
+			goto done;
+		}
+		s += tlen;
+		if (strncmp(s, GOT_OBJ_TAG_COMMIT,
+		    strlen(GOT_OBJ_TAG_COMMIT)) == 0) {
+			(*tag)->obj_type = GOT_OBJ_TYPE_COMMIT;
+			tlen = strlen(GOT_OBJ_TAG_COMMIT);
+			s += tlen;
+			remain -= tlen;
+		} else if (strncmp(s, GOT_OBJ_TAG_TREE,
+		    strlen(GOT_OBJ_TAG_TREE)) == 0) {
+			(*tag)->obj_type = GOT_OBJ_TYPE_TREE;
+			tlen = strlen(GOT_OBJ_TAG_TREE);
+			s += tlen;
+			remain -= tlen;
+		} else if (strncmp(s, GOT_OBJ_TAG_BLOB,
+		    strlen(GOT_OBJ_TAG_BLOB)) == 0) {
+			(*tag)->obj_type = GOT_OBJ_TYPE_BLOB;
+			tlen = strlen(GOT_OBJ_TAG_BLOB);
+			s += tlen;
+			remain -= tlen;
+		} else if (strncmp(s, GOT_OBJ_TAG_TAG,
+		    strlen(GOT_OBJ_TAG_TAG)) == 0) {
+			(*tag)->obj_type = GOT_OBJ_TYPE_TAG;
+			tlen = strlen(GOT_OBJ_TAG_TAG);
+			s += tlen;
+			remain -= tlen;
+		} else {
+			err = got_error(GOT_ERR_BAD_OBJ_DATA);
+			goto done;
+		}
+
+		if (remain <= 0 || *s != '\n') {
+			err = got_error(GOT_ERR_BAD_OBJ_DATA);
+			goto done;
+		}
+		s++;
+		remain--;
+		if (remain <= 0) {
+			err = got_error(GOT_ERR_BAD_OBJ_DATA);
+			goto done;
+		}
+	} else {
+		err = got_error(GOT_ERR_BAD_OBJ_DATA);
+		goto done;
+	}
+
+	tlen = strlen(GOT_TAG_TAG_TAG);
+	if (strncmp(s, GOT_TAG_TAG_TAG, tlen) == 0) {
+		char *p;
+		size_t slen;
+		remain -= tlen;
+		if (remain <= 0) {
+			err = got_error(GOT_ERR_BAD_OBJ_DATA);
+			goto done;
+		}
+		s += tlen;
+		p = strchr(s, '\n');
+		if (p == NULL) {
+			err = got_error(GOT_ERR_BAD_OBJ_DATA);
+			goto done;
+		}
+		*p = '\0';
+		slen = strlen(s);
+		(*tag)->tag = strndup(s, slen);
+		if ((*tag)->tag == NULL) {
+			err = got_error_from_errno();
+			goto done;
+		}
+		s += slen + 1;
+		remain -= slen + 1;
+		if (remain <= 0) {
+			err = got_error(GOT_ERR_BAD_OBJ_DATA);
+			goto done;
+		}
+	} else {
+		err = got_error(GOT_ERR_BAD_OBJ_DATA);
+		goto done;
+	}
+
+	tlen = strlen(GOT_TAG_TAG_TAGGER);
+	if (strncmp(s, GOT_TAG_TAG_TAGGER, tlen) == 0) {
+		char *p;
+		size_t slen;
+
+		remain -= tlen;
+		if (remain <= 0) {
+			err = got_error(GOT_ERR_BAD_OBJ_DATA);
+			goto done;
+		}
+		s += tlen;
+		p = strchr(s, '\n');
+		if (p == NULL) {
+			err = got_error(GOT_ERR_BAD_OBJ_DATA);
+			goto done;
+		}
+		*p = '\0';
+		slen = strlen(s);
+		err = parse_commit_time(&(*tag)->tagger_time,
+		    &(*tag)->tagger_gmtoff, s);
+		if (err)
+			goto done;
+		(*tag)->tagger = strdup(s);
+		if ((*tag)->tagger == NULL) {
+			err = got_error_from_errno();
+			goto done;
+		}
+		s += slen + 1;
+		remain -= slen + 1;
+		if (remain <= 0) {
+			err = got_error(GOT_ERR_BAD_OBJ_DATA);
+			goto done;
+		}
+	} else {
+		err = got_error(GOT_ERR_BAD_OBJ_DATA);
+		goto done;
+	}
+
+	(*tag)->tagmsg = strndup(s, remain);
+	if ((*tag)->tagmsg == NULL) {
+		err = got_error_from_errno();
+		goto done;
+	}
+done:
+	if (err) {
+		got_object_tag_close(*tag);
+		*tag = NULL;
+	}
+	return err;
+}
+
+const struct got_error *
 got_read_file_to_mem(uint8_t **outbuf, size_t *outlen, FILE *f)
 {
 	const struct got_error *err = NULL;
blob - 28b85018e0bd0857fe3c037382a2318f2f331963
blob + d95f5012099136dd65c3fbc85cbf8a99a89abdf7
--- lib/privsep.c
+++ lib/privsep.c
@@ -237,6 +237,9 @@ got_privsep_send_obj_req(struct imsgbuf *ibuf, int fd,
 			break;
 		case GOT_OBJ_TYPE_BLOB:
 			imsg_code = GOT_IMSG_BLOB_REQUEST;
+			break;
+		case GOT_OBJ_TYPE_TAG:
+			imsg_code = GOT_IMSG_TAG_REQUEST;
 			break;
 		default:
 			return got_error(GOT_ERR_OBJ_TYPE);
@@ -814,6 +817,211 @@ got_privsep_recv_blob(size_t *size, struct imsgbuf *ib
 		iblob = imsg.data;
 		*size = iblob->size;
 		/* Data has been written to file descriptor. */
+		break;
+	default:
+		err = got_error(GOT_ERR_PRIVSEP_MSG);
+		break;
+	}
+
+	imsg_free(&imsg);
+
+	return err;
+}
+
+static const struct got_error *
+send_tagmsg(struct imsgbuf *ibuf, struct got_tag_object *tag, size_t tagmsg_len)
+{
+	const struct got_error *err = NULL;
+	size_t offset, remain;
+
+	offset = 0;
+	remain = tagmsg_len;
+	while (remain > 0) {
+		size_t n = MIN(MAX_IMSGSIZE - IMSG_HEADER_SIZE, remain);
+
+		if (imsg_compose(ibuf, GOT_IMSG_TAG_TAGMSG, 0, 0, -1,
+		    tag->tagmsg + offset, n) == -1) {
+			err = got_error_from_errno();
+			break;
+		}
+
+		err = flush_imsg(ibuf);
+		if (err)
+			break;
+
+		offset += n;
+		remain -= n;
+	}
+
+	return err;
+}
+
+const struct got_error *
+got_privsep_send_tag(struct imsgbuf *ibuf, struct got_tag_object *tag)
+{
+	const struct got_error *err = NULL;
+	struct got_imsg_tag_object *itag;
+	uint8_t *buf;
+	size_t len, total;
+	size_t tag_len = strlen(tag->tag);
+	size_t tagger_len = strlen(tag->tagger);
+	size_t tagmsg_len = strlen(tag->tagmsg);
+
+	total = sizeof(*itag) + tag_len + tagger_len + tagmsg_len;
+
+	buf = malloc(total);
+	if (buf == NULL)
+		return got_error_from_errno();
+
+	itag = (struct got_imsg_tag_object *)buf;
+	memcpy(itag->id, tag->id.sha1, sizeof(itag->id));
+	itag->obj_type = tag->obj_type;
+	itag->tag_len = tag_len;
+	itag->tagger_len = tagger_len;
+	itag->tagger_time = tag->tagger_time;
+	itag->tagger_gmtoff = tag->tagger_gmtoff;
+	itag->tagmsg_len = tagmsg_len;
+
+	len = sizeof(*itag);
+	memcpy(buf + len, tag->tag, tag_len);
+	len += tag_len;
+	memcpy(buf + len, tag->tagger, tagger_len);
+	len += tagger_len;
+
+	if (imsg_compose(ibuf, GOT_IMSG_TAG, 0, 0, -1, buf, len) == -1) {
+		err = got_error_from_errno();
+		goto done;
+	}
+
+	if (tagmsg_len == 0 ||
+	    tagmsg_len + len > MAX_IMSGSIZE - IMSG_HEADER_SIZE) {
+		err = flush_imsg(ibuf);
+		if (err)
+			goto done;
+	}
+	err = send_tagmsg(ibuf, tag, tagmsg_len);
+done:
+	free(buf);
+	return err;
+}
+
+const struct got_error *
+got_privsep_recv_tag(struct got_tag_object **tag, struct imsgbuf *ibuf)
+{
+	const struct got_error *err = NULL;
+	struct imsg imsg;
+	struct got_imsg_tag_object *itag;
+	size_t len, datalen;
+	const size_t min_datalen =
+	    MIN(sizeof(struct got_imsg_error),
+	    sizeof(struct got_imsg_tag_object));
+
+	*tag = NULL;
+
+	err = got_privsep_recv_imsg(&imsg, ibuf, min_datalen);
+	if (err)
+		return err;
+
+	datalen = imsg.hdr.len - IMSG_HEADER_SIZE;
+	len = 0;
+
+	switch (imsg.hdr.type) {
+	case GOT_IMSG_TAG:
+		if (datalen < sizeof(*itag)) {
+			err = got_error(GOT_ERR_PRIVSEP_LEN);
+			break;
+		}
+		itag = imsg.data;
+		if (datalen != sizeof(*itag) + itag->tag_len +
+		    itag->tagger_len) {
+			err = got_error(GOT_ERR_PRIVSEP_LEN);
+			break;
+		}
+		len += sizeof(*itag);
+
+		*tag = calloc(1, sizeof(**tag));
+		if (*tag == NULL) {
+			err = got_error_from_errno();
+			break;
+		}
+
+		memcpy((*tag)->id.sha1, itag->id, SHA1_DIGEST_LENGTH);
+
+		if (itag->tag_len == 0) {
+			(*tag)->tag = strdup("");
+			if ((*tag)->tag == NULL) {
+				err = got_error_from_errno();
+				break;
+			}
+		} else {
+			(*tag)->tag = malloc(itag->tag_len + 1);
+			if ((*tag)->tag == NULL) {
+				err = got_error_from_errno();
+				break;
+			}
+			memcpy((*tag)->tag, imsg.data + len,
+			    itag->tag_len);
+			(*tag)->tag[itag->tag_len] = '\0';
+		}
+		len += itag->tag_len;
+
+		(*tag)->obj_type = itag->obj_type;
+		(*tag)->tagger_time = itag->tagger_time;
+		(*tag)->tagger_gmtoff = itag->tagger_gmtoff;
+
+		if (itag->tagger_len == 0) {
+			(*tag)->tagger = strdup("");
+			if ((*tag)->tagger == NULL) {
+				err = got_error_from_errno();
+				break;
+			}
+		} else {
+			(*tag)->tagger = malloc(itag->tagger_len + 1);
+			if ((*tag)->tagger == NULL) {
+				err = got_error_from_errno();
+				break;
+			}
+			memcpy((*tag)->tagger, imsg.data + len,
+			    itag->tagger_len);
+			(*tag)->tagger[itag->tagger_len] = '\0';
+		}
+		len += itag->tagger_len;
+
+		if (itag->tagmsg_len == 0) {
+			(*tag)->tagmsg = strdup("");
+			if ((*tag)->tagmsg == NULL) {
+				err = got_error_from_errno();
+				break;
+			}
+		} else {
+			size_t offset = 0, remain = itag->tagmsg_len;
+
+			(*tag)->tagmsg = malloc(itag->tagmsg_len + 1);
+			if ((*tag)->tagmsg == NULL) {
+				err = got_error_from_errno();
+				break;
+			}
+			while (remain > 0) {
+				struct imsg imsg_log;
+				size_t n = MIN(MAX_IMSGSIZE - IMSG_HEADER_SIZE,
+				    remain);
+
+				err = got_privsep_recv_imsg(&imsg_log, ibuf, n);
+				if (err)
+					return err;
+
+				if (imsg_log.hdr.type != GOT_IMSG_TAG_TAGMSG)
+					return got_error(GOT_ERR_PRIVSEP_MSG);
+
+				memcpy((*tag)->tagmsg + offset, imsg_log.data,
+				    n);
+				imsg_free(&imsg_log);
+				offset += n;
+				remain -= n;
+			}
+			(*tag)->tagmsg[itag->tagmsg_len] = '\0';
+		}
+
 		break;
 	default:
 		err = got_error(GOT_ERR_PRIVSEP_MSG);
blob - 3becb1ca45ed5e33df2ffa0843361e63961071ad
blob + 941438ef7171b50941a6d4601ad0c38546dfebed
--- lib/repository.c
+++ lib/repository.c
@@ -229,6 +229,27 @@ got_repo_get_cached_commit(struct got_repository *repo
 {
 	return (struct got_commit_object *)got_object_cache_get(
 	    &repo->commitcache, id);
+}
+
+const struct got_error *
+got_repo_cache_tag(struct got_repository *repo, struct got_object_id *id,
+    struct got_tag_object *tag)
+{
+#ifndef GOT_NO_OBJ_CACHE
+	const struct got_error *err = NULL;
+	err = got_object_cache_add(&repo->tagcache, id, tag);
+	if (err)
+		return err;
+	tag->refcnt++;
+#endif
+	return NULL;
+}
+
+struct got_tag_object *
+got_repo_get_cached_tag(struct got_repository *repo, struct got_object_id *id)
+{
+	return (struct got_tag_object *)got_object_cache_get(
+	    &repo->tagcache, id);
 }
 
 const struct got_error *
@@ -354,6 +375,10 @@ got_repo_open(struct got_repository **repop, const cha
 	    GOT_OBJECT_CACHE_TYPE_COMMIT);
 	if (err)
 		goto done;
+	err = got_object_cache_init(&repo->tagcache,
+	    GOT_OBJECT_CACHE_TYPE_TAG);
+	if (err)
+		goto done;
 
 	normpath = got_path_normalize(abspath);
 	if (normpath == NULL) {
@@ -413,6 +438,7 @@ got_repo_close(struct got_repository *repo)
 	got_object_cache_close(&repo->objcache);
 	got_object_cache_close(&repo->treecache);
 	got_object_cache_close(&repo->commitcache);
+	got_object_cache_close(&repo->tagcache);
 
 	for (i = 0; i < nitems(repo->privsep_children); i++) {
 		if (repo->privsep_children[i].imsg_fd == -1)
blob - 7738a57cd9ee8350a5e72c39a576e5b654aa263c
blob + ae3722c08c1905a6e64dee0e34ec327baf6cb82c
--- libexec/got-read-object/got-read-object.c
+++ libexec/got-read-object/got-read-object.c
@@ -45,6 +45,7 @@
 #define GOT_OBJ_TAG_COMMIT	"commit"
 #define GOT_OBJ_TAG_TREE	"tree"
 #define GOT_OBJ_TAG_BLOB	"blob"
+#define GOT_OBJ_TAG_TAG		"tag"
 
 static volatile sig_atomic_t sigint_received;
 
@@ -60,12 +61,14 @@ parse_object_header(struct got_object **obj, char *buf
 	const char *obj_tags[] = {
 		GOT_OBJ_TAG_COMMIT,
 		GOT_OBJ_TAG_TREE,
-		GOT_OBJ_TAG_BLOB
+		GOT_OBJ_TAG_BLOB,
+		GOT_OBJ_TAG_TAG,
 	};
 	const int obj_types[] = {
 		GOT_OBJ_TYPE_COMMIT,
 		GOT_OBJ_TYPE_TREE,
 		GOT_OBJ_TYPE_BLOB,
+		GOT_OBJ_TYPE_TAG,
 	};
 	int type = 0;
 	size_t size = 0, hdrlen = 0;
blob - 24f359a343b084dcfe5e2654c1b4ae57fcaa9e05
blob + e5d58ee0bb72b099f33b72f3367599d9f6fee2eb
--- libexec/got-read-pack/got-read-pack.c
+++ libexec/got-read-pack/got-read-pack.c
@@ -272,6 +272,43 @@ done:
 }
 
 static const struct got_error *
+tag_request(struct imsg *imsg, struct imsgbuf *ibuf, struct got_pack *pack,
+    struct got_packidx *packidx, struct got_object_cache *objcache)
+{
+	const struct got_error *err = NULL;
+	struct got_object *obj = NULL;
+	struct got_tag_object *tag = NULL;
+	uint8_t *buf;
+	size_t len;
+
+	err = get_object(&obj, imsg, ibuf, pack, packidx, objcache,
+	    GOT_OBJ_TYPE_TAG);
+	if (err)
+		return err;
+
+	err = got_packfile_extract_object_to_mem(&buf, &len, obj, pack);
+	if (err)
+		return err;
+
+	obj->size = len;
+	err = got_object_parse_tag(&tag, buf, len);
+	free(buf);
+
+	err = got_privsep_send_tag(ibuf, tag);
+	if (obj)
+		got_object_close(obj);
+	got_object_tag_close(tag);
+	if (err) {
+		if (err->code == GOT_ERR_PRIVSEP_PIPE)
+			err = NULL;
+		else
+			got_privsep_send_error(ibuf, err);
+	}
+
+	return err;
+}
+
+static const struct got_error *
 receive_packidx(struct got_packidx **packidx, struct imsgbuf *ibuf)
 {
 	const struct got_error *err = NULL;
@@ -488,6 +525,10 @@ main(int argc, char *argv[])
 			err = blob_request(&imsg, &ibuf, pack, packidx,
 			   &objcache);
 			break;
+		case GOT_IMSG_TAG_REQUEST:
+			err = tag_request(&imsg, &ibuf, pack, packidx,
+			   &objcache);
+			break;
 		default:
 			err = got_error(GOT_ERR_PRIVSEP_MSG);
 			break;