package.path = Helix.Core.Server.GetArchDirFileName( "?.lua" ) local inspect = require( "inspect" ) local utils = require "ExtUtils" local initDone = false local p4 function init() if not initDone then utils.init() end end function GlobalConfigFields() return {} end function InstanceConfigFields() -- todo: allow more than one path return { ["path"] = "//depot/path/to/check/...", ["excludeUser"] = "User that can bypass the check, " .. "e.g. git-fusion-user" } end function InstanceConfigEvents() init() local path = utils.iCfgData[ "path" ] return { ["change-submit"] = path, ["change-content"] = path } end function SplitPath( path ) local i = 1 local ps = {} for p in string.gmatch( path, "([^/]*)" ) do -- Skip the leading // in //depot. if string.len( p ) > 0 then ps[ i ] = p i = i + 1 end end return ps end function SplitFileDir( path ) local _, _, file = string.find( path, "([^/]*)$" ) local dir = string.gsub( path, "([^/]*)$", "" ) return dir, file end function checkFile( depots, df ) local ps = SplitPath( df ) if not depots[ ps[ 1 ] ] then -- Depot doesn't exist. -- todo: add string to the translations return false, df, "no such depot" end local cidftab = p4:run( "files", "-e", "-i", df ) if #cidftab == 0 then -- No exact file match. local d, f = SplitFileDir( df ) local x = p4:run( "files", "-e", "-i", "-m1", d .. "*" ) if #x == 0 then -- No files in the dir, dir doesn't exist, return true end local xf = x[ 1 ][ "depotFile" ] local de, dff = SplitFileDir( xf ) if de ~= d then -- Path mismatch return false, df, de end return true end local cidf = cidftab[ 1 ][ "depotFile" ] if df ~= cidf then return false, df, cidf end return true end function checkDepot( fs ) local depots = {} local depots = p4:run( "depots" ) for i, depot in ipairs( depots ) do depots[ depot[ "name" ] ] = true end for i, df in ipairs( fs ) do local r, err, ef = checkFile( depots, df ) if not r then return r, err, ef end end return true end function get_files_cc( change ) local fs_all = p4:run( "files", "@=" .. change ) if #fs_all == 0 then return {} end local fs = {} for i, dict in ipairs( fs_all ) do fs[ i ] = dict[ "depotFile" ] end return fs end function get_files( change ) -- We'd like to be able to use an fstat like this, but it's not available until a change-content trigger. -- p4:run( "fstat", "-Ro", "-T", "depotFile,action", "-F", "^headAction=delete", "-e", change, "//..." ) -- todo: restrict '//...' by the mapping? local all_fs = p4:run( "describe", "-s", change ) all_fs = all_fs[ 1 ] local fs = {} local n = 1 for i, action in ipairs( all_fs[ "action" ] ) do if action == "branch" or action == "add" then fs[ n ] = all_fs[ "depotFile" ][ i ] n = n + 1 end end return fs end function run() init() if user == utils.iCfgData[ "excludeUser" ] then return true end local change = Helix.Core.Server.GetVar( "change" ) local cmd = Helix.Core.Server.GetVar( "command" ) local fs p4 = P4.P4.new() p4:autoconnect() p4.exception_level = 0 p4:connect() if cmd == "user-submit" then fs = get_files( change ) end if cmd == "user-populate" then fs = get_files_cc( change ) end local r, err, ef = checkDepot( fs ) if not r then local msg = Helix.Core.Server.i18n.GetMessage( "errorMessage", err, ef ) Helix.Core.Server.SetClientMsg( msg ) end p4:disconnect() return r end function ChangeContent() -- The 'p4 populate' command does not fire the change-submit event. if Helix.Core.Server.GetVar( "command" ) ~= "user-populate" then return true end return run() end function ChangeSubmit() return run() end