╭──────────╮ │← previous│ ╰──────────╯ ╭ changed ──╮ │src/try.zig│ ╰───────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────╮ │651 }, .{ │ │652 .id = issue_events[issue_data.len - 2].id, │ │653 .timestamp = 105, │ │656 - .author_email = user_data[2].email, │ │654 + .author = .{ .name = user_data[2].name, .email = user_data[2].email },│ │655 .event = .{ .issue = .{ │ │656 .title = desc_issue.title, │ │657 .description = tz_p1 ++ "\n\n" ++ tz_p2 ++ "\n\n" ++ │ ╰───────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │669 for (theirs, 0..) |event, i| { │ │670 json.clearRetainingCapacity(); │ │671 try std.json.Stringify.value(event, .{}, &json.writer); │ │674 - const author = try std.fmt.allocPrint(allocator, "haxy <{s}>", .{event.author_email}); │ │672 + const author = try std.fmt.allocPrint(allocator, "{s} <{s}>", .{ event.author.name, event.author.email });│ │673 defer allocator.free(author); │ │674 _ = try template_repo.commitAtRef(io, allocator, .{ │ │675 .author = author, │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │715 event.* = .{ │ │716 .id = std.fmt.bytesToHex(comment_ids[i], .lower), │ │717 .timestamp = @intCast(200 + i), │ │720 - .author_email = user_data[(i + 1) % user_data.len].email, │ │718 + .author = .{ .name = user_data[(i + 1) % user_data.len].name, .email = user_data[(i + 1) % user_data.len].email },│ │719 .event = .{ .comment = .{ │ │720 .thread_id = issue_events[issue_events.len - 1].id, │ │721 .parent_id = switch (i) { │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ─╮ │src/ui.zig│ ╰──────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │ 962 pub const RepoSource = struct { │ │ 963 path: []const u8, │ │ 964 repo_kind: rp.RepoKind, │ │ 965 + // the user's global git config, so local mode picks up their identity and │ │ 966 + // their signing and ssh settings. null on the server paths, which must not │ │ 967 + // read whoever runs the server. │ │ 968 + global_config_path: ?[]const u8 = null, │ │ 969 + │ │ 970 + pub fn localInitOpts(self: RepoSource) rp.InitOpts { │ │ 971 + return .{ .path = self.path, .global_config_path = self.global_config_path }; │ │ 972 + } │ │ 973 }; │ │ 974 + │ │ 975 +// events created in local mode have no account behind them, so the commit │ │ 976 +// author comes from the repo's config, which the global config feeds into. │ │ 977 +pub const local_author_fallback = evt.CommitAuthor{ .name = "haxy", .email = "user@haxy" }; │ │ 978 + │ │ 979 +pub fn localAuthor( │ │ 980 + src: RepoSource, │ │ 981 + io: std.Io, │ │ 982 + allocator: std.mem.Allocator, │ │ 983 + arena: *std.heap.ArenaAllocator, │ │ 984 +) !evt.CommitAuthor { │ │ 985 + switch (src.repo_kind) { │ │ 986 + inline else => |repo_kind| { │ │ 987 + var any_repo = rp.AnyRepo(repo_kind, .{}).open(io, allocator, src.localInitOpts()) catch return local_author_fallback;│ │ 988 + defer any_repo.deinit(io, allocator); │ │ 989 │ │ 990 + switch (any_repo) { │ │ 991 + inline else => |*repo| { │ │ 992 + var config = try repo.listConfig(io, allocator); │ │ 993 + defer config.deinit(); │ │ 994 + │ │ 995 + const user = config.sections.get("user") orelse return local_author_fallback; │ │ 996 + const name = user.get("name") orelse return local_author_fallback; │ │ 997 + const email = user.get("email") orelse return local_author_fallback; │ │ 998 + return .{ │ │ 999 + .name = try arena.allocator().dupe(u8, name), │ │1000 + .email = try arena.allocator().dupe(u8, email), │ │1001 + }; │ │1002 + }, │ │1003 + } │ │1004 + }, │ │1005 + } │ │1006 +} │ │1007 + │ │1008 // per-connection mutable state. each SSH session / web session / local TUI │ │1009 // run gets its own. │ │1010 pub const Session = struct { │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1118 } │ │1119 } │ │1120 │ │1080 - // the author for an event this session creates, or null when it may not │ │1081 - // create one. local mode has no accounts, so it authors anonymously. │ │1082 - pub fn eventAuthorEmail(self: *Self) !?[]const u8 { │ │1083 - if (self.data.is_local) return "user@haxy"; // TODO: try the repo's git config user.email first │ │1121 + // the commit author for an event this session creates, or null when it may │ │1122 + // not create one │ │1123 + pub fn eventAuthor(self: *Self) !?evt.CommitAuthor { │ │1124 + if (self.data.is_local) { │ │1125 + const src = self.local orelse return local_author_fallback; │ │1126 + const io = self.io orelse return local_author_fallback; │ │1127 + return try localAuthor(src, io, self.page_arena.child_allocator, self.page_arena); │ │1128 + } │ │1129 const user_id = self.data.user_id orelse return null; │ │1130 const moment = self.haxy_moment orelse return null; │ │1131 const user = (try evt.User.readById(evt.AdminDB, evt.admin_repo_opts.hash, moment, self.page_arena, user_id)) orelse return null;│ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────╮ │1087 - return user.event.email; │ │1132 + return .{ .name = user.event.name, .email = user.event.email };│ │1133 } │ │1134 │ │1135 // queue an action for the host to drain this frame. │ ╰─────────────────────────────────────────────────────────────────────────────╯ ╭ changed ────────────╮ │src/ui/Home/Users.zig│ ╰─────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │ 94 // clicking a user opens their page; the "a:" prefix makes the web │ │ 95 // renderer emit an <a href="/user/foo"> anchor. │ │ 96 try items.append(aa, .{ │ │ 97 - .text = try std.fmt.allocPrint(aa, "{s} ({s})", .{ user.name, user.display_name }),│ │ 97 + .text = try aa.dupe(u8, user.name), │ │ 98 .link = try std.fmt.allocPrint(aa, "a:/user/{s}", .{user.name}), │ │ 99 }); │ │100 if (data.next_start) |next_start| │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ──────╮ │src/ui/Repo.zig│ ╰───────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │184 const gpa = arena.child_allocator; │ │185 switch (src.repo_kind) { │ │186 inline else => |repo_kind| { │ │187 - var any_repo = rp.AnyRepo(repo_kind, .{}).open(io, gpa, .{ .path = src.path }) catch break :read;│ │187 + var any_repo = rp.AnyRepo(repo_kind, .{}).open(io, gpa, src.localInitOpts()) catch break :read; │ │188 defer any_repo.deinit(io, gpa); │ │189 switch (any_repo) { │ │190 inline else => |*opened| { │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ─────────────╮ │src/ui/Repo/Events.zig│ ╰──────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │645 │ │646 switch (source.repo_kind) { │ │647 inline else => |repo_kind| { │ │648 - var any_repo = try rp.AnyRepo(repo_kind, .{ .ProgressCtx = Header.Progress }).open(io, allocator, .{ .path = source.path });│ │648 + var any_repo = try rp.AnyRepo(repo_kind, .{ .ProgressCtx = Header.Progress }).open(io, allocator, source.localInitOpts()); │ │649 defer any_repo.deinit(io, allocator); │ │650 switch (any_repo) { │ │651 inline else => |*repo| { │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ─────────────╮ │src/ui/Repo/Issues.zig│ ╰──────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────╮ │2147 const io = self.session.io orelse return; │ │2148 const src = self.data.repo_source orelse return; │ │2149 const entry = self.data.selectedIssue() orelse return; │ │2150 - const author_email = (try self.session.eventAuthorEmail()) orelse return;│ │2150 + const author = (try self.session.eventAuthor()) orelse return; │ │2151 const form = self.issueForm() orelse return; │ │2152 │ │2153 // gather the inputs by name; the d<n> hunk inputs appear in chunk order │ ╰───────────────────────────────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯