git repos / sago_web_git

commit d2d178ce

Poul Sander · 2026-06-19 12:43
d2d178cea97e3d1912849cb39f9d24332cce0d5d patch · browse files
parent 4884661bf7b1a182586a207ea9e977adeef641ae

Now also show all branches that has been checked out

Changed files

M src/application.cpp before
M src/application.h before
M src/content.h before
M src/git_repo.cpp before
M src/git_repo.h before
M templates/skin.tmpl before
diff --git a/src/application.cpp b/src/application.cpp index 8e39241..87478b2 100644 --- a/src/application.cpp +++ b/src/application.cpp
@@ -46,6 +46,7 @@ GitApp::GitApp(cppcms::service &srv, const RepoScanner *scanner)
dispatcher().assign("^/repos/([^/]+)/commit/([0-9a-f]+)$", &GitApp::commit, this, 1, 2);
dispatcher().assign("^/repos/([^/]+)/tags/?$", &GitApp::tags, this, 1);
dispatcher().assign("^/repos/([^/]+)/tags/(.+)$", &GitApp::tag, this, 1, 2);
+ dispatcher().assign("^/repos/([^/]+)/branches/?$", &GitApp::branches, this, 1);
dispatcher().assign("^/repos/([^/]+)/?$", &GitApp::repo_home, this, 1);
}
@@ -292,6 +293,21 @@ void GitApp::tag(std::string name, std::string tag_name) {
}
}
+void GitApp::branches(std::string name) {
+ std::unique_ptr<GitRepo> repo = open(name);
+ if (!repo)
+ return;
+ try {
+ content::branches_view c;
+ c.repo = name;
+ c.title = "branches " + name;
+ c.branches = repo->branches();
+ render(SKIN, "branches_view", c);
+ } catch (const GitError &e) {
+ show_error(404, e.what());
+ }
+}
+
void GitApp::commit(std::string name, std::string oid) {
std::unique_ptr<GitRepo> repo = open(name);
if (!repo)
@@ -301,6 +317,13 @@ void GitApp::commit(std::string name, std::string oid) {
c.repo = name;
c.title = "commit " + oid;
c.commit = repo->commit_detail(oid);
+ c.branches = repo->branches_for_commit(c.commit.oid);
+ for (const BranchInfo &b : c.branches) {
+ if (b.is_default) {
+ c.on_default_branch = true;
+ break;
+ }
+ }
render(SKIN, "commit_view", c);
} catch (const GitError &e) {
show_error(404, e.what());
diff --git a/src/application.h b/src/application.h index 497f873..2b66f1d 100644 --- a/src/application.h +++ b/src/application.h
@@ -29,6 +29,7 @@ private:
void object(std::string name, std::string oid);
void tags(std::string name);
void tag(std::string name, std::string tag_name);
+ void branches(std::string name);
void commit(std::string name, std::string oid);
void commit_patch(std::string name, std::string oid);
void static_file(std::string rel);
diff --git a/src/content.h b/src/content.h index 0fbdcf2..de68461 100644 --- a/src/content.h +++ b/src/content.h
@@ -78,8 +78,14 @@ struct tag_view : public master {
TagDetail tag;
};
+struct branches_view : public master {
+ std::vector<BranchInfo> branches;
+};
+
struct commit_view : public master {
CommitDetail commit;
+ std::vector<BranchInfo> branches; // local branches whose history contains the commit
+ bool on_default_branch = false; // when true, branch annotation is suppressed
};
} // namespace content
diff --git a/src/git_repo.cpp b/src/git_repo.cpp index 4230eeb..d4fca83 100644 --- a/src/git_repo.cpp +++ b/src/git_repo.cpp
@@ -47,6 +47,14 @@ git_commit *lookup_commit(git_repository *repo, const std::string &id) {
return reinterpret_cast<git_commit *>(obj);
}
+// Whether `target` is reachable from `tip` (i.e. the commit at `tip` contains
+// `target` in its history, or is that commit).
+bool commit_reaches(git_repository *repo, const git_oid *tip, const git_oid *target) {
+ if (git_oid_equal(tip, target))
+ return true;
+ return git_graph_descendant_of(repo, tip, target) == 1;
+}
+
git_commit *lookup_commit_by_oid(git_repository *repo, const std::string &oid_hex) {
git_oid oid;
check(git_oid_fromstr(&oid, oid_hex.c_str()), "parse oid");
@@ -497,6 +505,57 @@ TagDetail GitRepo::tag_detail(const std::string &name) const {
return detail;
}
+std::vector<BranchInfo> GitRepo::branches() const {
+ const std::string def = default_branch();
+ std::vector<BranchInfo> result;
+
+ git_branch_iterator *it = nullptr;
+ check(git_branch_iterator_new(&it, repo_, GIT_BRANCH_LOCAL), "iterate branches");
+
+ git_reference *ref = nullptr;
+ git_branch_t type;
+ while (git_branch_next(&ref, &type, it) == 0) {
+ BranchInfo branch;
+ const char *name = nullptr;
+ if (git_branch_name(&name, ref) == 0 && name)
+ branch.name = name;
+ branch.is_default = branch.name == def;
+
+ git_object *commit = nullptr;
+ if (git_reference_peel(&commit, ref, GIT_OBJECT_COMMIT) == 0) {
+ branch.target_oid = oid_to_string(git_object_id(commit));
+ branch.short_oid = branch.target_oid.substr(0, SHORT_OID_LEN);
+ const git_signature *author =
+ git_commit_author(reinterpret_cast<git_commit *>(commit));
+ if (author)
+ branch.time = author->when.time;
+ git_object_free(commit);
+ }
+ result.push_back(std::move(branch));
+ git_reference_free(ref);
+ }
+ git_branch_iterator_free(it);
+
+ std::sort(result.begin(), result.end(),
+ [](const BranchInfo &a, const BranchInfo &b) { return a.name < b.name; });
+ return result;
+}
+
+std::vector<BranchInfo> GitRepo::branches_for_commit(const std::string &commit_oid) const {
+ git_oid target;
+ check(git_oid_fromstr(&target, commit_oid.c_str()), "parse oid");
+
+ std::vector<BranchInfo> result;
+ for (const BranchInfo &b : branches()) {
+ git_oid tip;
+ if (git_oid_fromstr(&tip, b.target_oid.c_str()) != 0)
+ continue;
+ if (commit_reaches(repo_, &tip, &target))
+ result.push_back(b);
+ }
+ return result;
+}
+
CommitDetail GitRepo::commit_detail(const std::string &oid) const {
git_commit *commit = lookup_commit(repo_, oid);
diff --git a/src/git_repo.h b/src/git_repo.h index 42e30be..0288d86 100644 --- a/src/git_repo.h +++ b/src/git_repo.h
@@ -49,6 +49,14 @@ struct TagInfo {
bool annotated = false;
};
+struct BranchInfo {
+ std::string name;
+ std::string target_oid; // commit the branch points at
+ std::string short_oid;
+ std::time_t time = 0;
+ bool is_default = false; // true for the repository's HEAD branch
+};
+
struct TagDetail {
std::string name;
std::string target_oid;
@@ -143,6 +151,12 @@ public:
// Details of a single tag by short name (e.g. "v1.0").
TagDetail tag_detail(const std::string &name) const;
+ // All local branches, sorted by name.
+ std::vector<BranchInfo> branches() const;
+
+ // Local branches whose history contains `commit_oid`, sorted by name.
+ std::vector<BranchInfo> branches_for_commit(const std::string &commit_oid) const;
+
// Commit metadata plus the diff against its first parent.
CommitDetail commit_detail(const std::string &oid) const;
diff --git a/templates/skin.tmpl b/templates/skin.tmpl index 6ecc5e5..b590bfa 100644 --- a/templates/skin.tmpl +++ b/templates/skin.tmpl
@@ -22,6 +22,7 @@
<nav class="repo-nav">
<a href="/repos/<%= repo %>/tree/<%= query %>">files</a>
<a href="/repos/<%= repo %>/log/<%= query %>">log</a>
+<a href="/repos/<%= repo %>/branches/">branches</a>
<a href="/repos/<%= repo %>/tags/">tags</a>
</nav>
<% end %>
@@ -229,14 +230,41 @@ points to <a href="/repos/<%= repo %>/commit/<%= tag.target_oid %>"><%= tag.shor
<% end template %>
<% end view %>
+<% view branches_view uses content::branches_view extends master %>
+<% template content_body() %>
+<h1>branches: <%= repo %></h1>
+<table class="log">
+<% foreach b in branches %><% item %>
+<tr>
+<td class="commit"><a href="/repos/<%= repo %>/commit/<%= b.target_oid %>"><%= b.short_oid %></a></td>
+<td class="msg"><%= b.name %><% if b.is_default %> (default)<% end %></td>
+<td class="msg"><a href="/repos/<%= repo %>/log/?id=<%= b.target_oid %>">log</a></td>
+<td class="msg"><a href="/repos/<%= repo %>/tree/?id=<%= b.target_oid %>">browse files</a></td>
+<td class="date"><% c++ out() << util::format_time(b.time); %></td>
+</tr>
+<% end item %><% empty %>
+<tr><td>no branches</td></tr>
+<% end %>
+</table>
+<% end template %>
+<% end view %>
+
<% view commit_view uses content::commit_view extends master %>
<% template content_body() %>
<h1>commit <%= commit.short_oid %></h1>
+<% if (not content.on_default_branch) %>
+<% if (content.branches.size() > 0) %>
+<p class="tags">branches: <% foreach b in branches %><% item %><a href="/repos/<%= repo %>/tree/?id=<%= b.target_oid %>"><%= b.name %></a> <% end item %><% end foreach %></p>
+<% else %>
+<p class="tags">not on any branch</p>
+<% end %>
+<% end %>
<p class="meta">
<%= commit.author_name %><% if (content.commit.author_email.length() > 0) %> &lt;<%= commit.author_email %>&gt;<% end %> &middot;
<% c++ out() << util::format_time(content.commit.time); %><br>
<%= commit.oid %>
<a href="/repos/<%= repo %>/commit/<%= commit.oid %>.patch">patch</a>
+&middot; <a href="/repos/<%= repo %>/tree/?id=<%= commit.oid %>">browse files</a>
<% foreach p in commit.parents %><% item %><br>parent <a href="/repos/<%= repo %>/commit/<%= p %>"><%= p %></a><% end item %><% end foreach %>
</p>
<pre class="code"><code><%= commit.message %></code></pre>