2279 lines
64 KiB
Plaintext
Executable File
2279 lines
64 KiB
Plaintext
Executable File
--
|
|
-- File:: pipeline/util/p4.ms
|
|
-- Description:: Perforce bindings for MaxScript.
|
|
--
|
|
-- Author:: David Muir <david.muir@rockstarnorth.com>
|
|
-- Date:: 21 June 2010
|
|
--
|
|
-- NOTE: please put higher-levels functions into 'p4_utils.ms'; the idea was
|
|
-- that this was a thin wrapper around the P4API.
|
|
--
|
|
|
|
-- This is set at end of script:
|
|
global gRsPerforce
|
|
|
|
-- Rollout used to request passwords for Perforce:
|
|
try (destroyDialog RsP4PasswordDialog) catch ()
|
|
rollout RsP4PasswordDialog "Perforce Connect" width:300
|
|
(
|
|
local standAlone = true
|
|
local ctrlPositions
|
|
|
|
local textWidth = 280
|
|
label messageLabel "" align:#left width:textWidth height:300
|
|
listbox serverList "Perforce Server Setups:" align:#left height:1 selection:0 width:273 pos:(messageLabel.pos + [-6,10])
|
|
|
|
label pwLabel "Password:" align:#left pos:(serverList.pos + [0,12])
|
|
dotNetControl txtPassword "System.Windows.Forms.TextBox" width:150 pos:(pwLabel.pos + [(GetTextExtent pwLabel.text).x + 5,-3])
|
|
|
|
button btnLogin "Login" width:100 align:#left offset:[0,15] across:2
|
|
button btnCancel "Cancel" width:100 align:#right offset:[0,15]
|
|
|
|
fn setServerList =
|
|
(
|
|
if (::gRsPerforce.p4 == undefined) do
|
|
(
|
|
gRsPerforce.connect()
|
|
)
|
|
|
|
local serverInfoList = gRsPerforce.getP4infoList regen:true
|
|
|
|
-- Get the current Perforce-settings:
|
|
local currentServer = gRsPerforce.p4.Port
|
|
local currentWorkSpace = gRsPerforce.p4.Client
|
|
local currentUsername = gRsPerforce.p4.User
|
|
local currentServerNum = 0
|
|
|
|
local serverListItems = #()
|
|
|
|
serverListItems = for serverNum = 1 to serverInfoList.count collect
|
|
(
|
|
local serverInfo = serverInfoList[serverNum]
|
|
|
|
-- Is this the currently-used server?
|
|
local isCurrentServer = false
|
|
if (currentServerNum == 0) do
|
|
(
|
|
if (serverInfo.server == currentServer) and
|
|
(serverInfo.workspace == currentWorkSpace) and
|
|
(serverInfo.username == currentUsername) do
|
|
(
|
|
currentServerNum = serverNum
|
|
isCurrentServer = true
|
|
)
|
|
)
|
|
|
|
(if isCurrentServer then "* " else " ") + serverInfo.name + " - " + serverInfo.server + " - " + serverInfo.workspace
|
|
)
|
|
|
|
serverList.items = serverListItems
|
|
serverList.selection = currentServerNum
|
|
)
|
|
|
|
fn setupLoginCtrls =
|
|
(
|
|
messageLabel.text = RsWordWrap (trimRight gRsPerforce.loginMsg) textWidth
|
|
|
|
-- Keep a copy of the original control-positions, to revert to if function is re-run:
|
|
if (ctrlPositions == undefined) then
|
|
(
|
|
ctrlPositions = for ctrl in RsP4PasswordDialog.controls collect ctrl.pos
|
|
)
|
|
else
|
|
(
|
|
-- Restore control positions if re-running:
|
|
for n = 1 to ctrlPositions.count do
|
|
(
|
|
RsP4PasswordDialog.controls[n].pos = ctrlPositions[n]
|
|
)
|
|
|
|
if ::gRsPerforce.loginMsg == "" then
|
|
(
|
|
messageLabel.text = "Login successful!"
|
|
)
|
|
else
|
|
(
|
|
-- Clear password text for retry:
|
|
txtPassword.text = ""
|
|
)
|
|
)
|
|
|
|
-- Clear error-message:
|
|
gRsPerforce.loginMsg = ""
|
|
|
|
local textHeight = (RsGetTextExtent messageLabel.text).y
|
|
|
|
-- Shift server-list down to fit text:
|
|
serverList.pos.y += textHeight
|
|
|
|
setServerList()
|
|
|
|
-- Resize the list to fit its contents:
|
|
serverList.height = (serverList.items.count * 13) +6
|
|
|
|
-- Move other controls down below list:
|
|
local moveCtrls = #(pwLabel, txtPassword, btnLogin, btnCancel)
|
|
for ctrl in moveCtrls do
|
|
(
|
|
ctrl.pos.y += (serverList.height + textHeight)
|
|
)
|
|
|
|
-- Resize rollout:
|
|
RsP4PasswordDialog.height = btnCancel.pos.y + 30
|
|
)
|
|
|
|
fn loginPressed =
|
|
(
|
|
if (serverList.selection != 0) do
|
|
(
|
|
::gRsPerforce.login serverList.selection txtPassword.text createNewDialogOnFail:standAlone
|
|
|
|
destroyDialog RsP4PasswordDialog
|
|
|
|
if RsP4PasswordDialog.open do
|
|
(
|
|
setupLoginCtrls()
|
|
)
|
|
)
|
|
)
|
|
|
|
fn cancelPressed =
|
|
(
|
|
gRsPerforce.lastLoginSucceded = false
|
|
|
|
if standAlone then
|
|
(
|
|
destroyDialog RsP4PasswordDialog
|
|
)
|
|
else
|
|
(
|
|
removeRollout RsP4PasswordDialog RsPerforceToolFloater
|
|
addRollout RsP4PasswordDialog RsPerforceToolFloater
|
|
)
|
|
)
|
|
|
|
on txtPassword KeyPress ev arg do
|
|
(
|
|
case of
|
|
(
|
|
(arg.keyChar == "\r"):(loginPressed()) -- Enter pressed
|
|
((bit.charAsInt arg.keyChar) == 27):(cancelPressed()) -- Escape pressed
|
|
)
|
|
)
|
|
|
|
on btnLogin pressed do
|
|
(
|
|
loginPressed()
|
|
)
|
|
|
|
on btnCancel pressed do
|
|
(
|
|
cancelPressed()
|
|
)
|
|
|
|
on RsP4PasswordDialog open do
|
|
(
|
|
-- Is rollout docked into a floater or not?
|
|
standAlone = ((getDialogSize RsP4PasswordDialog) != [0,0])
|
|
|
|
-- If rollout is docked, hide the "Cancel" button, and move "Login" over to middle:
|
|
if not standAlone do
|
|
(
|
|
btnCancel.visible = false
|
|
btnLogin.pos = btnLogin.pos + ((btnCancel.pos - btnLogin.pos) / 2)
|
|
)
|
|
|
|
setupLoginCtrls()
|
|
|
|
-- Set up dotNet password-box:
|
|
txtPassword.Height = 20
|
|
txtPassword.multiLine = true
|
|
txtPassword.acceptsTab = true
|
|
txtPassword.PasswordChar = "*"
|
|
txtPassword.text= ::gRsPerforce.P4password as string
|
|
setFocus txtPassword
|
|
|
|
-- Set password-box colors to match UI:
|
|
local textCol = (colorMan.getColor #windowText) * 255
|
|
local windowCol = (colorMan.getColor #window) * 255
|
|
local DNcolour = dotNetClass "System.Drawing.Color"
|
|
txtPassword.foreColor = DNcolour.FromArgb textCol[1] textCol[2] textCol[3]
|
|
txtPassword.backColor = DNcolour.FromArgb windowCol[1] windowCol[2] windowCol[3]
|
|
)
|
|
)
|
|
|
|
--
|
|
-- struct: Perforce
|
|
-- desc: Perforce functions for MaxScript.
|
|
--
|
|
-- notes:
|
|
-- Just like any Perforce API please batch your requests as much as possible;
|
|
-- by passing MaxScript Array objects as file arguments.
|
|
--
|
|
-- examples:
|
|
-- p4 = RsPerforce()
|
|
-- (RsPerforce false P4:dotNetObject:P4API.P4Connection result:undefined)
|
|
-- p4.connect "x:\\payne"
|
|
-- Successfully connect to the Perforce server (rsgvanp4s1:1666 as david.muir).
|
|
-- true
|
|
-- p4.local2depot "x:\\payne\\build\\dev\\levelstatus.txt"
|
|
-- "//projects/payne/build/dev/levelstatus.txt"
|
|
-- p4.FileMapTo #local "//depot/gta5/assets_ng/export/levels/gta5/_cityw/beverly_01/bh1_01.zip"
|
|
-- "X:\gta5\assets_ng\export\levels\gta5\_cityw\beverly_01\bh1_01.zip"
|
|
|
|
struct RsPerforce
|
|
(
|
|
p4 = undefined, -- .Net P4API.P4Connection object.
|
|
result = undefined, -- Last P4.Net result (RecordSet) or undefined.
|
|
currScmGroup = 0,
|
|
fileMapping = undefined,
|
|
p4rootPattern = "*",
|
|
|
|
-- Used by RsP4PasswordDialog:
|
|
P4password = "",
|
|
loginMsg = "",
|
|
lastLoginSucceded = false,
|
|
|
|
-- Used by PostExportAdd:
|
|
addQueue = #(),
|
|
|
|
-- Used by SubmitFilesWithText:
|
|
newListText,
|
|
|
|
-- The server-list is only generated when it needs to be, to avoid garbage-collect errors from RsProjectGetPerforceInfo:
|
|
p4infoList,
|
|
fn getP4infoList regen:false =
|
|
(
|
|
if regen or (p4infoList == undefined) do
|
|
(
|
|
p4infoList = for n = 1 to RsProjectGetNumPerforces() collect (RsProjectGetPerforceInfo (n - 1))
|
|
)
|
|
|
|
return p4infoList
|
|
),
|
|
|
|
-- Calls up a login-rollout, and returns success true/false:
|
|
fn getPerforcePassword =
|
|
(
|
|
destroyDialog RsP4PasswordDialog
|
|
|
|
if RsProjectGetPerforceIntegration() then
|
|
(
|
|
createDialog RsP4PasswordDialog style:#(#style_titlebar, #style_border) width:300 modal:true
|
|
|
|
return ::gRsPerforce.lastLoginSucceded
|
|
)
|
|
else
|
|
(
|
|
return false
|
|
)
|
|
),
|
|
|
|
--
|
|
-- name: connect
|
|
-- desc: Connect to the Perforce server that has dir mapped.
|
|
--
|
|
-- This required your Perforce environment to be set up correctly.
|
|
--
|
|
fn connect =
|
|
(
|
|
if not RsProjectGetPerforceIntegration() do
|
|
(
|
|
return false
|
|
)
|
|
|
|
local outVal = false
|
|
|
|
local currentDir = sysInfo.currentdir
|
|
|
|
try
|
|
(
|
|
sysInfo.currentdir = (RsConfigGetProjRootDir())
|
|
|
|
if ( p4 == undefined ) then p4 = dotNetObject "P4API.P4Connection"
|
|
if ( fileMapping == undefined) then fileMapping = dotNetClass "RSG.SourceControl.Perforce.FileMapping"
|
|
|
|
p4.cwd = sysInfo.currentdir
|
|
p4.connect()
|
|
p4.run "login" #("-s")
|
|
format "Successfully connect to the Perforce server (% as %).\n" p4.Port p4.User
|
|
|
|
outVal = (p4.isValidConnection true true)
|
|
)
|
|
catch
|
|
(
|
|
local message = "Exception connecting to Perforce:\n" + (getCurrentException())
|
|
format "%\n" message
|
|
loginMsg = message
|
|
|
|
getPerforcePassword()
|
|
sysInfo.currentdir = currentDir
|
|
|
|
outVal = lastLoginSucceded
|
|
)
|
|
|
|
-- If connection was successful, extract client-root from Perforce-info:
|
|
p4rootPattern = "*"
|
|
if outVal do
|
|
(
|
|
-- Get P4 root-path ("x:/*") to allow 'fn Run' to tell if a path is under the P4 root or not:
|
|
local P4info = This.Info()
|
|
p4rootPattern = (P4info.ClientRoot + "*")
|
|
)
|
|
|
|
sysInfo.currentdir = currentDir
|
|
|
|
return outVal
|
|
),
|
|
|
|
fn dump =
|
|
(
|
|
format "User %\nPort %\nHost %\nClient %\n" p4.User p4.Port p4.Host p4.client
|
|
),
|
|
|
|
--
|
|
-- name: connected
|
|
-- desc: Return true if successfully connected; false otherwise.
|
|
--
|
|
fn connected =
|
|
(
|
|
pushPrompt "Checking P4 connection..."
|
|
local retVal = (p4 != undefined) and (p4.IsValidConnection true true)
|
|
popPrompt()
|
|
|
|
return retVal
|
|
),
|
|
|
|
|
|
fn logout =
|
|
(
|
|
try(
|
|
p4.run "logout" #()
|
|
)catch()
|
|
return true
|
|
),
|
|
|
|
--
|
|
-- name: disconnect
|
|
-- desc: Disconnect from the Perforce server.
|
|
--
|
|
fn disconnect = (
|
|
try(
|
|
logout()
|
|
p4.Disconnect()
|
|
)catch
|
|
(
|
|
print (getCurrentException())
|
|
return false
|
|
)
|
|
return true
|
|
),
|
|
|
|
--
|
|
-- name: run
|
|
-- desc: Generic run method that maps straight onto the low-level P4API.P4 object.
|
|
-- If "showProgress" is used, non-file arguments must be supplied via "switches",
|
|
-- as the "args" list will be split up into chunks
|
|
--
|
|
fn run cmd args switches:#() showProgress:false progressTitle: returnResult:false silent:false checkEmptyArgs:true doLogin:true =
|
|
(
|
|
-- Skip command if Perforce integration is turned off:
|
|
if (not RsProjectGetPerforceIntegration()) do
|
|
(
|
|
return (if returnResult then undefined else True)
|
|
)
|
|
|
|
-- Log this Perforce call; this is for Perforce command tracking.
|
|
-- Its too easy to kill Perforce if you're not careful.
|
|
if ( undefined != gRsULog ) then
|
|
(
|
|
local logMessage = stringStream ""
|
|
format "RsPerforce::Run( % % )" cmd args to:logMessage
|
|
gRsULog.LogDebug (logMessage as String) context:"p4.ms"
|
|
)
|
|
|
|
local failReturn = if returnResult then undefined else False
|
|
|
|
if (args == undefined) or (args == "") then
|
|
(
|
|
format "Empty string as perforce parameter!\n"
|
|
return failReturn
|
|
)
|
|
|
|
if not isKindOf args Array do
|
|
(
|
|
args = #(args)
|
|
)
|
|
|
|
if ( checkEmptyArgs and args.count == 0 ) do
|
|
(
|
|
format "Empty array passed as perforce args! (switches: %)\n" (switches as string)
|
|
gRsULog.LogWarning ("P4 command with 0 arguments passed, for command " + cmd + " - should this have arguments?" ) context:"P4.ms"
|
|
return failReturn
|
|
)
|
|
|
|
if not connected() do
|
|
(
|
|
if (not doLogin) or (not (connect())) do
|
|
(
|
|
if doLogin do
|
|
(
|
|
format "Perforce login failed.\n"
|
|
)
|
|
return failReturn
|
|
)
|
|
)
|
|
|
|
-- Make paths p4-legal:
|
|
args = for thisArg in args collect
|
|
(
|
|
-- We don't want to "fix" the double-slashes at the start of //depot/ path:
|
|
local doubleSlashStart = (matchPattern thisArg pattern:"//*")
|
|
|
|
-- Remove double-slashes, convert to forward-slashes:
|
|
thisArg = RsMakeSafeSlashes thisArg
|
|
|
|
-- Add double-slash back for depot-paths:
|
|
if doubleSlashStart do
|
|
(
|
|
thisArg = ("/" + thisArg)
|
|
)
|
|
|
|
-- Don't process arg if it's a legal path with drive-letter specified, that isn't under P4 root:
|
|
-- Non-client files will cause errors
|
|
-- (this should ignore non-path args)
|
|
if (thisArg[2] == ":") and (thisArg[1] != "-") and (pathConfig.isLegalPath thisArg) and (not matchPattern thisArg pattern:p4rootPattern) do
|
|
(
|
|
thisArg = DontCollect
|
|
)
|
|
|
|
thisArg
|
|
)
|
|
|
|
-- Abort if all files were filtered out:
|
|
if ( checkEmptyArgs and args.count == 0 ) do
|
|
(
|
|
return failReturn
|
|
)
|
|
|
|
if showProgress and (progressTitle == unsupplied) do
|
|
(
|
|
progressTitle = "P4.run " + cmd + " (" + (args.count as string) + " files)"
|
|
)
|
|
|
|
-- If this function is updating a progress-bar, chop the file-list up into chunks:
|
|
local fileArgList = #()
|
|
if showProgress then
|
|
(
|
|
progressStart progressTitle
|
|
|
|
local maxChunkSize = args.count / 15.0
|
|
|
|
local currentChunk
|
|
local nextChunk = 0
|
|
|
|
for n = 1 to args.count do
|
|
(
|
|
if (n > nextChunk) or (n == args.count) do
|
|
(
|
|
nextChunk += maxChunkSize
|
|
|
|
currentChunk = #()
|
|
join currentChunk switches
|
|
append fileArgList currentChunk
|
|
)
|
|
|
|
append currentChunk args[n]
|
|
)
|
|
)
|
|
else
|
|
(
|
|
append fileArgList #()
|
|
join fileArgList[1] switches
|
|
join fileArgList[1] args
|
|
)
|
|
|
|
local errors = #()
|
|
local warnings = #()
|
|
local messages = #()
|
|
local errorMessage = ""
|
|
|
|
local notCancelled = true
|
|
local useArgs
|
|
local useCommand = "" -- Used by error-catch
|
|
|
|
local results = #()
|
|
local numRetries = 3
|
|
|
|
local success = false
|
|
for retry=1 to numRetries while (not success) do
|
|
(
|
|
try
|
|
(
|
|
for n = 1 to fileArgList.count while (not showProgress) or (notCancelled = progressUpdate (100.0 * n / fileArgList.count)) do
|
|
(
|
|
useArgs = fileArgList[n]
|
|
useCommand = "gRsPerforce.p4.run \"" + cmd + "\" " + (useArgs as string)
|
|
|
|
if not silent do
|
|
(format "%\n" useCommand)
|
|
|
|
-- Process the RecordSet into something to return to MaxScript:
|
|
result = (p4.Run cmd useArgs)
|
|
|
|
-- Build results-list:
|
|
append results result
|
|
|
|
join errors result.Errors
|
|
join messages result.messages
|
|
if result.errorMessage != "" do (errorMessage = result.errorMessage)
|
|
|
|
-- Don't show warnings for up-to-date files:
|
|
join warnings (for warning in result.Warnings where (not matchpattern warning pattern:"* - file(s) up-to-date.") collect warning)
|
|
)
|
|
|
|
success = true --Do not retry.
|
|
)
|
|
catch
|
|
(
|
|
local exception = getCurrentException()
|
|
--if not silent do
|
|
(
|
|
format "P4 Error: %\n%\n" useCommand exception
|
|
)
|
|
|
|
local connectionReset = ((findstring exception "WSAECONNRESET") != undefined) or ((findstring exception "Partner exited unexpectedly") != undefined)
|
|
local rsULogMethod = undefined
|
|
if connectionReset == false then
|
|
(
|
|
rsULogMethod = gRsULog.LogWarning
|
|
notCancelled = false
|
|
)
|
|
else
|
|
(
|
|
if retry == numRetries then
|
|
(
|
|
--Users are unable to connect to the Perforce server. Prompt users that the command has failed.
|
|
errorMessage = ("Perforce has become unresponsive. Waiting before retrying...\n\n" + cmd + " " + useArgs as string)
|
|
if false == (RsQueryBoxTimeOut errorMessage title:"Perforce Command Error" yesText:"Try Again" noText:"Cancel" ) then
|
|
(
|
|
--Cancel, log this as an error to stop the export.
|
|
notCancelled = false
|
|
rsULogMethod = gRsULog.LogError
|
|
exception = "Command has been aborted by the user."
|
|
)
|
|
else
|
|
(
|
|
--Continue to try.
|
|
retry = 0
|
|
)
|
|
)
|
|
)
|
|
|
|
if rsULogMethod != undefined then
|
|
(
|
|
local tokens = filterstring (exception) "\n"
|
|
if tokens != undefined do
|
|
(
|
|
for n = 1 to tokens.count do
|
|
(
|
|
local token = tokens[n]
|
|
token = substituteString token "\\" "/"
|
|
token = substituteString token "" "_"
|
|
|
|
rsULogMethod token context:"P4.ms"
|
|
)
|
|
)
|
|
)
|
|
)
|
|
)
|
|
|
|
if showProgress do
|
|
(
|
|
progressEnd()
|
|
|
|
-- Blank out the progress-text, as proper redraw might not happen for a while:
|
|
progressStart ""; progressUpdate 100.0; progressEnd()
|
|
)
|
|
|
|
-- Return array of results if requested:
|
|
if returnResult do
|
|
(
|
|
if notCancelled then
|
|
(
|
|
-- showProgress generates multiple result-records, so returns an array of them:
|
|
if showProgress then
|
|
(
|
|
return results
|
|
)
|
|
else
|
|
(
|
|
return result
|
|
)
|
|
)
|
|
else
|
|
(
|
|
return undefined
|
|
)
|
|
)
|
|
|
|
if (not notCancelled) or (errors.count != 0) or (warnings.count != 0) then
|
|
(
|
|
-- Display errors in a messagebox and output to listener, if not running silently:
|
|
if not silent do
|
|
(
|
|
local dontError = (RsUserGetFriendlyName()=="Tools" and cmd=="submit")
|
|
for er in errors do
|
|
(
|
|
if dontError then
|
|
gRsUlog.LogWarning (er as string)
|
|
else
|
|
gRsUlog.LogError (er as string)
|
|
)
|
|
for m in messages do
|
|
gRsUlog.LogMessage (m as string)
|
|
for w in warnings do
|
|
gRsUlog.LogWarning (w as string)
|
|
)
|
|
return false
|
|
)
|
|
return true
|
|
),
|
|
|
|
--
|
|
-- name: login
|
|
-- desc: login with given group id
|
|
--
|
|
fn login id pw createNewDialogOnFail:false =
|
|
(
|
|
currScmGroup = id
|
|
P4password = pw
|
|
|
|
getP4infoList()
|
|
local P4Info = p4infoList[id]
|
|
|
|
format "P4 login: %\n" (P4Info as string)
|
|
|
|
case of
|
|
(
|
|
(id > p4infoList.count):
|
|
(
|
|
messagebox ("Id higher than exisiting server setups: "+ p4infoList.count as string)
|
|
return false
|
|
)
|
|
(P4Info == undefined):
|
|
(
|
|
messagebox ("Invalid Perforce-setup item: " + (id - 1) as string)
|
|
return false
|
|
)
|
|
)
|
|
|
|
try
|
|
(
|
|
disconnect()
|
|
|
|
p4.User = P4Info.username
|
|
p4.Port = P4Info.server
|
|
p4.Client = P4Info.workspace
|
|
|
|
p4.connect()
|
|
p4.login pw
|
|
result = p4.run "login" #("-s")
|
|
|
|
if (result != undefined) do
|
|
(
|
|
if (result.HasErrors()) or (result.HasWarnings()) then
|
|
(
|
|
local msg = stringstream "Login result:\n"
|
|
format "%: %\n" "ErrorMessage" (result.ErrorMessage as string) to:msg
|
|
format "%: %\n" "Errors" (result.Errors as string) to:msg
|
|
format "%: %\n" "Item" (result.item as string) to:msg
|
|
format "%: %\n" "Messages" (result.Messages as string) to:msg
|
|
format "%: %\n" "Records" (result.Records as string) to:msg
|
|
format "%: %\n" "Warnings" (result.Warnings as string) to:msg
|
|
-- messagebox msg
|
|
|
|
throw msg
|
|
)
|
|
)
|
|
|
|
lastLoginSucceded = true
|
|
|
|
)
|
|
catch
|
|
(
|
|
local message = getCurrentException()
|
|
if (message == undefined) do
|
|
(
|
|
message="Perforce command returned undefined result."
|
|
)
|
|
disconnect()
|
|
|
|
loginMsg = message
|
|
getPerforcePassword()
|
|
)
|
|
|
|
return returnVal
|
|
),
|
|
|
|
-- Converts a Perforce result-record to a Max struct:
|
|
fn record2struct record =
|
|
(
|
|
local structString = stringStream ""
|
|
|
|
format "struct p4recordStruct (" to:structString
|
|
|
|
local keyNames = record.fields.keys
|
|
local keyCount = record.fields.keys.count
|
|
|
|
for n = 1 to keyCount do
|
|
(
|
|
local keyname = keyNames[n]
|
|
local key = RsFileSafeString keyname
|
|
local item = RsMakeSafeSlashes (record.fields.item keyname)
|
|
format "% = \"%\"" key item to:structString
|
|
|
|
if (n != keyCount) do
|
|
(
|
|
format ", " to:structString
|
|
)
|
|
)
|
|
|
|
format ")" to:structString
|
|
|
|
-- Return struct instance, converted from stringstream:
|
|
(execute (structString as string))()
|
|
),
|
|
|
|
-- Returns struct containing Perforce info:
|
|
fn info =
|
|
(
|
|
result = run "info" #() returnResult:true silent:true checkEmptyArgs:false
|
|
|
|
if result == undefined then undefined else
|
|
(
|
|
record2struct result.records[1]
|
|
)
|
|
),
|
|
|
|
-- Finds/fixes files that have been deleted locally, but not on the server:
|
|
-- (not as thorough as a full Reconcile, but useful as a quick fix for an issue we see regularly...)
|
|
fn deleteSyncCheck filenames doFix:True silent:True =
|
|
(
|
|
-- Filter out filenames that exist locally, we don't need to see if they've been deleted from server:
|
|
local filenamesList = filenames
|
|
if (not isKindOf filenames Array) do
|
|
(
|
|
filenamesList = #(filenames)
|
|
)
|
|
filenamesList = for filename in filenamesList where (not doesFileExist filename) collect filename
|
|
|
|
if (filenamesList.count ==0) do (return #())
|
|
|
|
-- Get list of files that have been locally deleted, but are still on Perforce:
|
|
pushPrompt "Checking for locally-deleted files..."
|
|
local result = run "reconcile" filenamesList switches:#("-d", "-n") returnResult:True silent:silent
|
|
popPrompt()
|
|
|
|
if (result == undefined) do (return undefined)
|
|
|
|
local records = result.Records
|
|
local delFilenames = for i = 1 to (records.Count) collect
|
|
(
|
|
records[i].Item["depotFile"]
|
|
)
|
|
|
|
if (delFilenames.count != 0) do
|
|
(
|
|
-- Set have-version of wonky files to #0 - they'll be synced by next proper sync:
|
|
-- ("flush" sets the synced-flag without attempting file-copy actions, although that may not be necessary here)
|
|
if (doFix) and (delFilenames.count != 0) do
|
|
(
|
|
format "Fixing locally-deleted files: Setting have-version to #0\n"
|
|
for thisFile in delFilenames do
|
|
(
|
|
format " %\n" thisFile
|
|
)
|
|
|
|
pushPrompt "Fixing have-version for locally-deleted files"
|
|
local fixFilenames = for thisFile in delFilenames collect (thisFile + "#0")
|
|
run "flush" fixFilenames silent:silent
|
|
popPrompt()
|
|
)
|
|
)
|
|
|
|
return delFilenames
|
|
),
|
|
|
|
-- Returns list of checked-out filenames:
|
|
fn checkedOut =
|
|
(
|
|
local outVal = #()
|
|
|
|
local infoResult = run "info" #() returnResult:true silent:true checkEmptyArgs:false
|
|
|
|
if (infoResult != undefined) do
|
|
(
|
|
local clientName = infoResult.records[1].fields.item["clientName"]
|
|
local prefixRemove = clientName.count + 4
|
|
|
|
local clientRoot = infoResult.records[1].fields.item["clientRoot"]
|
|
|
|
local openedResult = run "opened" #() returnResult:true silent:true checkEmptyArgs:false
|
|
|
|
outVal = for item in openedResult.records collect
|
|
(
|
|
toLower (clientRoot + (substring item.fields.item["clientFile"] prefixRemove -1))
|
|
)
|
|
)
|
|
|
|
outVal
|
|
),
|
|
|
|
-- Get set of file-status records:
|
|
fn getFileStats files silent:true =
|
|
(
|
|
local fileStats = run "fstat" files silent:silent returnResult:true
|
|
|
|
if (fileStats == undefined) do return undefined
|
|
|
|
for record in result.records collect record
|
|
),
|
|
|
|
-- This checks the file list to see if they are checked out for edit/add by the current user
|
|
-- already. It sets the corresponding element to true in the bitarray if it is.
|
|
fn checkedOutForEdit files silent:true =
|
|
(
|
|
local retVal = #{}
|
|
|
|
if (files.count == 0) do return retVal
|
|
|
|
local results = files
|
|
if (not isKindOf files[1] dotNetObject) do
|
|
(
|
|
results = getFileStats files silent:silent
|
|
)
|
|
|
|
if (results == undefined) do return retVal
|
|
|
|
retVal.count = results.count
|
|
|
|
for fileNum = 1 to results.count do
|
|
(
|
|
local action = results[fileNum].Item["action"]
|
|
|
|
if (action == "edit") or (action == "add") do
|
|
(
|
|
retVal[fileNum] = true
|
|
)
|
|
)
|
|
|
|
return retVal
|
|
),
|
|
|
|
--------------------------------------------------------------
|
|
-- Returns bitarray of files that are exclusively
|
|
-- checked out by someone else
|
|
--------------------------------------------------------------
|
|
fn isCheckedoutExclusive files silent:true =
|
|
(
|
|
local retVal = #{}
|
|
|
|
if (files.count == 0) do return retVal
|
|
|
|
local results = files
|
|
if (not isKindOf files[1] dotNetObject) do
|
|
(
|
|
results = getFileStats files silent:silent
|
|
)
|
|
|
|
if (results == undefined) do return retVal
|
|
|
|
retVal.count = results.count
|
|
|
|
for fileNum = 1 to results.count do
|
|
(
|
|
local record = results[fileNum]
|
|
|
|
-- otherOpen key is undefined if file isn't checked out by anyone else:
|
|
local otherOpened = (record.fields.item["otherOpen"] != undefined)
|
|
|
|
if otherOpened do
|
|
(
|
|
-- Does filetype have +l flag set?
|
|
local fileType = record.fields.item["headType"]
|
|
local typeFlags = (filterString fileType "+")[2]
|
|
|
|
if (typeFlags != undefined) and (matchPattern typeFlags pattern:"*l*") do
|
|
(
|
|
retVal[fileNum] = true
|
|
)
|
|
)
|
|
)
|
|
|
|
return retVal
|
|
),
|
|
|
|
-- Sync to latest revision of the specified file(s) on Perforce:
|
|
fn sync filename force:false silent:false showProgress:false progressTitle: =
|
|
(
|
|
run "sync" filename switches:(if force then #("-f") else #()) silent:silent showProgress:showProgress progressTitle:progressTitle
|
|
),
|
|
|
|
-- Syncs files, after fixing records for files that have been deleted locally:
|
|
-- (this used to perform similar function by force-getting missing files after initial sync - hence the name)
|
|
fn syncWithRetry filenames force:false silent:false showProgress:false progressTitle: =
|
|
(
|
|
if not isKindOf filenames Array do
|
|
(
|
|
filenames = #(filenames)
|
|
)
|
|
|
|
-- No need to perform this check when force-syncing:
|
|
if (not force) do
|
|
(
|
|
-- Make sure that none of these files are locally deleted outside the server's knowledge:
|
|
local result = deleteSyncCheck filenames doFix:True silent:silent
|
|
if (result == undefined) do (return undefined)
|
|
)
|
|
|
|
sync filenames force:force silent:silent showProgress:showProgress progressTitle:progressTitle
|
|
),
|
|
|
|
-- Returns filenames that have changed on the server, or undefined if check failed:
|
|
fn changedFiles filenames silent:true showProgress:false progressTitle: =
|
|
(
|
|
-- Make sure that none of these files are locally deleted, outside the server's knowledge:
|
|
local result = deleteSyncCheck filenames doFix:True silent:silent
|
|
if (result == undefined) do (return undefined)
|
|
|
|
if not silent do (format "Getting P4 changes: ")
|
|
local results = run "sync" filenames switches:#("-n") silent:silent returnResult:true showProgress:showProgress progressTitle:progressTitle
|
|
|
|
if (results == undefined) then (return undefined) else
|
|
(
|
|
-- Results is returned as an array when showProgress is used:
|
|
if not (isKindOf results array) do (results = #(results))
|
|
|
|
local outList = #()
|
|
|
|
for result in results do
|
|
(
|
|
for record in result.records do
|
|
(
|
|
append outList record.item["clientFile"]
|
|
)
|
|
)
|
|
|
|
return outList
|
|
)
|
|
),
|
|
|
|
-- Sync only changed files, allowing for better progress-bar showing:
|
|
-- Can set limit for minimum number of sync-items before progress-bar is shown
|
|
fn syncChanged filenames force:false clobber:false clobberAsk:true silent:true minForProgress:0 showProgress:true progressTitle: =
|
|
(
|
|
if RsProjectGetPerforceIntegration() do
|
|
(
|
|
local getFilenames
|
|
|
|
if force then
|
|
(
|
|
getFilenames = filenames
|
|
)
|
|
else
|
|
(
|
|
local changedFilenames = changedFiles filenames silent:silent
|
|
|
|
if (changedFilenames == undefined) do (return false)
|
|
|
|
-- Find writable files:
|
|
local writableFiles = #{}
|
|
for fileNum = 1 to changedFilenames.count do
|
|
(
|
|
local filename = changedFilenames[fileNum]
|
|
|
|
writableFiles[fileNum] = (doesFileExist filename) and not (getfileattribute filename #readOnly)
|
|
)
|
|
local clobberFilenames = for n = writableFiles collect changedFilenames[n]
|
|
|
|
local getFileNums = #{}
|
|
getFileNums.count = changedFilenames.count
|
|
getFileNums = -getFileNums
|
|
|
|
if (writableFiles.numberSet != 0) and clobberAsk and not clobber do
|
|
(
|
|
clobberAsk = false
|
|
|
|
local plural = if (clobberFilenames.count == 1) then "" else "s"
|
|
local pronoun = if (clobberFilenames.count == 1) then "it" else "them"
|
|
|
|
local queryVal = RsQueryBoxMultiBtn ("Writable file" + plural + " found.\nClobber (overwrite) " + pronoun + "?") title:("Sync over writable file" + plural + "?") \
|
|
labels:#("Clobber File" + plural, "Ignore File" + plural) tooltips:#("Overwrite these files", "Don't overwrite these files") \
|
|
width:600 timeout:30 defaultBtn:1 listItems:(for item in clobberFilenames collect (RsMakeBackSlashes item))
|
|
|
|
clobber = (queryVal == 1)
|
|
)
|
|
|
|
if clobber then
|
|
(
|
|
for filename in clobberFilenames do
|
|
(
|
|
setfileattribute filename #readOnly true
|
|
)
|
|
)
|
|
else
|
|
(
|
|
getFileNums -= writableFiles
|
|
)
|
|
|
|
-- Don't sync readonly files, but make them writable if clobber is turned on
|
|
local getFilenames = for filename in changedFilenames collect
|
|
(
|
|
local getItem = filename
|
|
|
|
if (doesFileExist filename) and not (getfileattribute filename #readOnly) do
|
|
(
|
|
if clobberAsk and not clobber do
|
|
(
|
|
clobberAsk = false
|
|
|
|
clobber = queryBox "Writable file(s) found. Clobber (overwrite) them?" title:"Sync over writable file(s)?"
|
|
)
|
|
|
|
if clobber then
|
|
(
|
|
setfileattribute filename #readOnly true
|
|
)
|
|
else
|
|
(
|
|
getItem = dontCollect
|
|
)
|
|
)
|
|
|
|
getItem
|
|
)
|
|
|
|
if (getFilenames == undefined) do (return false)
|
|
if (getFilenames.count == 0) do (return true)
|
|
|
|
if not silent do (format "(% files have changed on server)\n" getFilenames.count)
|
|
)
|
|
|
|
if showProgress and (getFilenames.count < minForProgress) do
|
|
(
|
|
showProgress = false
|
|
)
|
|
|
|
sync getFilenames force:force silent:silent showProgress:showProgress progressTitle:progressTitle
|
|
)
|
|
),
|
|
|
|
-- Checkout current revision of the specified file(s) from Perforce:
|
|
fn edit filename exclusive:false silent:false switches:#() =
|
|
(
|
|
local filenames = if isKindOf filename Array then filename else #(filename)
|
|
|
|
-- If Perforce integration is turned off, just make the file writable:
|
|
if not RsProjectGetPerforceIntegration() do
|
|
(
|
|
for filename in filenames where doesFileExist filename do
|
|
(
|
|
setFileAttribute filename #readOnly false
|
|
)
|
|
|
|
return true
|
|
)
|
|
|
|
if not (run "edit" filenames silent:silent switches:switches) do
|
|
(
|
|
return false
|
|
)
|
|
|
|
if exclusive do
|
|
(
|
|
if not (run "reopen" filenames switches:#("-t", "+l") silent:silent) do
|
|
(
|
|
return false
|
|
)
|
|
)
|
|
return true
|
|
),
|
|
|
|
-- Mark the file for add on Perforce (default changelist)
|
|
fn add filename exclusive:false silent:false switches:#() =
|
|
(
|
|
-- If Perforce integration is turned off, just make these files writable.
|
|
-- Loops through just incase an array of multiple filenames given
|
|
if not RsProjectGetPerforceIntegration() do
|
|
(
|
|
if ( classof filename == Array ) then
|
|
(
|
|
for file in filename where doesFileExist file do
|
|
(
|
|
setFileAttribute file #readOnly false
|
|
)
|
|
)
|
|
else
|
|
(
|
|
setFileAttribute filename #readOnly false
|
|
)
|
|
return true
|
|
)
|
|
|
|
if not (run "add" filename silent:silent switches:switches) then
|
|
return false
|
|
|
|
if exclusive do
|
|
(
|
|
if not (run "reopen" filename switches:#("-t", "+l") silent:silent) then
|
|
return false
|
|
)
|
|
),
|
|
|
|
fn reopen filenames filetype: exclusive:true silent:false =
|
|
(
|
|
if not isKindOf filenames Array do
|
|
(
|
|
filenames = #(filenames)
|
|
)
|
|
local fileTypeString = ""
|
|
if unsupplied!=filetype then
|
|
append fileTypeString filetype
|
|
if exclusive then
|
|
(
|
|
-- need to check for existing "+" as causing problems like "binary+m+l"
|
|
local tok = FilterString fileTypeString "+"
|
|
|
|
if tok.count == 0 or tok.count == 1 then append fileTypeString "+l" -- +l = exclusive open
|
|
else(
|
|
|
|
fileTypeString = tok[1] -- e.g. "binary"
|
|
append fileTypeString "+" -- "binary+"
|
|
|
|
if not (matchPattern tok[2] pattern:"*l*") then (
|
|
append fileTypeString "l" -- "binary+l"
|
|
)
|
|
append fileTypeString tok[2] -- "binary+lmS16....."
|
|
|
|
)
|
|
)
|
|
|
|
run "reopen" filenames switches:#("-t", fileTypeString) silent:silent
|
|
),
|
|
|
|
--
|
|
-- fn exists
|
|
-- desc Checks a list of files to see if they are in perforce already, and
|
|
-- sets their index to false in the filesInP4List if it doesn't
|
|
--
|
|
fn exists &fileList silent:true =
|
|
(
|
|
local result = undefined
|
|
local filesInP4List = #{}
|
|
filesInP4List.count = fileList.count
|
|
|
|
if ( fileList.count > 0 ) do
|
|
(
|
|
result = ( run "fstat" fileList silent:silent returnResult:true )
|
|
|
|
-- Array of files passed
|
|
if ( (result != undefined) and (result.records.count > 0) ) then
|
|
(
|
|
-- If the clientFile matches the one in the record then it's in perforce so no
|
|
-- need to add it/create it later unless forced
|
|
local unMatchedRecords = (for n = 1 to result.records.count collect n) as bitarray
|
|
local recordNames = for record in result.records collect (toLower (RsMakeSafeSlashes record.fields.item["clientFile"]))
|
|
local recordHeadActions = for record in result.records collect (toLower (RsMakeSafeSlashes record.fields.item["headAction"]))
|
|
|
|
for fileNum = 1 to fileList.count do
|
|
(
|
|
local findFilename = toLower (RsMakeSafeSlashes fileList[fileNum])
|
|
|
|
local keepLooking = true
|
|
for recordNum in unMatchedRecords while keepLooking do
|
|
(
|
|
if (recordNames[recordNum] == findFilename and recordHeadActions[recordNum] != "delete") do
|
|
(
|
|
filesInP4List[fileNum] = true
|
|
unMatchedRecords[recordNum] = false
|
|
keepLooking = false
|
|
)
|
|
)
|
|
)
|
|
)
|
|
)
|
|
filesInP4List
|
|
),
|
|
|
|
--
|
|
-- fn files
|
|
-- desc Returns a list of all the files in a given directory that have not been deleted
|
|
--
|
|
fn files directory silent:true =
|
|
(
|
|
local p4FileList = #()
|
|
result = (run "files" directory silent:silent returnResult:true)
|
|
|
|
if ( (result != undefined) and (result.records.count > 0)) then
|
|
(
|
|
for i = 1 to result.records.count do
|
|
(
|
|
local record = result.Records[i]
|
|
if (record != undefined) then
|
|
(
|
|
local depotFile = record.Fields.Item["depotFile"]
|
|
local action = record.Fields.Item["action"]
|
|
|
|
if (action != "delete") then
|
|
append p4FileList depotFile
|
|
)
|
|
)
|
|
)
|
|
|
|
p4FileList
|
|
),
|
|
|
|
fn IsQueueEmpty =
|
|
(
|
|
addQueue.count==0
|
|
),
|
|
|
|
-- Used to add files to Perforce after export, queued up beforehand:
|
|
-- RsPerforce.addQueue is used if queue: argument is unsupplied
|
|
fn postExportAdd exclusive:false silent:false cancel:false changelistNum: queue: =
|
|
(
|
|
if (queue == unsupplied) do
|
|
(
|
|
queue = addQueue
|
|
)
|
|
|
|
if cancel or not RsProjectGetPerforceIntegration() do
|
|
(
|
|
return true
|
|
)
|
|
|
|
local retVal = true
|
|
local switches = #()
|
|
|
|
if (changelistNum != unsupplied and changelistNum != undefined) then
|
|
(
|
|
switches = #("-c", changelistNum as string)
|
|
)
|
|
|
|
-- Only add files that exist locally:
|
|
local addFiles = for filename in queue where (doesFileExist filename) collect filename
|
|
|
|
if (addFiles.count != 0) do
|
|
(
|
|
retVal = add addFiles exclusive:exclusive silent:silent switches:switches
|
|
|
|
-- Remove files we marked for add in perforce, as there could be some which are in the queue
|
|
-- which haven't been created yet and might get added later.
|
|
for fileIdx = queue.count to 1 by -1 do
|
|
(
|
|
if (findItem addFiles queue[fileIdx] != 0) do
|
|
(
|
|
deleteItem queue fileIdx
|
|
)
|
|
)
|
|
)
|
|
|
|
retVal
|
|
),
|
|
|
|
-- Add or edit wrapper
|
|
-- If queueAdd is true, add-files will be added to queue array, to be processed later by PostExportAdd
|
|
-- if queue is unsupplied, RsPerforce.addQueue array is used instead.
|
|
fn add_or_edit filenames exclusive:false silent:false queueAdd:false queue: switches:#() =
|
|
(
|
|
local retVal = true
|
|
|
|
if not isKindOf filenames Array do
|
|
(
|
|
filenames = #(filenames)
|
|
)
|
|
|
|
-- If Perforce integration is turned off, just make these files writable:
|
|
if not RsProjectGetPerforceIntegration() do
|
|
(
|
|
for filename in filenames where (doesFileExist filename) do
|
|
(
|
|
setFileAttribute filename #readOnly false
|
|
)
|
|
|
|
return true
|
|
)
|
|
|
|
-- Set queueAdd to true if queue is specified:
|
|
if (queue == unsupplied) then
|
|
(
|
|
queue = addQueue
|
|
)
|
|
else
|
|
(
|
|
queueAdd = true
|
|
)
|
|
|
|
if (filenames.count != 0) do
|
|
(
|
|
-- Check to see which filenames are already in Perforce:
|
|
local existingP4FileNums = exists filenames
|
|
|
|
local onServerFiles = for n = existingP4FileNums collect filenames[n]
|
|
local notOnServerFiles = for n = -existingP4FileNums collect filenames[n]
|
|
local notOnClientFiles = #()
|
|
local noPermissionFiles = #()
|
|
local notCheckedOutFiles = #()
|
|
|
|
if ( onServerFiles.count > 0 ) do
|
|
(
|
|
pushPrompt ("Perforce: Edit files: " + existingFiles as string)
|
|
retVal = run "edit" onServerFiles switches:switches silent:silent returnResult:true
|
|
popPrompt()
|
|
|
|
if (retVal == undefined) then (return false)
|
|
|
|
|
|
-- Get list of missing files:
|
|
local notOnClientWarn = "* - file(s) not on client."
|
|
local noSuchFileWarn = "* - no such file(s)."
|
|
local noPermissionWarn = "* - no permission for operation on file(s)."
|
|
|
|
for warn in retVal.warnings do
|
|
(
|
|
case of
|
|
(
|
|
(matchPattern warn pattern:notOnClientWarn):
|
|
(
|
|
append notOnClientFiles (substring warn 1 (warn.count - (notOnClientWarn.count - 1)))
|
|
)
|
|
(matchPattern warn pattern:noPermissionWarn):
|
|
(
|
|
append noPermissionFiles (substring warn 1 (warn.count - (noPermissionWarn.count - 1)))
|
|
)
|
|
(matchPattern warn pattern:noSuchFileWarn):
|
|
(
|
|
append notOnServerFiles (substring warn 1 (warn.count - (noSuchFileWarn.count - 1)))
|
|
)
|
|
)
|
|
)
|
|
)
|
|
|
|
-- Edit non-synced files:
|
|
if (notOnClientFiles.count != 0) do
|
|
(
|
|
-- Switch -k sets file-status to fully synced, even if it's not:
|
|
pushPrompt ("Perforce: Not on client, attempting to set as Synced: " + notOnClientFiles as string)
|
|
run "sync" notOnClientFiles switches:#("-k") silent:silent
|
|
popPrompt ()
|
|
|
|
-- Brings up warning-window if edit still fails:
|
|
pushPrompt ("Perforce: Retrying file Edit: " + notOnClientFiles as string)
|
|
retVal = run "edit" notOnClientFiles switches:switches silent:silent returnResult:true
|
|
popPrompt()
|
|
|
|
if (retVal != undefined) do
|
|
(
|
|
-- Anything that's still marked as Not On Client will need to be added to the Add list:
|
|
for warn in retVal.warnings where (matchPattern warn pattern:notOnClientWarn) do
|
|
(
|
|
append notOnServerFiles (substring warn 1 (warn.count - (notOnClientWarn.count - 1)))
|
|
)
|
|
)
|
|
)
|
|
|
|
retVal = true
|
|
|
|
-- Add missing files, or add them to queue to be processed later by PostExportAdd:
|
|
if (notOnServerFiles.count != 0) do
|
|
(
|
|
join NotCheckedOutFiles NotOnServerFiles
|
|
|
|
if queueAdd then
|
|
(
|
|
if not silent do
|
|
(
|
|
format "Queueing files for add: %\n" (notOnServerFiles as string)
|
|
)
|
|
|
|
join queue notOnServerFiles
|
|
)
|
|
else
|
|
(
|
|
retVal = run "add" notOnServerFiles switches:switches silent:silent
|
|
)
|
|
)
|
|
|
|
-- Warn if user doesn't have checkout permission for some files, and ask to make them writable instead:
|
|
if (NoPermissionFiles.count != 0) do
|
|
(
|
|
join NotCheckedOutFiles NoPermissionFiles
|
|
|
|
format "ERROR: No permission to checkout:\n"
|
|
for ThisFile in NoPermissionFiles do
|
|
(
|
|
format " %\n" ThisFile
|
|
gRsUlog.LogWarning ("No permission to checkout file: " + ThisFile)
|
|
)
|
|
)
|
|
)
|
|
|
|
if not retVal do (return false)
|
|
|
|
-- Set files as exclusive-checkouts:
|
|
if exclusive do
|
|
(
|
|
local existingFiles = for filename in filenames where ((findItem NotCheckedOutFiles filename) == 0) collect filename
|
|
|
|
if (existingFiles.count != 0) do
|
|
(
|
|
retVal = run "reopen" existingFiles switches:#("-t", "+l") silent:silent
|
|
)
|
|
)
|
|
|
|
return retVal
|
|
),
|
|
|
|
-- Mark file(s) for deletion on Perforce:
|
|
fn del filename silent:false =
|
|
(
|
|
run "delete" filename silent:silent
|
|
|
|
-- Delete existing file(s) if Perforce is disabled or files haven't been added to it:
|
|
if not isKindOf filename array do
|
|
(
|
|
filename = #(filename)
|
|
)
|
|
|
|
for item in filename where doesFileExist item do
|
|
(
|
|
setFileAttribute item #readOnly false
|
|
deleteFile item
|
|
)
|
|
),
|
|
|
|
fn revert filename unchangedOrUnsubmitted:false silent:false cl: =
|
|
(
|
|
local optns = #()
|
|
if unchangedOrUnsubmitted then
|
|
(
|
|
append optns "-a"
|
|
)
|
|
|
|
if unsupplied!=cl then
|
|
(
|
|
append optns ("-c"+cl as string)
|
|
)
|
|
|
|
if not isKindOf filename array do
|
|
(
|
|
filename = #(filename)
|
|
)
|
|
join optns filename
|
|
run "revert" optns silent:silent
|
|
),
|
|
|
|
-- Creates a new changelist, and returns the changelist number:
|
|
fn createChangelist description silent:true =
|
|
(
|
|
connect()
|
|
-- If Perforce integration is turned off, skip this:
|
|
if not RsProjectGetPerforceIntegration() do
|
|
(
|
|
return undefined
|
|
)
|
|
|
|
if not (connect()) do
|
|
(
|
|
format "Perforce login failed.\n"
|
|
return undefined
|
|
)
|
|
|
|
outVal = this.findChangelistByName description
|
|
if undefined!=outVal then
|
|
(
|
|
gRsUlog.LogMessage ("Changelist with description already existent, going to append!")
|
|
return outVal
|
|
)
|
|
|
|
-- Can't use max string directly here
|
|
local s = dotNetObject "System.String" description
|
|
local outVal = p4.CreatePendingChangelist s
|
|
|
|
if (outVal != undefined) do (outVal = outVal.number)
|
|
|
|
if not silent do
|
|
(
|
|
format "createChangelist: %\n" outVal
|
|
)
|
|
|
|
return outVal
|
|
),
|
|
|
|
-- Adds files to numbered changelist:
|
|
fn addToChangelist listNum filename silent:true =
|
|
(
|
|
if (listNum != undefined and listNum != unsupplied) then
|
|
(
|
|
run "reopen" filename switches:#("-c", listNum as string) silent:silent
|
|
)
|
|
else
|
|
(
|
|
gRsUlog.LogWarning ("Undefined/Unsupplied listNum given to addToChangelist in p4.ms for filename: " + filename as string)
|
|
)
|
|
),
|
|
|
|
fn addDeleteFilesToChangelist listNum filename silent:true =
|
|
(
|
|
if (listNum != undefined and listNum != unsupplied) then
|
|
(
|
|
run "delete" filename switches:#("-c", listNum as string) silent:silent
|
|
)
|
|
else
|
|
(
|
|
gRsUlog.LogWarning ("Undefined/Unsupplied listNum given to addDeleteFilesToChangelist in p4.ms for filename: " + filename as string)
|
|
)
|
|
),
|
|
|
|
-- Finds pending changelist with description matching Description:
|
|
fn findChangelistByName description silent:true =
|
|
(
|
|
-- If Perforce integration is turned off, skip this:
|
|
if not RsProjectGetPerforceIntegration() do
|
|
(
|
|
return undefined
|
|
)
|
|
|
|
if not gRsPerforce.connected() and not connect() then
|
|
return undefined
|
|
|
|
-- Get list of client's pending changelists:
|
|
local changelists = run "changelists" #("-c", p4.client, "-s", "pending", "-l") returnResult:true silent:silent
|
|
if (changelists == undefined) do (return false)
|
|
|
|
-- Search for changelist with matching
|
|
description += "\n" -- Descriptions have an extra linebreak
|
|
local listNum
|
|
|
|
for record in changelists.records while (listNum == undefined) do
|
|
(
|
|
if (matchPattern record.item["desc"] pattern:description) do
|
|
(
|
|
listNum = record.item["change"]
|
|
)
|
|
)
|
|
|
|
return listNum
|
|
),
|
|
|
|
-- Adds files to changelist matching the given description, creating a new one if necessary:
|
|
fn addToChangelistByName description filename exclusive:false silent:true queue: =
|
|
(
|
|
-- If Perforce integration is turned off, skip this:
|
|
if not RsProjectGetPerforceIntegration() do
|
|
(
|
|
return undefined
|
|
)
|
|
|
|
local retVal = add_or_edit filename silent:silent queue:queue
|
|
|
|
if not retVal do return false
|
|
|
|
local addToListNum = findChangelistByName description silent:silent
|
|
|
|
if (addToListNum == false) do (return false)
|
|
|
|
if (addToListNum == undefined) do
|
|
(
|
|
addToListNum = createChangelist description silent:silent
|
|
)
|
|
|
|
if (addToListNum == undefined) do (return false)
|
|
|
|
retVal = addToChangelist addToListNum filename silent:silent
|
|
|
|
return retVal
|
|
),
|
|
|
|
-- Sees if a given changelist has any files in it. If so, then it submits the changelist, otherwise it deletes it.
|
|
fn submitOrDeleteChangelist changelistNum reOpen:false silent:false =
|
|
(
|
|
local retVal = false
|
|
|
|
if changelistNum != undefined do
|
|
(
|
|
local clString = changelistNum as string
|
|
|
|
pushPrompt ("Submitting changelist " + clString)
|
|
|
|
-- Revert unchanged files before submitting:
|
|
if not reOpen do
|
|
(
|
|
run "revert" #("-a", "-c", clString) silent:true
|
|
)
|
|
|
|
local p4Res = run "describe" clString returnResult:true silent:true
|
|
|
|
-- Only submit if there are files to submit
|
|
if( p4Res != undefined and
|
|
p4Res.records.count>0 and
|
|
( p4Res.records[1].ArrayFields.Item["depotFile"] != undefined ) ) then
|
|
(
|
|
local args = #("-c", clString)
|
|
|
|
if reOpen do
|
|
(
|
|
-- Reopen files after submit, and don't submit unchanged files:
|
|
join args #("-r", "-f", "leaveunchanged")
|
|
)
|
|
|
|
if not silent do
|
|
(
|
|
format "Auto-checkin of changelist number: %\n" clString
|
|
)
|
|
|
|
-- Submit changelist:
|
|
retVal = run "submit" args silent:silent
|
|
)
|
|
else if (p4Res != undefined and
|
|
p4Res.records.count==0) then
|
|
(
|
|
gRsUlog.LogMessage ("Trying to \"submitOrDeleteChangelist\" with undefined CL number \""+clString+"\"." )
|
|
retVal = true
|
|
)
|
|
else
|
|
(
|
|
-- Delete empty changelist
|
|
retVal = run "change" #("-d", (changelistNum as string)) silent:true
|
|
)
|
|
|
|
popPrompt()
|
|
)
|
|
|
|
return retVal
|
|
),
|
|
|
|
-- Sees if a given changelist has any changed files in it. If not then it is deleted.
|
|
fn deleteChangelistIfItHasNoChanges changelistNum =
|
|
(
|
|
local retVal = true
|
|
|
|
if changelistNum != undefined do
|
|
(
|
|
local clString = changelistNum as string
|
|
|
|
pushPrompt ("Checking changelist " + clString)
|
|
|
|
-- Revert unchanged files
|
|
run "revert" #("-a", "-c", clString) silent:true
|
|
|
|
local p4Res = run "describe" clString returnResult:true silent:true
|
|
|
|
-- Only submit if there are files to submit
|
|
if( p4Res == undefined or
|
|
p4Res.records.count == 0 or
|
|
( p4Res.records[1].ArrayFields.Item["depotFile"] == undefined ) ) then
|
|
(
|
|
-- Delete empty changelist
|
|
retVal = run "change" #("-d", (changelistNum as string)) silent:true
|
|
)
|
|
|
|
popPrompt()
|
|
)
|
|
|
|
return retVal
|
|
),
|
|
|
|
-- Returns the time this file was last submitted:
|
|
fn fileSubmitTime filename silent:true doLogin:false =
|
|
(
|
|
local fileInfo = run "fstat" filename returnResult:true silent:silent doLogin:doLogin
|
|
|
|
if (fileInfo == undefined) do return undefined
|
|
|
|
local fileRecord= fileInfo.records[1]
|
|
|
|
if (fileRecord == undefined) do return undefined
|
|
|
|
local headTime = fileRecord.Item["headTime"]
|
|
|
|
if (headTime == undefined) do return undefined
|
|
|
|
-- Convert time from unix-epoch to dotnet ticks:
|
|
local headTimeTicks = Integer64 (((headTime as Integer64) * 10000000) + 621355968000000000)
|
|
|
|
local dnHeadTime = dotNetObject "system.dateTime" headTimeTicks
|
|
return dnHeadTime
|
|
),
|
|
|
|
--------------------------------------------------------------
|
|
-- Resolves checked-out files in list,
|
|
-- to show as latest versions:
|
|
--------------------------------------------------------------
|
|
fn resolveToLatest filenames =
|
|
(
|
|
if not (isKindOf filenames array) do (filenames = #(filenames))
|
|
|
|
local checkedOutNums = checkedOutForEdit filenames
|
|
local checkedOutFiles = for fileNum = checkedOutNums collect filenames[fileNum]
|
|
|
|
if (checkedOutFiles.count == 0) then
|
|
(
|
|
return true
|
|
)
|
|
else
|
|
(
|
|
sync checkedOutFiles silent:true
|
|
run "resolve" checkedOutFiles switches:#("-ay") silent:true
|
|
)
|
|
),
|
|
|
|
--------------------------------------------------------------
|
|
-- Tells user if files are read-only or not checked out,
|
|
-- and offers choice to check them out,
|
|
-- make them writable, or cancel
|
|
--------------------------------------------------------------
|
|
readOnlyP4Check_ignoreFiles = #(),
|
|
fn readOnlyP4Check filenames exclusive:true queue: switches:#() includeWritable:false =
|
|
(
|
|
|
|
local localFiles = for f in filenames where (RsFileExists f) collect f
|
|
|
|
local defBtn = if (RsAutomatedExport == false) then 2 else 1
|
|
local tout = if (RsAutomatedExport == false) then 30 else 1
|
|
|
|
local strMissingLocally = "Files appear to be missing locally but are present in perforce.\nThis can cause issues when exporting.\nDo you want to force sync these files?:\n"
|
|
local strWritable = "Files appear to be present locally but are marked as writable, so perforce cannot work with the file.\nDo you want to force sync and check out these files?\n***WARNING!*** This will clobber (overwrite) any local changes!"
|
|
|
|
local useStr = strMissingLocally
|
|
local useItems = filenames
|
|
|
|
if(localFiles.count >= filenames.count) then
|
|
(
|
|
useStr = strWritable
|
|
useItems = for f in filenames where (not (getFileAttribute f #readOnly)) collect f
|
|
)
|
|
|
|
if (RsQueryBoxMultiBtn useStr title:"File Query" timeout:tout labels:#("Yes", "No") defaultBtn:defBtn listItems:useItems width:500) == 1 then
|
|
(
|
|
gRsPerforce.sync filenames force:true
|
|
)
|
|
|
|
::RsMapExportCancelled = false
|
|
|
|
-- Filter out ignored filenames, but make sure they're writable:
|
|
filenames = for filename in filenames collect
|
|
(
|
|
if (findItem readOnlyP4Check_ignoreFiles filename) == 0 then filename else
|
|
(
|
|
if doesFileExist filename do
|
|
(
|
|
setFileAttribute filename #readOnly false
|
|
)
|
|
dontCollect
|
|
)
|
|
)
|
|
|
|
if (filenames.count == 0) do return true
|
|
|
|
local retVal = true
|
|
|
|
local notInP4Files = #()
|
|
local filesNotCheckedOut = #()
|
|
|
|
if RsProjectGetPerforceIntegration() then
|
|
(
|
|
-- Check to see which files are already in Perforce:
|
|
pushPrompt "Checking for existing Perforce files"
|
|
local existingP4FileNums = exists filenames
|
|
local existingP4Files = #()
|
|
|
|
-- Check bitarray to see what files do exist in perforce
|
|
for i = 1 to filenames.count do
|
|
(
|
|
if existingP4FileNums[i] then
|
|
(
|
|
append existingP4Files filenames[i]
|
|
)
|
|
else
|
|
(
|
|
append notInP4Files filenames[i]
|
|
)
|
|
)
|
|
popPrompt()
|
|
|
|
pushPrompt "Looking for checked-out files"
|
|
local filesAlreadyCheckedOut = checkedOutForEdit existingP4Files
|
|
|
|
-- Check writability of non-checked-out files:
|
|
for i = 1 to existingP4Files.count where not filesAlreadyCheckedOut[i] do
|
|
(
|
|
-- File isn't already checked out by the user:
|
|
-- AJM: A flag can be passed now to get files that are writeable locally to
|
|
-- still get included in the list of files that they don't have checked out. (.ped.zip files
|
|
-- being requested for this).
|
|
if (RsIsFileReadOnly existingP4Files[i] or includeWritable) do
|
|
(
|
|
append filesNotCheckedOut existingP4Files[i]
|
|
)
|
|
)
|
|
|
|
popPrompt()
|
|
)
|
|
else
|
|
(
|
|
-- If Perforce integration is turned off, make the export-files writable automatically:
|
|
for filename in filenames where doesFileExist filename do
|
|
(
|
|
setFileAttribute filename #readOnly false
|
|
)
|
|
)
|
|
|
|
local hasNotInP4 = (notInP4Files.count != 0)
|
|
local hasNotCheckedOut = (filesNotCheckedOut.count != 0)
|
|
local filesCount = notInP4Files.count + filesNotCheckedOut.count
|
|
|
|
-- Bring up query if any non-P4/readonly files were found:
|
|
if hasNotInP4 or hasNotCheckedOut do
|
|
(
|
|
local queryList = #()
|
|
|
|
for filename in notInP4Files do
|
|
(
|
|
append queryList #(filename, "not in Perforce")
|
|
)
|
|
for filename in filesNotCheckedOut do
|
|
(
|
|
append queryList #(filename, "not checked out")
|
|
)
|
|
|
|
local btnLabels = #("Checkout", "Make Writable", "Cancel")
|
|
local checkboxLabels = #()
|
|
local checkboxStates = #{}
|
|
|
|
if hasNotInP4 do
|
|
(
|
|
btnLabels[1] = if (notInP4Files.count == filesCount) then "Add" else "Add/Checkout"
|
|
|
|
local checkPlural = if (notInP4Files.count == 1) then "this file" else "these files"
|
|
append checkboxLabels ("Don't ask to add " + checkPlural + " again")
|
|
)
|
|
|
|
local queryMsg = stringStream ""
|
|
format "%" (if (filesCount == 1) then "This file is not " else "These files are not ") to:queryMsg
|
|
if hasNotInP4 do (format "in perforce" to:queryMsg)
|
|
if (hasNotInP4 and hasNotCheckedOut) do (format "/" to:queryMsg)
|
|
if hasNotCheckedOut do (format "checked out" to:queryMsg)
|
|
format ".\n\nDo you want to fix this, or cancel export?" to:queryMsg
|
|
|
|
local checkOutFiles
|
|
local makeWritable
|
|
local ignoreInFuture
|
|
|
|
if not RsAutomatedExport then (
|
|
local queryVal = RsQueryBoxMultiBtn (queryMsg as string) title:"Build-file Perforce-check warnings" timeout:30 defaultBtn:1 listItems:queryList \
|
|
checkLabels:checkboxLabels checkStates:checkboxStates width:500 \
|
|
labels:btnLabels tooltips:#("Check files out of Perforce, or add them if they're new.", "Make files locally writable", "Cancel export")
|
|
|
|
checkOutFiles = (queryVal == 1)
|
|
makeWritable = (queryVal == 2)
|
|
RsMapExportCancelled = (queryVal == 3)
|
|
ignoreInFuture = checkboxStates[1]
|
|
)
|
|
else
|
|
(
|
|
-- If automated then make files writable rather than trying to checkout.
|
|
-- Also saves putting up the dialog and then timing out waiting for next choice
|
|
-- if the files are already checked out.
|
|
checkOutFiles = false
|
|
makeWritable = true
|
|
ignoreInFuture = false
|
|
)
|
|
|
|
if not RsMapExportCancelled do
|
|
(
|
|
-- These files will no longer trigger "Add to Perforce?" warnings for this session:
|
|
if ignoreInFuture do
|
|
(
|
|
join readOnlyP4Check_ignoreFiles notInP4Files
|
|
)
|
|
|
|
-- Set queueAdd to true if queue is specified:
|
|
if (queue == unsupplied) then
|
|
(
|
|
queue = addQueue
|
|
)
|
|
|
|
if checkOutFiles and not RsAutomatedExport do
|
|
(
|
|
local success = add_or_edit filenames exclusive:exclusive switches:switches queueAdd:true queue:queue
|
|
|
|
-- If user currently has checked out files with non-latest local versions, resolve to the current versions:
|
|
resolveToLatest filenames
|
|
|
|
if not success do
|
|
(
|
|
local message = "Perforce errors were encountered.\nDo you want to make the files locally writable, or cancel export?"
|
|
|
|
local queryVal = RsQueryBoxMultiBtn message title:"Build-file Perforce-checkout errors" timeout:30 defaultBtn:2 \
|
|
labels:#("Make Writable", "Cancel") tooltips:#("Make files locally writable", "Cancel export")
|
|
|
|
makeWritable = (queryVal == 1)
|
|
|
|
if not makeWritable do (RsMapExportCancelled = true)
|
|
)
|
|
)
|
|
|
|
if makeWritable do
|
|
(
|
|
for filename in filenames where doesFileExist filename do
|
|
(
|
|
setFileAttribute filename #readOnly false
|
|
)
|
|
)
|
|
)
|
|
)
|
|
|
|
return retVal and (not RsMapExportCancelled)
|
|
),
|
|
|
|
--------------------------------------------------------------
|
|
-- Adds filenames to new named changelist,
|
|
-- then submits it
|
|
--------------------------------------------------------------
|
|
fn submitFilesWithText filenames changeText silent:false queue: =
|
|
(
|
|
pushPrompt "Creating new changelist..."
|
|
|
|
add_Or_Edit filenames silent:silent queue:queue
|
|
local changelistNum = createChangelist changeText silent:silent
|
|
addToChangelist changelistNum filenames silent:silent
|
|
|
|
local success = submitOrDeleteChangelist changelistNum silent:silent reOpen:true
|
|
|
|
popPrompt()
|
|
|
|
if not (success or silent) do
|
|
(
|
|
messageBox ("Failed to submit changelist " + changelistNum as string + "\n\nPlease check this in your pending changes - perhaps a file needs to be resolved?") title:"Error: Changelist submission failure"
|
|
)
|
|
|
|
return success
|
|
),
|
|
|
|
--------------------------------------------------------------
|
|
-- Gets changelist-description from user,
|
|
-- submits filenames to new changelist with that text
|
|
--------------------------------------------------------------
|
|
fn submitFilesGetText filenames silent:false =
|
|
(
|
|
newListText = filenames
|
|
|
|
rollout RsP4GetText "Perforce Changelist Text" width:400
|
|
(
|
|
label lblChangeText "Edit changelist comment:"
|
|
editText txtChangeText "" height:100
|
|
button btnOK "Okay" width:60 across:2
|
|
button btnCancel "Cancel" width:60
|
|
|
|
on RsP4GetText open do
|
|
(
|
|
local msg = stringStream ""
|
|
format "Submitting latest changes to: " to:msg
|
|
|
|
for filename in ::gRsPerforce.newListText do
|
|
(
|
|
format "% " (filenameFromPath filename) to:msg
|
|
)
|
|
|
|
txtChangeText.text = msg as string
|
|
|
|
::gRsPerforce.newListText = undefined
|
|
)
|
|
|
|
on btnOK pressed do
|
|
(
|
|
::gRsPerforce.newListText = txtChangeText.text
|
|
destroyDialog RsP4GetText
|
|
)
|
|
|
|
on btnCancel pressed do
|
|
(
|
|
destroyDialog RsP4GetText
|
|
)
|
|
)
|
|
|
|
createDialog RsP4GetText modal:true
|
|
|
|
if (newListText == undefined) then
|
|
(
|
|
return false
|
|
)
|
|
else
|
|
(
|
|
submitFilesWithText filenames newListText silent:silent
|
|
)
|
|
),
|
|
|
|
--------------------------------------------------------------
|
|
-- Deletes empty changelists which match the wildcard description
|
|
-- passed in.
|
|
--------------------------------------------------------------
|
|
fn deleteEmptyChangelists description =
|
|
(
|
|
if ( description == undefined ) do
|
|
(
|
|
return false
|
|
)
|
|
if (p4 == undefined) do
|
|
(
|
|
connect()
|
|
)
|
|
|
|
-- Find pending changelists for this user
|
|
-- -l is used for full length changelist descriptions
|
|
local p4RecordSet = run "changes" #("-l", "-s", "pending", "-u", p4.User) returnResult:true silent:true
|
|
|
|
changeListNumbers = #()
|
|
|
|
if ( p4RecordSet != undefined ) do
|
|
(
|
|
for record in p4RecordSet.Records do
|
|
(
|
|
if (matchPattern record.Item["desc"] pattern:description) do
|
|
(
|
|
append changeListNumbers record.Item["change"]
|
|
)
|
|
)
|
|
)
|
|
|
|
-- Find out about each changelist
|
|
if ( changeListNumbers.count > 0 ) do
|
|
(
|
|
local p4Res = run "describe" changeListNumbers returnResult:true silent:true
|
|
|
|
if ( p4Res != undefined ) do
|
|
(
|
|
for record in p4Res.records do
|
|
(
|
|
-- Only delete if there are no files in the changelist
|
|
if ( p4Res != undefined and ( record.ArrayFields.Item["depotFile"] == undefined ) ) then
|
|
(
|
|
run "change" #("-d", record.Item["change"]) silent:true
|
|
gRsULog.LogMessage ("Deleted old empty auto-changelist number: " + record.Item["change"])
|
|
)
|
|
)
|
|
)
|
|
)
|
|
),
|
|
|
|
|
|
--------------------------------------------------------------
|
|
-- ShouldGetHeadRevision files (= string or Array)
|
|
-- Fires a query box and give the user the current revision and asks if they want the latest head revision of one or multiple files.
|
|
--------------------------------------------------------------
|
|
|
|
fn ShouldGetHeadRevision files title:"File Query" =(
|
|
|
|
if not RsProjectGetPerforceIntegration() do
|
|
return false
|
|
|
|
local fname = #()
|
|
if (isKindOf files string) then (
|
|
if (files != "") then (append fname files)
|
|
)else if (isKindOf files Array) then (
|
|
fname = for f in files where (f != "") collect f
|
|
)
|
|
|
|
if(fname.count == 0) then(
|
|
gRsUlog.LogWarning "gRsPerforce.ShouldGetHeadRevision called with no files in file list"
|
|
return false
|
|
)
|
|
|
|
local fstats = ( gRsPerforce.getFileStats fname )
|
|
|
|
local outofdate = #()
|
|
local outofdateFileNamesToSync = #()
|
|
for i = 1 to fstats.count do(
|
|
local haveRev = fstats[i].Item["haveRev"]
|
|
local headRev = fstats[i].Item["headRev"]
|
|
|
|
if (haveRev != undefined) then (haveRev = haveRev as integer) else continue
|
|
if (headRev != undefined) then (headRev = headRev as integer) else continue
|
|
|
|
if haveRev < headRev then (
|
|
append outofdate #((RsMakeSafeSlashes fname[i]),haveRev,headRev)
|
|
append outofdateFileNamesToSync (RsMakeSafeSlashes fname[i])
|
|
)
|
|
|
|
)
|
|
|
|
local sPlural = if (outofdate.count == 1) then "" else "s"
|
|
local arePlural = if (outofdate.count == 1) then "is" else "are"
|
|
local thesePlural = if (outofdate.count == 1) then "this" else "these"
|
|
|
|
if (outofdate.count == 0 ) then
|
|
(
|
|
msg = stringstream ""
|
|
format "No file% % out of date\n" sPlural arePlural to:msg
|
|
gRsUlog.LogMessage msg
|
|
return false
|
|
)
|
|
ss = stringstream ""
|
|
format "There % % file% which % out of date. Would you like to sync % file%?" arePlural outofdate.count sPlural arePlural thesePlural sPlural to:ss
|
|
if (RsQueryBoxMultiBtn (ss as string) title:title timeout:20 labels:#("Yes", "No") defaultBtn:2 listTitles:#("File","Current Revision","Head revision") listItems:outofdate width:800) == 1 then (
|
|
gRsPerforce.sync outofdateFileNamesToSync
|
|
)
|
|
|
|
),
|
|
|
|
--------------------------------------------------------------
|
|
-- Returns either:
|
|
-- An array of FileMapping dotNetObjects if an array is supplied
|
|
-- OR
|
|
-- A single FileMapping dotNetObject if a single string supplied
|
|
--------------------------------------------------------------
|
|
fn getFileMapping files =
|
|
(
|
|
local returnVal = undefined
|
|
if ( fileMapping == undefined) then fileMapping = dotNetClass "RSG.SourceControl.Perforce.FileMapping"
|
|
|
|
if(isKindOf files Array) then
|
|
(returnVal = fileMapping.Create p4 files)
|
|
else
|
|
(returnVal = (fileMapping.Create p4 #(files))[1])
|
|
returnVal
|
|
),
|
|
|
|
--------------------------------------------------------------
|
|
-- Returns either a string or an array depending on what was passed.
|
|
-- Will match the number of array elements supplied whether they exist in p4 or not for easy matching whether files exist e.g.
|
|
-- Passing:
|
|
-- #("//depot/gta5/blah/blah/fake/dir/MadeUpName.bat", "X:\gta5\assets_ng\export\data\peds.pso.meta")
|
|
-- will return
|
|
-- #(undefined, "//depot/gta5/assets_ng/export/data/peds.pso.meta")
|
|
--
|
|
-- Usage
|
|
-- gRsPerforce.FileMapTo #depot @"x:\gta5\assets_ng\export\levels\gta5\_cityw\beverly_01\bh1_01_props.xml"
|
|
-- gRsPerforce.FileMapTo #local "//depot/gta5/assets_ng/export/levels/gta5/_cityw/beverly_01/bh1_01_props.xml"
|
|
--
|
|
--------------------------------------------------------------
|
|
fn FileMapTo mapTo fname =
|
|
(
|
|
if (not connected()) then
|
|
connect()
|
|
|
|
if(fileMapping == undefined) then
|
|
return undefined
|
|
|
|
local fMap = undefined
|
|
local returnVal = undefined
|
|
|
|
if(isKindOf fname Array) then
|
|
(
|
|
returnVal = #()
|
|
|
|
-- Fix all the paths in array
|
|
local slash = "/"
|
|
if(mapTo as name) == #depot do( slash = "" )
|
|
|
|
local tmpArr = #()
|
|
for f in fname do(
|
|
append tmpArr ( slash + (RsMakeSafeSlashes f))
|
|
)
|
|
-- clear original
|
|
fname = #()
|
|
join fname tmpArr
|
|
|
|
try(
|
|
fMap = (getFileMapping fname)
|
|
)catch(
|
|
gRsULog.LogError("Exception: " + getCurrentException())
|
|
return undefined
|
|
)
|
|
|
|
localNameArray = #()
|
|
depotNameArray = #()
|
|
if(fMap != undefined) then (
|
|
for i in fMap do(
|
|
append depotNameArray (i.DepotFilename)
|
|
append localNameArray (i.LocalFilename)
|
|
)
|
|
)else(
|
|
local arr = #()
|
|
-- try to pass an array of the same size back.
|
|
for i = 1 to fname.count do( append arr undefined )
|
|
)
|
|
-- get bitarray of existing files.
|
|
bits = exists localNameArray
|
|
|
|
-- Append only existing files.
|
|
for f = 1 to bits.count do(
|
|
if(bits[f] == true) then
|
|
if(mapTo as Name) == #depot then (append returnVal depotNameArray[f]) else (append returnVal localNameArray[f])
|
|
else
|
|
(append returnVal undefined)
|
|
)
|
|
|
|
)else(
|
|
|
|
local slash = "/"
|
|
if(mapTo as name) == #depot do( slash = "" )
|
|
fname = slash + (RsMakeSafeSlashes fname)
|
|
try(
|
|
fMap = getFileMapping fname
|
|
)catch(
|
|
format "Exception: %\n" getCurrentException()
|
|
return undefined
|
|
)
|
|
if(fMap != undefined) then (
|
|
local bits = exists(#(fMap.LocalFilename))
|
|
|
|
if(bits[1] == true) then
|
|
returnVal = if(mapTo as name) == #depot then fMap.DepotFilename else fMap.LocalFilename
|
|
else
|
|
returnVal = undefined
|
|
|
|
)else(
|
|
returnVal = undefined
|
|
)
|
|
)
|
|
returnVal
|
|
),
|
|
|
|
--------------------------------------------------------------
|
|
-- Replacement for old function using the new FileMapTo function
|
|
--------------------------------------------------------------
|
|
fn local2depot fname = (
|
|
FileMapTo #depot fname
|
|
),
|
|
|
|
--------------------------------------------------------------
|
|
-- Unused at the moment but put in anyway.
|
|
--------------------------------------------------------------
|
|
fn depot2local fname = (
|
|
FileMapTo #local fname
|
|
)
|
|
)
|
|
|
|
fn ShutdownP4 =
|
|
(
|
|
try
|
|
(
|
|
gRsPerforce.p4.dispose()
|
|
--print "Disposing current Perforce object..."
|
|
)
|
|
catch ()
|
|
)
|
|
|
|
-- Make sure we clear our last instance of the P4 object otherwise we leak as bad as my kitchen sink does. A bucket won't help this scenario though!
|
|
if (gRsPerforce != undefined) then ShutdownP4()
|
|
|
|
gRsPerforce = RsPerforce()
|
|
|
|
callbacks.removeScripts id:#RsP4Callbacks
|
|
callbacks.addScript #preSystemShutdown "ShutdownP4()" id:#RsP4Callbacks
|
|
|
|
-- pipeline/util/p4.ms
|