// Get status for a single file (non recursive). // Will return valid status even if the file has nothing to show (has no changes). // If error happened, invalid status data will be returned (check statusData.IsValid). public static SVNAsyncOperation<SVNStatusData> GetStatusAsync(string path, bool offline, bool fetchLockDetails = true, int timeout = COMMAND_TIMEOUT) { // If default timeout, give some more time for online operations, just in case. if (timeout == COMMAND_TIMEOUT && !offline) { timeout *= 2; } var options = new SVNStatusDataOptions() { Depth = SVNStatusDataOptions.SearchDepth.Empty, Timeout = timeout, RaiseError = false, Offline = offline, FetchLockOwner = fetchLockDetails, // If offline, this is ignored. }; return SVNAsyncOperation<SVNStatusData>.Start(op => { var statusData = GetStatuses(path, options).FirstOrDefault(); // If no path was found, error happened. if (!statusData.IsValid) { // Fallback to unversioned as we don't touch them. statusData.Status = VCFileStatus.Unversioned; } return statusData; }); }
// Executed in a worker thread. protected override GuidStatusDatasBind[] GatherDataInThread() { var statusOptions = new SVNStatusDataOptions() { Depth = SVNStatusDataOptions.SearchDepth.Infinity, RaiseError = false, Timeout = WiseSVNIntegration.ONLINE_COMMAND_TIMEOUT * 2, Offline = !SVNPreferencesManager.Instance.DownloadRepositoryChanges && !SVNPreferencesManager.Instance.ProjectPrefs.EnableAutoLocking, FetchLockOwner = true, }; // Will get statuses of all added / modified / deleted / conflicted / unversioned files. Only normal files won't be listed. var statuses = WiseSVNIntegration.GetStatuses("Assets", statusOptions) #if UNITY_2018_4_OR_NEWER .Concat(WiseSVNIntegration.GetStatuses("Packages", statusOptions)) #endif .Where(s => s.Status != VCFileStatus.Missing) .ToList(); for (int i = 0, count = statuses.Count; i < count; ++i) { var statusData = statuses[i]; // Statuses for entries under unversioned directories are not returned. Add them manually. if (statusData.Status == VCFileStatus.Unversioned && Directory.Exists(statusData.Path)) { try { var paths = Directory.EnumerateFileSystemEntries(statusData.Path, "*", SearchOption.AllDirectories) .Where(path => !WiseSVNIntegration.IsHiddenPath(path)) ; statuses.AddRange(paths .Select(path => path.Replace(WiseSVNIntegration.ProjectRoot, "")) .Select(path => new SVNStatusData() { Status = VCFileStatus.Unversioned, Path = path, LockDetails = LockDetails.Empty }) ); } catch (Exception) { // Files must have changed while scanning. Nothing we can do. } } } // HACK: the base class works with the DataType for pending data. Guid won't be used. return(statuses .Where(s => statuses.Count < SanityStatusesLimit || // Include everything when below the limit s.Status == VCFileStatus.Added || s.Status == VCFileStatus.Modified || s.Status == VCFileStatus.Conflicted ) .Select(s => new GuidStatusDatasBind() { MergedStatusData = s }) .ToArray()); }
// Get statuses of files based on the options you provide. // NOTE: data is returned ONLY for folders / files that has something to show (has changes, locks or remote changes). // If used with non-recursive option it will return single data with normal status (if non). // NOTE2: this is a synchronous operation. // If you use it in online mode it might freeze your code for a long time. // To avoid this, use the Async version! public static IEnumerable<SVNStatusData> GetStatuses(string path, SVNStatusDataOptions options) { // File can be missing, if it was deleted by svn. //if (!File.Exists(path) && !Directory.Exists(path)) { // if (!Silent) { // EditorUtility.DisplayDialog("SVN Error", "SVN error happened while processing the assets. Check the logs.", "I will!"); // } // throw new IOException($"Trying to get status for file {path} that does not exist!"); //} var depth = options.Depth == SVNStatusDataOptions.SearchDepth.Empty ? "empty" : "infinity"; var offline = options.Offline ? string.Empty : "-u"; var result = ShellUtils.ExecuteCommand(SVN_Command, $"status --depth={depth} {offline} \"{SVNFormatPath(path)}\"", options.Timeout); if (!string.IsNullOrEmpty(result.error)) { if (!options.RaiseError) return Enumerable.Empty<SVNStatusData>(); string displayMessage; bool isCritical = IsCriticalError(result.error, out displayMessage); if (!string.IsNullOrEmpty(displayMessage) && !Silent && m_LastDisplayedError != displayMessage) { Debug.LogError($"{displayMessage}\n\n{result.error}"); m_LastDisplayedError = displayMessage; EditorUtility.DisplayDialog("SVN Error", displayMessage, "I will!"); } if (isCritical) { throw new IOException($"Trying to get status for file {path} caused error:\n{result.error}!"); } else { return Enumerable.Empty<SVNStatusData>(); } } // If no info is returned for path, the status is normal. Reflect this when searching for Empty depth. if (options.Depth == SVNStatusDataOptions.SearchDepth.Empty) { if (options.Offline && string.IsNullOrWhiteSpace(result.output)) { return Enumerable.Repeat(new SVNStatusData() { Status = VCFileStatus.Normal, Path = path, LockDetails = LockDetails.Empty }, 1); } // If -u is used, additional line is added at the end: // Status against revision: 14 if (!options.Offline && result.output.StartsWith("Status", StringComparison.Ordinal)) { return Enumerable.Repeat(new SVNStatusData() { Status = VCFileStatus.Normal, Path = path, LockDetails = LockDetails.Empty }, 1); } } return ExtractStatuses(result.output, options); }
// Get offline status for a single file (non recursive). This won't make requests to the repository. // Will return valid status even if the file has nothing to show (has no changes). // If error happened, invalid status data will be returned (check statusData.IsValid). public static SVNStatusData GetStatus(string path) { // Optimization: empty depth will return nothing if status is normal. // If path is modified, added, deleted, unversioned, it will return proper value. var statusOptions = new SVNStatusDataOptions(SVNStatusDataOptions.SearchDepth.Empty); var statusData = GetStatuses(path, statusOptions).FirstOrDefault(); // If no path was found, error happened. if (!statusData.IsValid) { // Fallback to unversioned as we don't touch them. statusData.Status = VCFileStatus.Unversioned; } return statusData; }
// Get statuses of files based on the options you provide. // NOTE: data is returned ONLY for folders / files that has something to show (has changes, locks or remote changes). // If used with non-recursive option it will return single data with normal status (if non). public static SVNAsyncOperation<IEnumerable<SVNStatusData>> GetStatusesAsync(string path, bool recursive, bool offline, bool fetchLockDetails = true, int timeout = COMMAND_TIMEOUT) { // If default timeout, give some more time for online operations, just in case. if (timeout == COMMAND_TIMEOUT && !offline) { timeout *= 2; } var options = new SVNStatusDataOptions() { Depth = recursive ? SVNStatusDataOptions.SearchDepth.Infinity : SVNStatusDataOptions.SearchDepth.Empty, Timeout = timeout, RaiseError = false, Offline = offline, FetchLockOwner = fetchLockDetails, // If offline, this is ignored. }; return SVNAsyncOperation<IEnumerable<SVNStatusData>>.Start(op => GetStatuses(path, options)); }
// Executed in a worker thread. private void GatherSVNStatuses() { try { var statusOptions = new SVNStatusDataOptions() { Depth = SVNStatusDataOptions.SearchDepth.Infinity, RaiseError = false, Timeout = WiseSVNIntegration.COMMAND_TIMEOUT * 2, Offline = !SVNPreferencesManager.Instance.DownloadRepositoryChanges, FetchLockOwner = true, }; // Will get statuses of all added / modified / deleted / conflicted / unversioned files. Only normal files won't be listed. var statuses = WiseSVNIntegration.GetStatuses("Assets", statusOptions) // Deleted svn file can still exist for some reason. Need to show it as deleted. // If file doesn't exists, skip it as we can't show it anyway. .Where(s => s.Status != VCFileStatus.Deleted || File.Exists(s.Path)) .Where(s => s.Status != VCFileStatus.Missing) .ToList(); for (int i = 0, count = statuses.Count; i < count; ++i) { var statusData = statuses[i]; // Statuses for entries under unversioned directories are not returned. Add them manually. if (statusData.Status == VCFileStatus.Unversioned && Directory.Exists(statusData.Path)) { var paths = Directory.EnumerateFileSystemEntries(statusData.Path, "*", SearchOption.AllDirectories) .Where(path => !WiseSVNIntegration.IsHiddenPath(path)) ; statuses.AddRange(paths .Select(path => path.Replace(WiseSVNIntegration.ProjectRoot, "")) .Select(path => new SVNStatusData() { Status = VCFileStatus.Unversioned, Path = path, LockDetails = LockDetails.Empty }) ); } } if (PendingUpdate == false) { throw new Exception("SVN thread finished work but the update is over?"); } m_PendingStatuses = statuses.ToArray(); } // Most probably the assembly got reloaded and the thread was aborted. catch (System.Threading.ThreadAbortException) { System.Threading.Thread.ResetAbort(); // Should always be true. if (PendingUpdate) { m_PendingStatuses = new SVNStatusData[0]; } } catch (Exception ex) { Debug.LogException(ex); // Should always be true. if (PendingUpdate) { m_PendingStatuses = new SVNStatusData[0]; } } }
// Get statuses of files based on the options you provide. // NOTE: data is returned ONLY for folders / files that has something to show (has changes, locks or remote changes). // If used with non-recursive option it will return single data with normal status (if non). public static SVNAsyncOperation<IEnumerable<SVNStatusData>> GetStatusesAsync(string path, SVNStatusDataOptions options) { return SVNAsyncOperation<IEnumerable<SVNStatusData>>.Start(op => GetStatuses(path, options)); }
private static IEnumerable<SVNStatusData> ExtractStatuses(string output, SVNStatusDataOptions options) { using (var sr = new StringReader(output)) { string line; while ((line = sr.ReadLine()) != null) { var lineLen = line.Length; // Last status was deleted / added+, so this is telling us where it moved to / from. Skip it. if (lineLen > 8 && line[8] == '>') continue; // Tree conflict "local dir edit, incoming dir delete or move upon switch / update" or similar. if (lineLen > 6 && line[6] == '>') continue; // If there are any conflicts, the report will have two additional lines like this: // Summary of conflicts: // Text conflicts: 1 if (line.StartsWith("Summary", StringComparison.Ordinal)) break; // If -u is used, additional line is added at the end: // Status against revision: 14 if (line.StartsWith("Status", StringComparison.Ordinal)) break; // All externals append separate sections with their statuses: // Performing status on external item at '...': if (line.StartsWith("Performing status", StringComparison.Ordinal)) continue; // If user has files in the "ignore-on-commit" list, this is added at the end plus empty line: // ---Changelist 'ignore-on-commit': ... if (string.IsNullOrEmpty(line)) continue; if (line.StartsWith("---", StringComparison.Ordinal)) break; // Rules are described in "svn help status". var statusData = new SVNStatusData(); statusData.Status = m_FileStatusMap[line[0]]; statusData.PropertyStatus = m_PropertyStatusMap[line[1]]; statusData.LockStatus = m_LockStatusMap[line[5]]; statusData.TreeConflictStatus = m_ConflictStatusMap[line[6]]; statusData.LockDetails = LockDetails.Empty; // 7 columns statuses + space; int pathStart = 7 + 1; if (!options.Offline) { // + remote status + revision pathStart += 13; statusData.RemoteStatus = m_RemoteStatusMap[line[8]]; } statusData.Path = line.Substring(pathStart); // NOTE: If you pass absolute path to svn, the output will be with absolute path -> always pass relative path and we'll be good. // If path is not relative, make it. //if (!statusData.Path.StartsWith("Assets", StringComparison.Ordinal)) { // // Length+1 to skip '/' // statusData.Path = statusData.Path.Remove(0, ProjectRoot.Length + 1); //} if (IsHiddenPath(statusData.Path)) continue; if (!options.Offline && options.FetchLockOwner) { if (statusData.LockStatus != VCLockStatus.NoLock && statusData.LockStatus != VCLockStatus.BrokenLock) { statusData.LockDetails = FetchLockDetails(statusData.Path, options.Timeout, options.RaiseError); } } yield return statusData; } } }