Files
gtav-src/tools_ng/dcc/current/max2012/scripts/pipeline/util/p4.ms
T
2025-09-29 00:52:08 +02:00

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