Commit Diff


commit - d82ad331dd0534d30ff4b9f4b180949377be04c1
commit + 899d86c25715751ba2eb6b328649331c079f3b07
blob - 30c2779376b3b0fffa8e8fe586ab2ea6d27f9f88
blob + edb8d47113d12cde5fa1161072e3d17296431831
--- tog/tog.c
+++ tog/tog.c
@@ -195,30 +195,19 @@ struct commit_queue_entry {
 };
 TAILQ_HEAD(commit_queue, commit_queue_entry);
 
-static const struct got_error *
-push_commit(struct commit_queue_entry **entryp, struct commit_queue *commits,
-    struct got_commit_object *commit, struct got_object_id *id)
+static struct commit_queue_entry *
+alloc_commit_queue_entry(struct got_commit_object *commit,
+    struct got_object_id *id)
 {
-	const struct got_error *err = NULL;
 	struct commit_queue_entry *entry;
 
-	*entryp = NULL;
-
 	entry = calloc(1, sizeof(*entry));
 	if (entry == NULL)
-		return got_error_from_errno();
+		return NULL;
 
-	entry->id = got_object_id_dup(id);
-	if (entry->id == NULL) {
-		err = got_error_from_errno();
-		free(entry);
-		return err;
-	}
-
+	entry->id = id;
 	entry->commit = commit;
-	TAILQ_INSERT_HEAD(commits, entry, entry);
-	*entryp = entry;
-	return NULL;
+	return entry;
 }
 
 static void
@@ -242,18 +231,16 @@ free_commits(struct commit_queue *commits)
 
 
 static const struct got_error *
-fetch_next_commit(struct commit_queue_entry **pentryp,
-    struct commit_queue *commits, struct got_repository *repo)
+fetch_parent_commit(struct commit_queue_entry **pentry,
+    struct commit_queue_entry *entry, struct got_repository *repo)
 {
-	const struct got_error *err;
+	const struct got_error *err = NULL;
 	struct got_object *obj = NULL;
-	struct got_commit_object *pcommit;
-	struct commit_queue_entry *entry, *pentry;
+	struct got_commit_object *commit;
+	struct got_object_id *id;
 	struct got_parent_id *pid;
 
-	if (pentryp)
-		*pentryp = NULL;
-	entry = TAILQ_LAST(commits, commit_queue);
+	*pentry = NULL;
 
 	/* Follow the first parent (TODO: handle merge commits). */
 	pid = SIMPLEQ_FIRST(&entry->commit->parent_ids);
@@ -268,60 +255,194 @@ fetch_next_commit(struct commit_queue_entry **pentryp,
 		return err;
 	}
 
-	err = got_object_commit_open(&pcommit, repo, obj);
+	err = got_object_commit_open(&commit, repo, obj);
 	got_object_close(obj);
 	if (err)
 		return err;
 
-	pentry = calloc(1, sizeof(*pentry));
-	if (pentry == NULL) {
+	id = got_object_id_dup(pid->id);
+	if (id == NULL) {
 		err = got_error_from_errno();
-		got_object_commit_close(pcommit);
-		return err;
+		got_object_commit_close(commit);
+		return err;;
 	}
-	pentry->id = got_object_id_dup(pid->id);
-	if (pentry->id == NULL) {
+
+	*pentry = alloc_commit_queue_entry(commit, id);
+	if (*pentry == NULL) {
 		err = got_error_from_errno();
-		got_object_commit_close(pcommit);
-		return err;;
+		got_object_commit_close(commit);
 	}
-	pentry->commit = pcommit;
-	TAILQ_INSERT_TAIL(commits, pentry, entry);
-	if (pentryp)
-		*pentryp = pentry;
 
+	return err;;
+}
+
+static const struct got_error *
+get_head_commit_id(struct got_object_id **head_id, struct got_repository *repo)
+{
+	const struct got_error *err = NULL;
+	struct got_reference *head_ref;
+
+	*head_id = NULL;
+
+	err = got_ref_open(&head_ref, repo, GOT_REF_HEAD);
+	if (err)
+		return err;
+
+	err = got_ref_resolve(head_id, repo, head_ref);
+	got_ref_close(head_ref);
+	if (err) {
+		*head_id = NULL;
+		return err;
+	}
+
 	return NULL;
 }
 
 static const struct got_error *
-fetch_commits(struct commit_queue *commits, struct got_object *root_obj,
-    struct got_object_id *root_id, struct got_repository *repo, int limit)
+prepend_commits(int *ncommits, struct commit_queue *commits,
+    struct got_object_id *first_id, struct got_object_id *last_id,
+    int limit, struct got_repository *repo)
+{
+	const struct got_error *err = NULL;
+	struct got_object *first_obj = NULL, *last_obj = NULL;
+	struct got_commit_object *commit = NULL;
+	struct got_object_id *id = NULL;
+	struct commit_queue_entry *entry, *old_head_entry;
+
+	*ncommits = 0;
+
+	err = got_object_open(&first_obj, repo, first_id);
+	if (err)
+		goto done;
+	if (got_object_get_type(first_obj) != GOT_OBJ_TYPE_COMMIT) {
+		err = got_error(GOT_ERR_OBJ_TYPE);
+		goto done;
+	}
+	err = got_object_open(&last_obj, repo, last_id);
+	if (err)
+		goto done;
+	if (got_object_get_type(last_obj) != GOT_OBJ_TYPE_COMMIT) {
+		err = got_error(GOT_ERR_OBJ_TYPE);
+		goto done;
+	}
+
+	err = got_object_commit_open(&commit, repo, first_obj);
+	if (err)
+		goto done;
+
+	id = got_object_id_dup(first_id);
+	if (id == NULL) {
+		err = got_error_from_errno();
+		goto done;
+	}
+
+	entry = alloc_commit_queue_entry(commit, id);
+	if (entry == NULL)
+		return got_error_from_errno();
+
+	old_head_entry = TAILQ_FIRST(commits);
+	if (old_head_entry)
+		TAILQ_INSERT_BEFORE(old_head_entry, entry, entry);
+	else
+		TAILQ_INSERT_HEAD(commits, entry, entry);
+
+	*ncommits = 1;
+
+	/*
+	 * Fetch parent commits.
+	 * XXX If first and last commit aren't ancestrally related this loop
+	 * we will keep iterating until a root commit is encountered.
+	 */
+	while (1) {
+		struct commit_queue_entry *pentry;
+
+		err = fetch_parent_commit(&pentry, entry, repo);
+		if (err)
+			goto done;
+		if (pentry == NULL)
+			break;
+
+		/*
+		 * Fill up to old HEAD commit if commit queue was not empty.
+		 * We must not leave a gap in history.
+		 */
+		if (old_head_entry &&
+		    got_object_id_cmp(pentry->id, old_head_entry->id) == 0)
+			break;
+
+		TAILQ_INSERT_AFTER(commits, entry, pentry, entry);
+		(*ncommits)++;
+		if (*ncommits >= limit)
+			break;
+
+		/* Fill up to last requested commit if queue was empty. */
+		if (old_head_entry == NULL &&
+		    got_object_id_cmp(pentry->id, last_id) == 0)
+			break;
+
+		entry = pentry;
+	}
+
+done:
+	if (first_obj)
+		got_object_close(first_obj);
+	if (last_obj)
+		got_object_close(last_obj);
+	return err;
+}
+
+static const struct got_error *
+fetch_commits(struct commit_queue_entry **start_entry,
+    struct got_object_id *start_id, struct commit_queue *commits,
+    int limit, struct got_repository *repo)
 {
 	const struct got_error *err;
-	struct got_commit_object *root_commit;
 	struct commit_queue_entry *entry;
 	int ncommits = 0;
+	struct got_object_id *head_id = NULL;
 
-	err = got_object_commit_open(&root_commit, repo, root_obj);
+	*start_entry = NULL;
+
+	err = get_head_commit_id(&head_id, repo);
 	if (err)
 		return err;
 
-	err = push_commit(&entry, commits, root_commit, root_id);
+	/* Prepend HEAD commit and all ancestors up to start commit. */
+	err = prepend_commits(&ncommits, commits, head_id, start_id, limit,
+	    repo);
 	if (err)
 		return err;
 
-	while (entry->commit->nparents > 0 && ncommits < limit) {
-		err = fetch_next_commit(&entry, commits, repo);
+	if (got_object_id_cmp(head_id, start_id) == 0)
+		*start_entry = TAILQ_FIRST(commits);
+	else
+		*start_entry = TAILQ_LAST(commits, commit_queue);
+
+	if (ncommits >= limit)
+		return NULL;
+
+	/* Append more commits from start commit up to the requested limit. */
+	entry = TAILQ_LAST(commits, commit_queue);
+	while (entry && ncommits < limit) {
+		struct commit_queue_entry *pentry;
+
+		err = fetch_parent_commit(&pentry, entry, repo);
 		if (err)
 			break;
+		if (pentry)
+			TAILQ_INSERT_TAIL(commits, pentry, entry);
+		entry = pentry;
 		ncommits++;
 	}
 
+	if (err)
+		*start_entry = NULL;
 	return err;
 }
 
 static const struct got_error *
-draw_commits(struct commit_queue *commits, int selected)
+draw_commits(struct commit_queue_entry **last,
+    struct commit_queue_entry *first, int selected, int limit)
 {
 	const struct got_error *err = NULL;
 	struct commit_queue_entry *entry;
@@ -329,7 +450,11 @@ draw_commits(struct commit_queue *commits, int selecte
 
 	wclear(tog_log_view.window);
 
-	TAILQ_FOREACH(entry, commits, entry) {
+	entry = first;
+	*last = first;
+	while (entry) {
+		if (ncommits == limit)
+			break;
 		if (ncommits == selected)
 			wstandout(tog_log_view.window);
 		err = draw_commit(entry->commit, entry->id);
@@ -338,6 +463,8 @@ draw_commits(struct commit_queue *commits, int selecte
 		if (err)
 			break;
 		ncommits++;
+		*last = entry;
+		entry = TAILQ_NEXT(entry, entry);
 	}
 
 	update_panels();
@@ -347,36 +474,15 @@ draw_commits(struct commit_queue *commits, int selecte
 }
 
 static const struct got_error *
-get_head_commit_id(struct got_object_id **head_id, struct got_repository *repo)
-{
-	const struct got_error *err = NULL;
-	struct got_reference *head_ref;
-
-	*head_id = NULL;
-
-	err = got_ref_open(&head_ref, repo, GOT_REF_HEAD);
-	if (err)
-		return err;
-
-	err = got_ref_resolve(head_id, repo, head_ref);
-	got_ref_close(head_ref);
-	if (err) {
-		*head_id = NULL;
-		return err;
-	}
-
-	return NULL;
-}
-
-static const struct got_error *
 show_log_view(struct got_object_id *start_id, struct got_repository *repo)
 {
 	const struct got_error *err = NULL;
-	struct got_object *obj = NULL;
 	struct got_object_id *id;
-	int ch, done = 0, selected = 0, refetch_commits = 1;
+	int ch, done = 0, selected = 0;
 	struct commit_queue commits;
-	struct commit_queue_entry *entry = NULL;
+	struct commit_queue_entry *pentry = NULL;
+	struct commit_queue_entry *first_displayed_entry = NULL;
+	struct commit_queue_entry *last_displayed_entry = NULL;
 
 	id = got_object_id_dup(start_id);
 	if (id == NULL)
@@ -394,25 +500,13 @@ show_log_view(struct got_object_id *start_id, struct g
 			return got_error_from_errno();
 	}
 
-	err = got_object_open(&obj, repo, start_id);
+	TAILQ_INIT(&commits);
+	err = fetch_commits(&first_displayed_entry, id, &commits, LINES, repo);
 	if (err)
-		return err;
-	if (got_object_get_type(obj) != GOT_OBJ_TYPE_COMMIT) {
-		err = got_error(GOT_ERR_OBJ_TYPE);
 		goto done;
-	}
-
-	TAILQ_INIT(&commits);
-	do {
-		if (refetch_commits) {
-			free_commits(&commits);
-			err = fetch_commits(&commits, obj, id, repo, LINES - 1);
-			refetch_commits = 0;
-			if (err)
-				goto done;
-		}
-
-		err = draw_commits(&commits, selected);
+	while (!done) {
+		err = draw_commits(&last_displayed_entry, first_displayed_entry,
+		    selected, LINES);
 		if (err)
 			goto done;
 
@@ -441,37 +535,19 @@ show_log_view(struct got_object_id *start_id, struct g
 					break;
 
 				/* scroll down if there are more parents */
-				entry = TAILQ_LAST(&commits, commit_queue);
-				if (entry->commit->nparents == 0)
+				if (last_displayed_entry->commit->nparents == 0)
 					break;
-				pop_commit(&commits);
-				if (TAILQ_EMPTY(&commits)) {
-					refetch_commits = 1;
+				first_displayed_entry =
+				    TAILQ_NEXT(first_displayed_entry, entry);
+				err = fetch_parent_commit(&pentry,
+				    last_displayed_entry, repo);
+				if (err)
 					break;
-				}
-				entry = TAILQ_FIRST(&commits);
-				id = got_object_id_dup(entry->id);
-				if (id == NULL) {
-					err = got_error_from_errno();
-					goto done;
-				}
-				got_object_close(obj);
-				err = got_object_open(&obj, repo, id);
-				if (err) {
-					obj = NULL;
-					goto done;
-				}
-				if (got_object_get_type(obj) !=
-				    GOT_OBJ_TYPE_COMMIT) {
-					err = got_error(GOT_ERR_OBJ_TYPE);
-					goto done;
-				}
-				err = fetch_next_commit(NULL, &commits, repo);
-				if (err)
-					goto done;
+				if (pentry)
+					TAILQ_INSERT_TAIL(&commits, pentry,
+					    entry);
 				break;
 			case KEY_RESIZE:
-				refetch_commits = 1;
 				if (selected > LINES)
 					selected = LINES - 1;
 				break;
@@ -479,11 +555,9 @@ show_log_view(struct got_object_id *start_id, struct g
 				break;
 		}
 		nodelay(stdscr, TRUE);
-	} while (!done);
+	}
 done:
 	free_commits(&commits);
-	if (obj)
-		got_object_close(obj);
 	return err;
 }
 
@@ -492,7 +566,7 @@ cmd_log(int argc, char *argv[])
 {
 	const struct got_error *error;
 	struct got_repository *repo;
-	struct got_object_id *id = NULL;
+	struct got_object_id *start_id = NULL;
 	char *repo_path = NULL;
 	char *start_commit = NULL;
 	int ch;
@@ -533,22 +607,22 @@ cmd_log(int argc, char *argv[])
 		return error;
 
 	if (start_commit == NULL) {
-		error = get_head_commit_id(&id, repo);
+		error = get_head_commit_id(&start_id, repo);
 		if (error != NULL)
 			return error;
 	} else {
 		struct got_object *obj;
 		error = got_object_open_by_id_str(&obj, repo, start_commit);
 		if (error == NULL) {
-			id = got_object_get_id(obj);
-			if (id == NULL)
+			start_id = got_object_get_id(obj);
+			if (start_id == NULL)
 				error = got_error_from_errno();
 		}
 	}
 	if (error != NULL)
 		return error;
-	error = show_log_view(id, repo);
-	free(id);
+	error = show_log_view(start_id, repo);
+	free(start_id);
 	got_repo_close(repo);
 	return error;
 }