|
| 1 | +local lazy = require("diffview.lazy") |
| 2 | +local oop = require('diffview.oop') |
| 3 | +local Commit = require('diffview.vcs.commit').Commit |
| 4 | +local RevType = require('diffview.vcs.rev').RevType |
| 5 | +local utils = require('diffview.utils') |
| 6 | + |
| 7 | +local M = {} |
| 8 | + |
| 9 | +---@class P4Commit : Commit |
| 10 | +local P4Commit = oop.create_class('P4Commit', Commit) |
| 11 | + |
| 12 | +---P4Commit constructor |
| 13 | +---@param opt table |
| 14 | +function P4Commit:init(opt) |
| 15 | + self:super(opt) -- Calls base Commit init |
| 16 | + |
| 17 | + -- Perforce describe output gives Unix timestamp directly. |
| 18 | + -- Timezone handling might require parsing the date string or relying on system locale. |
| 19 | + -- Let's use the raw timestamp and format it simply for now. |
| 20 | + if self.time then |
| 21 | + self.iso_date = os.date("%Y-%m-%d %H:%M:%S", self.time) -- Simple local time formatting |
| 22 | + self.rel_date = utils.relative_time(self.time) -- Use existing utility if possible |
| 23 | + end |
| 24 | + self.hash = opt.changelist -- Store CL number as 'hash' for consistency |
| 25 | +end |
| 26 | + |
| 27 | +-- Helper to parse p4 describe output |
| 28 | +local function parse_describe_output(output_lines) |
| 29 | + local data = { files = {} } |
| 30 | + local reading_files = false |
| 31 | + local reading_diff = false |
| 32 | + local current_file_diff = {} |
| 33 | + |
| 34 | + for i, line in ipairs(output_lines) do |
| 35 | + if not reading_files and not reading_diff then |
| 36 | + local cl, date_str, user_at_client, desc_start = line:match("^Change (%d+) on ([^%s]+) by ([^@]+@[^%s]+) (.*)") |
| 37 | + if cl then |
| 38 | + data.changelist = cl |
| 39 | + -- Attempt to parse date - p4 dates can be yyyy/mm/dd:hh:mm:ss |
| 40 | + -- Let's stick to the Unix timestamp provided later for accuracy |
| 41 | + data.user = user_at_client:match("^(.*)@") |
| 42 | + data.client = user_at_client:match("@(.*)$") |
| 43 | + data.description = desc_start .. "\n" |
| 44 | + elseif line:match("^Description:") then |
| 45 | + -- Ignore, handled above or description continues |
| 46 | + elseif line:match("^Affected files ...") or line:match("^Differences ...") then |
| 47 | + reading_files = true |
| 48 | + else -- Continue capturing multi-line description |
| 49 | + if data.description and line:match("^ ") then -- Indented lines are part of desc |
| 50 | + data.description = data.description .. line:sub(2) .. "\n" |
| 51 | + end |
| 52 | + end |
| 53 | + elseif reading_files then |
| 54 | + local path, rev, action = line:match("^... (%S+)#(%d+) (%S+)") |
| 55 | + if path then |
| 56 | + table.insert(data.files, { path = path, rev = rev, action = action }) |
| 57 | + elseif line:match("^Differences ...$") then |
| 58 | + reading_files = false |
| 59 | + reading_diff = true |
| 60 | + elseif line == "" then -- End of file list section |
| 61 | + reading_files = false |
| 62 | + end |
| 63 | + -- elseif reading_diff then |
| 64 | + -- Diff parsing is handled separately if needed, `p4 describe -du` gives unified diff |
| 65 | + end |
| 66 | + -- Look for the timestamp which is usually near the end |
| 67 | + local time_unix = line:match("^\*edit @ (%d+)") or line:match("^\*add @ (%d+)") or line:match("^\*delete @ (%d+)") or line:match("^\*branch @ (%d+)") or line:match("^\*integrate @ (%d+)") |
| 68 | + if time_unix then |
| 69 | + data.time = tonumber(time_unix) |
| 70 | + end |
| 71 | + end |
| 72 | + -- Clean up description whitespace |
| 73 | + if data.description then |
| 74 | + data.description = vim.trim(data.description) |
| 75 | + end |
| 76 | + |
| 77 | + -- Simple relative date calculation based on timestamp |
| 78 | + if data.time then |
| 79 | + data.rel_date = utils.relative_time(data.time) |
| 80 | + else |
| 81 | + -- Fallback if timestamp wasn't found (unlikely for submitted CLs) |
| 82 | + data.rel_date = "unknown" |
| 83 | + end |
| 84 | + |
| 85 | + return data |
| 86 | +end |
| 87 | + |
| 88 | + |
| 89 | +---@param rev_arg string # Changelist number or specifier like @CL |
| 90 | +---@param adapter P4Adapter |
| 91 | +---@return P4Commit? |
| 92 | +function P4Commit.from_rev_arg(rev_arg, adapter) |
| 93 | + local cl_num = rev_arg:match("^@(%d+)$") or rev_arg:match("^(%d+)$") |
| 94 | + if not cl_num then |
| 95 | + -- Handle other revspecs? For now, only support CL numbers for commit details. |
| 96 | + -- Could potentially support labels or #head, but describe works best with CLs. |
| 97 | + -- Maybe run 'p4 changes -m1 rev_arg' first to resolve to a CL number. |
| 98 | + local resolve_out, resolve_code = adapter:exec_sync({ "changes", "-m1", rev_arg }, adapter.ctx.toplevel) |
| 99 | + if resolve_code ~= 0 or #resolve_out == 0 then return nil end |
| 100 | + cl_num = resolve_out[1]:match("^Change (%d+)") |
| 101 | + if not cl_num then return nil end |
| 102 | + end |
| 103 | + |
| 104 | + -- Fetch changelist details using p4 describe |
| 105 | + local out, code = adapter:exec_sync({ "describe", cl_num }, adapter.ctx.toplevel) |
| 106 | + |
| 107 | + if code ~= 0 or #out == 0 then |
| 108 | + return nil |
| 109 | + end |
| 110 | + |
| 111 | + local data = parse_describe_output(out) |
| 112 | + if not data.changelist then return nil end |
| 113 | + |
| 114 | + return P4Commit({ |
| 115 | + changelist = data.changelist, -- Store the actual CL number |
| 116 | + hash = data.changelist, -- Use CL number as 'hash' for internal consistency |
| 117 | + author = data.user, |
| 118 | + time = data.time, -- Unix timestamp |
| 119 | + subject = data.description:match("^[^\n]*") or ("Changelist " .. data.changelist), -- First line of description |
| 120 | + body = data.description, |
| 121 | + rel_date = data.rel_date, -- Calculated relative date |
| 122 | + -- Perforce doesn't have direct ref names like git branches/tags in describe output |
| 123 | + }) |
| 124 | +end |
| 125 | + |
| 126 | +M.P4Commit = P4Commit |
| 127 | +M.parse_describe_output = parse_describe_output |
| 128 | +return M |
0 commit comments