authorgravatar for hello@nektro.netMeghan Denny <hello@nektro.net> 2025-06-25 12:56:20 -07:00
committergravatar for hello@nektro.netMeghan Denny <hello@nektro.net> 2025-06-25 12:56:20 -07:00
loge3fb67f40f18132e1553024aaa8efaa306f2cb13
treef823f6d441ac6782408d63ca2052b8544d32b44e
parent3835107c3034398b00c58b6341d017a4893b9126

add blame getter and parser


1 files changed, 106 insertions(+), 0 deletions(-)

git.zig+106
...@@ -769,3 +769,109 @@ pub const Ref = struct {...@@ -769,3 +769,109 @@ pub const Ref = struct {
769 commit: CommitId,769 commit: CommitId,
770 tag: ?TagId,770 tag: ?TagId,
771};771};
772
773// TODO make this inspect .git/objects
774pub fn getBlame(alloc: std.mem.Allocator, dir: std.fs.Dir, at: CommitId, sub_path: string) !string {
775 const t = tracer.trace(@src(), " {s} -- {s}", .{ at.id, sub_path });
776 defer t.end();
777
778 const result = try std.process.Child.run(.{
779 .allocator = alloc,
780 .cwd_dir = dir,
781 .argv = &.{ "git", "blame", "-p", at.id, "--", sub_path },
782 .max_output_bytes = 1024 * 1024 * 1024,
783 });
784 extras.assertLog(result.term == .Exited and result.term.Exited == 0, "{s}", .{result.stderr});
785 return result.stdout;
786}
787
788pub const BlameIterator = struct {
789 inner: std.mem.SplitIterator(u8, .scalar),
790
791 pub fn init(blamefile: []const u8) BlameIterator {
792 return .{
793 .inner = std.mem.splitScalar(u8, blamefile, '\n'),
794 };
795 }
796
797 /// when line.continuity is >0 then .commit will be the same for the next continuity-1 iterations
798 pub fn next(self: *BlameIterator) ?Line {
799 var result: Line = .{
800 .commit = undefined,
801 .prev_line = 0,
802 .curr_line = 0,
803 .continuity = 0,
804 .author = .{ .name = "", .email = "", .at = .initUnix(0) },
805 .committer = .{ .name = "", .email = "", .at = .initUnix(0) },
806 .summary = "",
807 .previous = null,
808 .filename = "",
809 .line = "",
810 };
811 blk: {
812 var it = std.mem.splitScalar(u8, self.inner.next() orelse return null, ' ');
813 result.commit = ensureObjId(CommitId, extras.nullifyS(it.next().?) orelse return null);
814 result.prev_line = std.fmt.parseInt(u32, it.next().?, 10) catch unreachable;
815 result.curr_line = std.fmt.parseInt(u32, it.next().?, 10) catch unreachable;
816 result.continuity = std.fmt.parseInt(u32, it.next() orelse break :blk, 10) catch unreachable;
817 }
818 while (self.inner.next()) |line| {
819 if (line[0] == '\t') {
820 result.line = line[1..];
821 break;
822 }
823 if (extras.trimPrefixEnsure(line, "author ")) |trimmed| {
824 result.author = .{
825 .name = trimmed,
826 .email = extras.trimPrefixEnsure(self.inner.next().?, "author-mail ").?,
827 .at = parseAt(
828 extras.trimPrefixEnsure(self.inner.next().?, "author-time ").?,
829 extras.trimPrefixEnsure(self.inner.next().?, "author-tz ").?,
830 ),
831 };
832 continue;
833 }
834 if (extras.trimPrefixEnsure(line, "committer ")) |trimmed| {
835 result.committer = .{
836 .name = trimmed,
837 .email = extras.trimPrefixEnsure(self.inner.next().?, "committer-mail ").?,
838 .at = parseAt(
839 extras.trimPrefixEnsure(self.inner.next().?, "committer-time ").?,
840 extras.trimPrefixEnsure(self.inner.next().?, "committer-tz ").?,
841 ),
842 };
843 continue;
844 }
845 if (extras.trimPrefixEnsure(line, "summary ")) |trimmed| {
846 result.summary = trimmed;
847 continue;
848 }
849 if (extras.trimPrefixEnsure(line, "previous ")) |trimmed| {
850 var it = std.mem.splitScalar(u8, trimmed, ' ');
851 result.previous = .{
852 ensureObjId(CommitId, it.next().?),
853 it.next().?,
854 };
855 continue;
856 }
857 if (extras.trimPrefixEnsure(line, "filename ")) |trimmed| {
858 result.filename = trimmed;
859 continue;
860 }
861 }
862 return result;
863 }
864
865 pub const Line = struct {
866 commit: CommitId,
867 prev_line: u32,
868 curr_line: u32,
869 continuity: u32,
870 author: UserAndAt,
871 committer: UserAndAt,
872 summary: string,
873 previous: ?struct { CommitId, string },
874 filename: string,
875 line: string,
876 };
877};