using UnityEditor; using UnityEngine; using System; using System.Collections.Generic; using System.Linq; using System.Text; using Perforce.P4; using System.Runtime.InteropServices; using System.Reflection; using System.IO; namespace P4Connect { /// <summary> /// Utility methods for P4Connect /// </summary> public class Utils { /// <summary> /// Returns true if running on windows (Case Insensitive file paths) /// </summary> public static bool IsCaseSensitive() { int p = (int)Environment.OSVersion.Platform; if ((p == 4) || (p == 6) || (p == 128)) // From mono documentation, "How to detect the execution platform". return false; return true; } public static bool IsRunningUnity5() { if (Application.unityVersion.Trim()[0] == '5') return true; return false; } /// <summary> /// Retrieves a displayable filename from a perforce file spec /// </summary> public static string GetFilename(FileAndMeta aFileAndMeta) { return GetFilename(aFileAndMeta, Config.ShowPaths); } /// <summary> /// Retrieves a displayable filename from a perforce file spec /// </summary> public static string GetFilename(FileAndMeta aFileAndMeta, bool aShowPath) { // Base version uses whatever prefs the user set if (aFileAndMeta.File != null) return GetFilename(aFileAndMeta.File, aShowPath); else if (aFileAndMeta.Meta != null) return GetFilename(aFileAndMeta.Meta, aShowPath); else return ""; } /// <summary> /// Retrieves a displayable filename from a perforce file spec /// </summary> public static string GetFilename(Perforce.P4.FileSpec aFile) { // Base version uses whatever prefs the user set return GetFilename(aFile, Config.ShowPaths); } /// <summary> /// Retrieves a displayable filename from a perforce file spec /// </summary> public static string GetFilename(Perforce.P4.FileSpec aFile, bool aShowPath) { string filename = ""; if (aFile.ClientPath != null) filename = aFile.ClientPath.Path; else if (aFile.DepotPath != null) filename = aFile.DepotPath.Path; else filename = aFile.LocalPath.Path; if (!filename.EndsWith("...") && !aShowPath) { // parse out the file name filename = System.IO.Path.GetFileName(filename); } return UnescapeFilename(filename); } /// <summary> /// Separates the files based on their extension /// </summary> public static void FilterTextAndBinary(List<FileSpec> aFiles, List<FileSpec> aOutText, List<FileSpec> aOutBinary, List<FileSpec> aOutUnknown) { foreach (var file in aFiles) { switch (FilterTextAndBinary(file)) { case FilterTextAndBinaryResult.Binary: aOutBinary.Add(file); break; case FilterTextAndBinaryResult.Text: aOutText.Add(file); break; case FilterTextAndBinaryResult.Unknown: aOutUnknown.Add(file); break; } } } public enum FilterTextAndBinaryResult { Text, Binary, Unknown } /// <summary> /// Separates the files based on their extension /// </summary> public static FilterTextAndBinaryResult FilterTextAndBinary(FileSpec aFile) { bool defaultToBinary = false; if (EditorSettings.serializationMode == SerializationMode.ForceBinary || EditorSettings.serializationMode == SerializationMode.Mixed) { defaultToBinary = true; } string extension = System.IO.Path.GetExtension(GetFilename(aFile, false)).ToLowerInvariant(); // Certain files are forced as text! if (extension == ".meta" || extension == ".txt" || extension == ".html" || extension == ".htm" || extension == ".xml" || extension == ".bytes" || extension == ".json" || extension == ".csv" || extension == ".yaml" || extension == ".fnt" || extension == ".js" || extension == ".cs" || extension == ".sln" || extension == ".csproj" || extension == ".compute" || extension == ".unityproj") { return FilterTextAndBinaryResult.Text; } // Others depend on the serialization setting else if (extension == ".scene" || extension == ".prefab" || extension == ".mat" || extension == ".cubemap" || extension == ".flare" || extension == ".rendertexture" || extension == ".controller" || extension == ".anim" || extension == ".overridecontroller" || extension == ".mask" || extension == ".physicmaterial" || extension == ".physicmaterial2d" || extension == ".guiskin" || extension == ".fontsettings") { if (defaultToBinary) { return FilterTextAndBinaryResult.Binary; } else { return FilterTextAndBinaryResult.Text; } } // And the rest, we leave up to perforce to determine. else { return FilterTextAndBinaryResult.Unknown; } } /// <summary> /// Escape filenames for perforce /// </summary> public static string EscapeFilename(string aFilename) { return aFilename.Replace("%", "%25").Replace("@", "%40").Replace("#", "%23").Replace("*", "%2A"); } /// <summary> /// Unescape filenames for perforce /// </summary> public static string UnescapeFilename(string aFilename) { return aFilename.Replace("%25", "%").Replace("%40", "@").Replace("%23", "#").Replace("%2A", "*"); } public static void CheckForMissingFilesAndWarn(IList<FileSpec> aSubmitted, IList<FileSpec> aRetrieved, string aErrorMessage) { if (aSubmitted != null && aSubmitted.Count > 0) { // Make a copy of the submitted list List<FileSpec> remainder = new List<FileSpec>(aSubmitted); // Remove special entries remainder.RemoveAll(spec => spec == null || (spec.ClientPath != null && spec.ClientPath.Path != null && spec.ClientPath.Path.EndsWith("..."))); // Remove all the files we did get back if (aRetrieved != null) { foreach (var spec in aRetrieved) { for (int i = 0; i < remainder.Count; ++i) { if (spec.LocalPath.Path.EndsWith(remainder[i].LocalPath.Path)) { remainder.RemoveAt(i); } } } } if (remainder.Count > 0) { StringBuilder builder = new StringBuilder(); builder.AppendLine(aErrorMessage); foreach (var spec in remainder) { if (spec != null) { builder.AppendLine("\t" + GetFilename(spec, true)); } } string errorMsg = builder.ToString(); Debug.LogWarning("P4Connect - " + errorMsg); EditorUtility.DisplayDialog("P4Connect - Error", errorMsg, "Ok"); } } } /// <summary> /// Logs a message to the console, formatting the filename properly /// </summary> public static void LogFile(FileAndMeta aFileAndMeta, string aMsg) { if (aFileAndMeta.File != null) { string filename = GetFilename(aFileAndMeta.File); if (aFileAndMeta.Meta != null) filename += " (and meta)"; Debug.Log("P4Connect - " + string.Format(aMsg, filename)); } else { if (aFileAndMeta.Meta != null) { string filename = GetFilename(aFileAndMeta.Meta); Debug.Log("P4Connect - " + string.Format(aMsg, filename)); } else { Debug.LogError("null file AND meta passed in to LogFile()"); } } } /// <summary> /// Logs a message to the console, formatting the filename properly /// </summary> public static void LogFile(Perforce.P4.FileSpec aFile, string aMsg) { LogFile(new FileAndMeta(aFile, null), aMsg); } /// <summary> /// Logs a message to the console, formatting the filename properly /// </summary> public static void LogFiles(IList<Perforce.P4.FileSpec> aFiles, string aMsg) { if (aFiles != null) { List<FileAndMeta> fileAndMetas = GetFileAndMetas(aFiles); LogFiles(fileAndMetas, aMsg); } } /// <summary> /// Logs a message to the console, formatting the filename properly /// </summary> public static void LogFiles(IList<FileAndMeta> aFilesAndMetas, string aMsg) { if (aFilesAndMetas != null && aFilesAndMetas.Count > 0) { if (aFilesAndMetas.Count == 1) { LogFile(aFilesAndMetas[0], aMsg); } else { StringBuilder builder = new StringBuilder(); builder.Append(aFilesAndMetas.Count.ToString()); builder.Append(" assets.\n"); foreach (var fileAndMeta in aFilesAndMetas) { if (fileAndMeta.File != null) { string filename = GetFilename(fileAndMeta.File, true); if (fileAndMeta.Meta != null) filename += " (and meta)"; builder.Append(filename); builder.Append("\n"); } else { if (fileAndMeta.Meta != null) { string filename = GetFilename(fileAndMeta.Meta, true); builder.Append(filename); builder.Append("\n"); } else { Debug.LogError("null file AND meta passed in to LogFile()"); } } } builder.Remove(builder.Length - 1, 1); Debug.Log("P4Connect - " + string.Format(aMsg, builder.ToString())); } } } /// <summary> /// Logs a message to the console, formatting the filename properly /// </summary> public static void LogFile(FileAndMeta aFileAndMeta, FileAndMeta aMoveToFileAndMeta, string aMsg, bool aShowPath) { if (aFileAndMeta.File != null && aMoveToFileAndMeta.File != null) { string filename = GetFilename(aFileAndMeta.File, aShowPath); string moveToFilename = GetFilename(aMoveToFileAndMeta.File, aShowPath); if (aFileAndMeta.Meta != null) filename += " (and meta)"; Debug.Log("P4Connect - " + string.Format(aMsg, filename, moveToFilename)); } else { if (aFileAndMeta.Meta != null && aMoveToFileAndMeta.Meta != null) { string filename = GetFilename(aFileAndMeta.Meta, aShowPath); string moveToFilename = GetFilename(aMoveToFileAndMeta.Meta, aShowPath); Debug.Log("P4Connect - " + string.Format(aMsg, filename, moveToFilename)); } else { Debug.LogError("null file AND meta passed in to LogFile()"); } } } /// <summary> /// Logs a message to the console, formatting the filename properly /// </summary> public static void LogFiles(IList<FileAndMeta> aFilesAndMetas, IList<FileAndMeta> aMoveToFilesAndMetas, string aMsg) { if (aFilesAndMetas != null && aMoveToFilesAndMetas != null && aFilesAndMetas.Count > 0 && aMoveToFilesAndMetas.Count > 0) { if (aFilesAndMetas.Count == 1 && aMoveToFilesAndMetas.Count == 1) { LogFile(aFilesAndMetas[0], aMoveToFilesAndMetas[0], aMsg); } else { StringBuilder builderFrom = new StringBuilder(); foreach (var fileAndMeta in aFilesAndMetas) { if (fileAndMeta.File != null) { string filename = GetFilename(fileAndMeta.File, true); if (fileAndMeta.Meta != null) filename += " (and meta)"; builderFrom.Append(filename); builderFrom.Append("\n"); } else { if (fileAndMeta.Meta != null) { string filename = GetFilename(fileAndMeta.Meta, true); builderFrom.Append(filename); builderFrom.Append("\n"); } else { Debug.LogError("null file AND meta passed in to LogFile()"); } } } builderFrom.Remove(builderFrom.Length - 1, 1); Debug.Log("P4Connect - " + string.Format(aMsg, aFilesAndMetas.Count.ToString(), "a new location") + "\n" + builderFrom.ToString()); } } } /// <summary> /// Logs a message to the console, formatting the filename properly /// </summary> public static void LogFiles(IList<FileSpec> aFiles, IList<FileAndMeta> aMoveToFilesAndMetas, string aMsg) { if (aFiles != null && aMoveToFilesAndMetas != null) { List<FileAndMeta> fileAndMetas = GetFileAndMetas(aFiles); LogFiles(fileAndMetas, aMoveToFilesAndMetas, aMsg); } } /// <summary> /// Logs a message to the console, formatting the filename properly /// </summary> public static void LogFile(FileAndMeta aFileAndMeta, FileAndMeta aMoveToFileAndMeta, string aMsg) { LogFile(aFileAndMeta, aMoveToFileAndMeta, aMsg, Config.ShowPaths); } /// <summary> /// Logs a message to the console, formatting the filename properly /// </summary> public static void LogFile(Perforce.P4.FileSpec aFile, Perforce.P4.FileSpec aMoveToFile, string aMsg, bool aShowPath) { LogFile(new FileAndMeta(aFile, null), new FileAndMeta(aMoveToFile, null), aMsg, aShowPath); } /// <summary> /// Logs a message to the console, formatting the filename properly /// </summary> public static void LogFile(Perforce.P4.FileSpec aFile, Perforce.P4.FileSpec aMoveToFile, string aMsg) { LogFile(aFile, aMoveToFile, aMsg, Config.ShowPaths); } /// <summary> /// Logs a warning to the console, formatting the filename properly /// </summary> public static void LogFileWarning(Perforce.P4.FileSpec aFile, string aMsg) { Debug.LogWarning("P4Connect - " + GetFilename(aFile) + aMsg); } /// <summary> /// Checks whether the filepath can be used by perforce /// </summary> public static bool IsFilePathValid(string aFilePath) { bool ret = true; char[] forbiddenChars = new char[] { '@', '#', '*', '%' }; for (int i = 0; ret && i < forbiddenChars.Length; ++i) { if (aFilePath.Contains(forbiddenChars[i])) { ret = false; } } return ret; } public static string ClientPathToLocalPath(string aClientPath) { string ClientPrefix = "//" + Config.Workspace; return aClientPath.Substring(ClientPrefix.Length + 1).Replace('/', System.IO.Path.DirectorySeparatorChar); } public static string ClientPathToFullPath(string aClientPath) { return LocalPathToFullPath(ClientPathToLocalPath(aClientPath)); } public static string DepotPathToLocalPath(PerforceConnection aConnection, FileSpec aDepotPath) { IList<FileSpec> mapping = aConnection.P4Client.GetClientFileMappings(null, aDepotPath); if (mapping != null && mapping.Count > 0) { return ClientPathToLocalPath(mapping[0].ClientPath.Path); } else { return ""; } } public static string DepotPathToFullPath(PerforceConnection aConnection, FileSpec aDepotPath) { IList<FileSpec> mapping = aConnection.P4Client.GetClientFileMappings(null, aDepotPath); if (mapping != null && mapping.Count > 0) { return ClientPathToFullPath(mapping[0].ClientPath.Path); } else { return ""; } } public static string AssetPathToLocalPath(string aAssetPath) { return System.IO.Path.Combine(Config.ProjectRoot, aAssetPath).Replace('/', System.IO.Path.DirectorySeparatorChar); } public static string LocalPathToAssetPath(string aLocalPath) { if (Config.ProjectRoot != "" && aLocalPath.StartsWith(Config.ProjectRoot)) { var cleanedRoot = Config.ProjectRoot.TrimEnd(System.IO.Path.DirectorySeparatorChar, System.IO.Path.AltDirectorySeparatorChar); if (aLocalPath.Length > cleanedRoot.Length + 1) { return aLocalPath.Substring(cleanedRoot.Length + 1).Replace(System.IO.Path.DirectorySeparatorChar, '/'); } else { return aLocalPath.Replace(System.IO.Path.DirectorySeparatorChar, '/'); } } else { return aLocalPath.Replace(System.IO.Path.DirectorySeparatorChar, '/'); } } public static string LocalPathToClientPath(string aLocalPath) { string ClientPrefix = "//" + Config.Workspace; return ClientPrefix + "/" + aLocalPath.Replace(System.IO.Path.DirectorySeparatorChar, '/'); } public static bool IsDirectory(string aAssetPath) { if (aAssetPath.EndsWith("...")) return true; else { string fullPath = LocalPathToFullPath(aAssetPath); if (System.IO.Directory.Exists(fullPath + System.IO.Path.DirectorySeparatorChar)) return true; else if (System.IO.Path.GetExtension(aAssetPath) == string.Empty) return true; else return false; } } public static string MetaFromAsset(string aAssetPath) { return aAssetPath + ".meta"; } public static string AssetFromMeta(string aMetaPath) { return aMetaPath.Substring(0, aMetaPath.Length - 5); } public static string AssetPathToFullPath(string aAssetPath) { return System.IO.Path.Combine(Main.RootPath, aAssetPath.Replace('/', System.IO.Path.DirectorySeparatorChar)); } public static string LocalPathToFullPath(string aLocalPath) { return AssetPathToFullPath(LocalPathToAssetPath(aLocalPath)); } public static string FullPathToAssetPath(string aFullPath) { return aFullPath.Substring(Main.RootPath.Length).Replace(System.IO.Path.DirectorySeparatorChar, '/'); } public static void GetFileAndMeta(string aName, out string aFileName, out string aMetaName) { // Is this a .meta file? if so we need to fetch the original file name from it if (aName.EndsWith(".meta")) { aMetaName = Utils.AssetPathToLocalPath(aName); aFileName = aMetaName.Substring(0, aMetaName.Length - 5); } // If it's not a meta file, generate the meta file name from it else { aFileName = Utils.AssetPathToLocalPath(aName); aMetaName = aFileName + ".meta"; } } public static string GetFileStateString(FileState aState) { string ret = "<unknown>"; switch (aState) { case FileState.None: ret = "<unknown>"; break; case FileState.InDepot: ret = "None"; break; case FileState.MarkedForEdit: ret = "Edit"; break; case FileState.MarkedForAdd: ret = "Add"; break; case FileState.MarkedForDelete: ret = "Delete"; break; case FileState.MarkedForAddMove: ret = "Move/Add"; break; case FileState.MarkedForDeleteMove: ret = "Move/Delete"; break; } return ret; } public static string GetStorageTypeString(StorageType aType) { string ret = "Other"; switch (aType) { case StorageType.Text: ret = "Text"; break; case StorageType.Binary: ret = "Binary"; break; } return ret; } public static List<string> GetDeepSelectedEffectivePaths() { var objects = Selection.GetFiltered(typeof(UnityEngine.Object), SelectionMode.Assets); List<string> assets = new List<string>(); foreach (var obj in objects) { string assetPath = AssetDatabase.GetAssetPath(obj); assets.Add(assetPath); } return assets; } public static void LaunchDiffAgainstHaveRev(string aAsset) { if (System.IO.File.Exists(Config.DiffToolPathname)) { Engine.PerformConnectionOperation(con => { FileSpec localSpec = FileSpec.LocalSpec(Utils.AssetPathToLocalPath(aAsset)); IList<FileMetaData> metaDataList = Engine.GetFileMetaData(con, null, localSpec); if (metaDataList != null && metaDataList.Count == 1) { BaseFileType baseFileType = metaDataList[0].Type.BaseType; if ((baseFileType == BaseFileType.Text) || (baseFileType == BaseFileType.Unicode) || (baseFileType == BaseFileType.UTF16)) { if (metaDataList[0].HaveRev != -1) { FileSpec headRevSpec = FileSpec.LocalSpec(Utils.AssetPathToLocalPath(aAsset), metaDataList[0].HaveRev); IList<string> contents = con.P4Depot.GetFileContents(null, headRevSpec); if (contents != null && contents.Count == 2) { // Create a temp file ad dump the content in there string haveRevPathname = System.IO.Path.Combine(System.IO.Path.GetTempPath(), System.IO.Path.GetFileName(headRevSpec.ToString())); System.IO.StreamWriter tempFile = System.IO.File.CreateText(haveRevPathname); tempFile.Write(contents[1]); tempFile.Close(); // Fetch the full pathname of the original file string localPathname = Utils.AssetPathToFullPath(aAsset); // Now run the diff tool string arguments = haveRevPathname + " " + localPathname; System.Diagnostics.Process.Start(Config.DiffToolPathname, arguments); } } else { EditorUtility.DisplayDialog("File not Synced", "You have never synced this file before and cannot compare it against your HAVE revision", "Ok"); } } } }); } else { EditorUtility.DisplayDialog("No Diff Utility Set", "You must point P4Connect to your favorite Diff Utility in Edit->Perforce Settings to use this feature.", "Ok"); } } public static bool MakeWriteable(string filename) { FileInfo fi = new FileInfo(filename); if (fi.Exists) { if (fi.IsReadOnly) { Debug.Log("MakeWriteable: " + filename); fi.IsReadOnly = false; // Make the file writeable anyway to avoid problems. return true; } } return false; } public static string GetBridgeDirectory() { return System.IO.Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) + System.IO.Path.DirectorySeparatorChar + (IntPtr.Size == 4 ? "x86" : "x86_64" ) + System.IO.Path.DirectorySeparatorChar; } public static string GetProjectDirectory() { string path = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); // Project_root/Assets/P4Connect/Editor return Path.GetDirectoryName(Path.GetDirectoryName(Path.GetDirectoryName(path))); // Project_root } public static string GetEditorAssetFullDirectory() { return System.IO.Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) + System.IO.Path.DirectorySeparatorChar; } static string GetFileSpecPath(FileSpec aSpec) { string filePath = ""; if (aSpec.LocalPath != null) filePath = aSpec.LocalPath.Path; else if (aSpec.ClientPath != null) filePath = aSpec.ClientPath.Path; else if (aSpec.DepotPath != null) filePath = aSpec.DepotPath.Path; return filePath; } /// <summary> /// Attempts to group files with their metas from a list of file specs /// </summary> public static List<FileAndMeta> GetFileAndMetas(IList<FileSpec> aFiles) { List<Perforce.P4.FileSpec> files = new List<Perforce.P4.FileSpec>(aFiles); List<FileAndMeta> fileAndMetas = new List<FileAndMeta>(); // Parse the list, seeing if files have metas and such while (files.Count > 0) { string filePath = GetFileSpecPath(files[0]); FileAndMeta assetAndMeta = new FileAndMeta(); if (filePath.EndsWith(".meta")) { // It's a meta file, is there an asset in the list as well? assetAndMeta.Meta = files[0]; string assetPath = Utils.AssetFromMeta(filePath); int assetIndex = files.FindIndex(fs => String.Compare(assetPath, GetFileSpecPath(fs), StringComparison.InvariantCultureIgnoreCase) == 0); if (assetIndex != -1) { assetAndMeta.File = files[assetIndex]; files.RemoveAt(assetIndex); } } else { // It's not a meta file, is there a meta as well? assetAndMeta.File = files[0]; string metaPath = Utils.MetaFromAsset(filePath); int metaIndex = files.FindIndex(fs => String.Compare(metaPath, GetFileSpecPath(fs), StringComparison.InvariantCultureIgnoreCase) == 0); if (metaIndex != -1) { assetAndMeta.Meta = files[metaIndex]; files.RemoveAt(metaIndex); } } // Finally, add the asset and meta to the list fileAndMetas.Add(assetAndMeta); // No matter what, remove the file files.RemoveAt(0); } return fileAndMetas; } /// <summary> /// Matches up a list of meta data to the list of files it was generated from /// </summary> public static void GetMatchingMetaData(List<FileSpec> aFiles, IList<FileMetaData> aMetaData, List<FileMetaData> aOutMeta) { Dictionary<string, FileMetaData> metaDataPaths = new Dictionary<string, FileMetaData>(); if (aMetaData != null) { foreach (var meta in aMetaData) { if (meta != null) { metaDataPaths[meta.LocalPath.Path.ToLowerInvariant()] = meta; } } } foreach (var file in aFiles) { if (file != null) { string lcPath = Utils.LocalPathToFullPath(file.LocalPath.Path).ToLowerInvariant(); FileMetaData meta = null; metaDataPaths.TryGetValue(lcPath, out meta); aOutMeta.Add(meta); } else { aOutMeta.Add(null); } } } public static bool IsSolutionProjectFile(string aPath) { return aPath.EndsWith(".csproj") || aPath.EndsWith(".sln"); } public static bool IsDirOrValidSubDirectoryOf(string aSubPath, string aAncestorPath) { System.IO.DirectoryInfo subDir = new System.IO.DirectoryInfo(aSubPath.TrimEnd(System.IO.Path.DirectorySeparatorChar, '/')); System.IO.DirectoryInfo ancestorDir = new System.IO.DirectoryInfo(aAncestorPath.TrimEnd(System.IO.Path.DirectorySeparatorChar, '/')); bool isParent = false; while (subDir != null) { if (string.Compare(subDir.FullName, ancestorDir.FullName, StringComparison.InvariantCultureIgnoreCase) == 0) { isParent = true; break; } else subDir = subDir.Parent; } return isParent; } } }
# | Change | User | Description | Committed | |
---|---|---|---|---|---|
#15 | 16210 | Norman Morse | Remove files from old locations | ||
#14 | 15383 | Norman Morse |
Improved Diagnostics, cleaned up unnecessary log output Moved some Dialog Initialization to OnEnable() Fixed Unity 5.1.1 incompatibilities Added Operation support for In Depot Deleted files |
||
#13 | 15244 | Norman Morse |
Better Directory support in "add" "get latest" "refresh" and other commands. Improved Project Root detection Various Bug Fixes and Clean up |
||
#12 | 15146 | Norman Morse |
Rewrote Config Dialog to resize well and work both vertically and horizontally. Fixed some internal issues in file handling. Removed .bytes from default type of "text" |
||
#11 | 15079 | Norman Morse |
Rewrote AssetStatusCache to Cache AssetStatuses and FileMetaData Fixed Edge conditions on Engine Operations Change Debug output defaults. Will now Checkout files which request to be "added" but which already exist in perforce. Output P4Connect version to log on initialization. |
||
#10 | 14801 | Norman Morse |
GA.9 changes. Fixed debug message exceptions Improved Pending Changes dialog for large changesets Changed configuration to allow saving configuration with Perforce disabled. Improved restart after recompile, automatically attempts connection now unless disabled. |
||
#9 | 14193 | Norman Morse |
GA.7 release Refactor Pending Changes Resolve Submit issues. Fixed Menu entries. Handle mismatched file and meta states. |
||
#8 | 13864 | Norman Morse | Final fixes for GA.5 release. | ||
#7 | 13813 | Norman Morse |
Changed Config to call CheckProjectRoot which creates ClientProjectRoot and DepotProjectRoot Filter files in changelists to include only files under the Project root. Run fstat on files in the default change to make sure we have complete metadata |
||
#6 | 12553 | Norman Morse |
integrate from internal main Build fixes for EC. Major changes to Configuration and re-initialization code. Bug fixes |
||
#5 | 12512 | Norman Morse | Integrate from Dev branch, preparing for Beta3 release | ||
#4 | 12362 | Norman Morse |
Added Debug Logging for p4log Fixed some path comparison issues. Created a CaseSensitivity test |
||
#3 | 12135 | Norman Morse |
Integrate dev branch changes into main. This code is the basiis of the 2.7 BETA release which provides Unity 5 compatibility |
||
#2 | 11447 | Norman Morse | Updates from internal main, moved to workshop | ||
#1 | 10940 | Norman Morse |
Inital Workshop release of P4Connect. Released under BSD-2 license |