srctree

Gregory Mullen parent d750f4e7 900daee3
enable repo list sorting based on tag

inlinesplit
src/endpoints/repos.zig added: 94, removed: 15, total 79
@@ -3,6 +3,7 @@ const std = @import("std");
const Allocator = std.mem.Allocator;
const aPrint = std.fmt.allocPrint;
const bPrint = std.fmt.bufPrint;
const eql = std.mem.eql;
 
const Context = @import("../context.zig");
const Response = @import("../response.zig");
@@ -17,6 +18,7 @@ const UriIter = Route.UriIter;
const ROUTE = Route.ROUTE;
const POST = Route.POST;
const GET = Route.GET;
const UserData = @import("../request_data.zig").UserData;
 
const Bleach = @import("../bleach.zig");
const Humanize = @import("../humanize.zig");
@@ -173,20 +175,47 @@ fn typeSorter(_: void, l: Git.Blob, r: Git.Blob) bool {
 
const repoctx = struct {
alloc: Allocator,
by: enum {
commit,
tag,
} = .commit,
};
 
fn tagSorter(_: void, l: Git.Tag, r: Git.Tag) bool {
return l.tagger.timestamp >= r.tagger.timestamp;
}
 
fn repoSorterNew(ctx: repoctx, l: Git.Repo, r: Git.Repo) bool {
return !repoSorter(ctx, l, r);
}
 
fn repoSorter(ctx: repoctx, l: Git.Repo, r: Git.Repo) bool {
var lc = l.headCommit(ctx.alloc) catch return true;
fn commitSorter(a: Allocator, l: Git.Repo, r: Git.Repo) bool {
var lc = l.headCommit(a) catch return true;
defer lc.raze();
var rc = r.headCommit(ctx.alloc) catch return false;
var rc = r.headCommit(a) catch return false;
defer rc.raze();
return sorter({}, lc.committer.timestr, rc.committer.timestr);
}
 
fn repoSorter(ctx: repoctx, l: Git.Repo, r: Git.Repo) bool {
switch (ctx.by) {
.commit => {
return commitSorter(ctx.alloc, l, r);
},
.tag => {
if (l.tags) |lt| {
if (r.tags) |rt| {
if (lt.len == 0) return true;
if (rt.len == 0) return false;
if (lt[0].tagger.timestamp == rt[0].tagger.timestamp)
return commitSorter(ctx.alloc, l, r);
return lt[0].tagger.timestamp > rt[0].tagger.timestamp;
} else return false;
} else return true;
},
}
}
 
fn sorter(_: void, l: []const u8, r: []const u8) bool {
return std.mem.lessThan(u8, l, r);
}
@@ -222,6 +251,10 @@ fn repoBlock(a: Allocator, name: []const u8, repo: Git.Repo) !Template.Structs.R
 
const ReposPage = Template.PageData("repos.html");
 
const RepoSortReq = struct {
sort: ?[]const u8,
};
 
fn list(ctx: *Context) Error!void {
var cwd = std.fs.cwd();
if (cwd.openDir("./repos", .{ .iterate = true })) |idir| {
@@ -234,9 +267,18 @@ fn list(ctx: *Context) Error!void {
var rpo = Git.Repo.init(rdir) catch continue;
rpo.loadData(ctx.alloc) catch return error.Unknown;
rpo.repo_name = ctx.alloc.dupe(u8, file.name) catch null;
rpo.loadTags(ctx.alloc) catch return error.Unknown;
if (rpo.tags != null) {
std.sort.heap(Git.Tag, rpo.tags.?, {}, tagSorter);
}
try repos.append(rpo);
}
std.sort.heap(Git.Repo, repos.items, repoctx{ .alloc = ctx.alloc }, repoSorterNew);
 
const udata = UserData(RepoSortReq).init(ctx.req_data.query_data) catch return error.BadData;
std.sort.heap(Git.Repo, repos.items, repoctx{
.alloc = ctx.alloc,
.by = if (udata.sort) |srt| if (eql(u8, srt, "tag")) .tag else .commit else .commit,
}, repoSorterNew);
 
var repo_buttons: []const u8 = "";
if (ctx.request.auth.valid()) {
@@ -788,6 +830,7 @@ fn tags(ctx: *Context) Error!void {
defer repo.raze(ctx.alloc);
 
repo.loadTags(ctx.alloc) catch unreachable;
std.sort.heap(Git.Tag, repo.tags.?, {}, tagSorter);
 
const tstack = try ctx.alloc.alloc(Template.Structs.Tags, repo.tags.?.len);
 
 
src/git.zig added: 94, removed: 15, total 79
@@ -387,16 +387,19 @@ pub const Repo = struct {
for (&sha, 0..) |*s, i| {
s.* = try std.fmt.parseInt(u8, lsha[i * 2 .. (i + 1) * 2], 16);
}
}
} else if (lsha.len != 20) return error.InvalidSha;
const tag_blob = try self.findBlob(a, sha[0..]);
return try Tag.fromSlice(lsha, tag_blob);
}
 
pub fn loadTags(self: *Repo, a: Allocator) !void {
var tagdir = try self.dir.openDir("refs/tags", .{ .iterate = true });
const pk_refs: ?[]const u8 = self.dir.readFileAlloc(a, "packed-refs", 0xffff) catch |err| pk: {
std.debug.print("packed-refs {any}\n", .{err});
break :pk null;
const pk_refs: ?[]const u8 = self.dir.readFileAlloc(a, "packed-refs", 0xffff) catch |err| switch (err) {
error.FileNotFound => null,
else => {
std.debug.print("packed-refs {any}\n", .{err});
unreachable;
},
};
defer if (pk_refs) |pr| a.free(pr);
 
@@ -404,14 +407,13 @@ pub const Repo = struct {
var itr = tagdir.iterate();
var count: usize = if (pk_refs) |p| std.mem.count(u8, p, "refs/tags/") else 0;
while (try itr.next()) |next| {
std.debug.print("next {s} {any}\n", .{ next.name, next.kind });
if (next.kind != .file) continue;
count += 1;
}
if (count == 0) return;
self.tags = try a.alloc(Tag, count);
errdefer a.free(self.tags.?);
errdefer self.tags = null;
errdefer a.free(self.tags.?);
 
var index: usize = 0;
if (pk_refs) |pkrefs| {
@@ -434,11 +436,11 @@ pub const Repo = struct {
std.debug.print("unexpected tag format for {s}\n", .{fname});
return err;
};
if (contents.len != 40) {
if (contents.len != 41) {
std.debug.print("unexpected tag format for {s}\n", .{fname});
return error.InvalidTagFound;
}
self.tags.?[index] = try self.loadTag(a, contents);
self.tags.?[index] = try self.loadTag(a, contents[0..40]);
index += 1;
}
if (index != self.tags.?.len) return error.UnexpectedError;
@@ -555,6 +557,7 @@ pub const Branch = struct {
 
pub const TagType = enum {
commit,
lightweight,
 
pub fn fromSlice(str: []const u8) ?TagType {
inline for (std.meta.tags(TagType)) |t| {
@@ -574,7 +577,40 @@ pub const Tag = struct {
signature: ?[]const u8,
//signature: ?Commit.GPGSig,
 
pub fn fromSlice(sha: SHA, blob: []const u8) !Tag {
pub fn fromSlice(sha: SHA, bblob: []const u8) !Tag {
// sometimes, the slice will have a preamble
var blob = bblob;
if (indexOf(u8, bblob[0..20], "\x00")) |i| {
std.debug.assert(startsWith(u8, bblob, "tag "));
blob = bblob[i + 1 ..];
}
//std.debug.print("tag\n{s}\n{s}\n", .{ sha, bblob });
if (startsWith(u8, blob, "tree ")) return try lightTag(sha, blob);
return try fullTag(sha, blob);
}
 
/// I don't like this implementation, but I can't be arsed... good luck
/// future me!
fn lightTag(sha: SHA, blob: []const u8) !Tag {
var actor: ?Actor = null;
if (indexOf(u8, blob, "committer ")) |i| {
var act = blob[i + 10 ..];
if (indexOf(u8, act, "\n")) |end| act = act[0..end];
actor = Actor.make(act) catch return error.InvalidActor;
} else return error.InvalidTag;
 
return .{
.name = "[lightweight tag]",
.sha = sha,
.object = sha,
.type = .lightweight,
.tagger = actor orelse unreachable,
.message = "",
.signature = null,
};
}
 
fn fullTag(sha: SHA, blob: []const u8) !Tag {
var name: ?[]const u8 = null;
var object: ?[]const u8 = null;
var ttype: ?TagType = null;