using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
using JetBrains.Annotations;
using Unity.Cloud.Collaborate.Assets;
using Unity.Cloud.Collaborate.Models.Api;
using Unity.Cloud.Collaborate.Models.Enums;
using Unity.Cloud.Collaborate.Models.Structures;
using Unity.Cloud.Collaborate.Utilities;
using UnityEditor;
using UnityEditor.Collaboration;
using UnityEditor.Connect;
using UnityEditorInternal;
using UnityEngine;
using UnityEngine.Assertions;
using static UnityEditor.Collaboration.Collab;
using ProgressInfo = UnityEditor.Collaboration.ProgressInfo;
namespace Unity.Cloud.Collaborate.Models.Providers
{
internal class Collab : ISourceControlProvider
{
const string k_KServiceUrl = "developer.cloud.unity3d.com";
readonly RevisionsService m_RevisionsService;
///
public event Action UpdatedChangeList;
///
public event Action> UpdatedSelectedChangeList;
///
public event Action UpdatedConflictState;
///
public event Action UpdatedRemoteRevisionsAvailability;
///
public event Action UpdatedProjectStatus;
///
public event Action UpdatedOperationStatus;
///
public event Action UpdatedOperationProgress;
///
public event Action ErrorOccurred;
///
public event Action ErrorCleared;
readonly List m_Changes;
bool m_ConflictCachedState;
bool m_RemoteRevisionsAvailableState;
// History entry requesting bits and bobs.
readonly Queue<(int offset, int size, Action>)> m_HistoryRequests;
[NotNull]
IReadOnlyList m_HistoryEntries;
(int offset, int size)? m_HistoryEntriesCache;
[CanBeNull]
IHistoryEntry m_HistoryEntryCache;
int? m_HistoryEntryCountCache;
string m_TipCache;
[CanBeNull]
IErrorInfo m_ErrorInfo;
[CanBeNull]
IProgressInfo m_ProgressInfo;
ProjectStatus m_ProjectStatus;
public Collab()
{
m_RevisionsService = new RevisionsService(instance, UnityConnect.instance);
m_Changes = new List();
m_HistoryEntries = new List();
m_HistoryRequests = new Queue<(int offset, int size, Action>)>();
// Get initial values.
var info = instance.collabInfo;
m_ConflictCachedState = info.conflict;
m_RemoteRevisionsAvailableState = info.update;
m_TipCache = info.tip;
m_ProgressInfo = info.inProgress ? ProgressInfoFromCollab(instance.GetJobProgress(0)) : null;
m_ErrorInfo = instance.GetError(UnityConnect.UnityErrorFilter.ByContext | UnityConnect.UnityErrorFilter.ByChild, out var errInfo)
? ErrorInfoFromUnity(errInfo)
: null;
m_ProjectStatus = GetNewProjectStatus(info, UnityConnect.instance.connectInfo, UnityConnect.instance.projectInfo);
SetupEvents();
}
///
/// Setup events for the provider.
///
void SetupEvents()
{
// just connect notifier events.
instance.ChangeItemsChanged += OnChangeItemsChanged;
instance.SelectedChangeItemsChanged += OnSelectedChangeItemsChanged;
instance.RevisionUpdated_V2 += OnRevisionUpdated;
instance.CollabInfoChanged += OnCollabInfoChanged;
instance.JobsCompleted += OnJobsCompleted;
instance.ErrorOccurred_V2 += OnErrorOccurred;
instance.ErrorCleared += OnErrorCleared;
instance.StateChanged += OnCollabStateChanged;
UnityConnect.instance.StateChanged += OnUnityConnectStateChanged;
UnityConnect.instance.ProjectStateChanged += OnUnityConnectProjectStateChanged;
m_RevisionsService.FetchRevisionsCallback += OnReceiveHistoryEntries;
}
#region Callback & Helper Methods
///
/// Event handler for when the change list has changed.
///
/// New change list.
/// Whether or not the list is filtered. Should always be false.
void OnChangeItemsChanged(ChangeItem[] changes, bool isFiltered)
{
UpdateChanges(changes);
UpdatedChangeList?.Invoke();
}
///
/// WIP method to handle partial publish in collab.
///
/// Received changes.
/// Whether or not it's a partial publish. Should always be true.
void OnSelectedChangeItemsChanged(ChangeItem[] changes, bool isFiltered)
{
// This is used by selective commit. Assert all API calls to here are setting isFiltered to true !
Debug.Assert(isFiltered);
var selectedChanges = changes.Select(e => e.Path).ToList();
UpdatedSelectedChangeList?.Invoke(selectedChanges);
}
///
/// Event handler for when a revision has been created or updated. It's not called 100% of the time when a user
/// publishes a new revision.
///
/// New collab info.
/// New revision id.
/// Action that occured.
void OnRevisionUpdated(CollabInfo info, string rev, string action)
{
// Invalidate the cache.
m_HistoryEntriesCache = null;
m_HistoryEntryCache = null;
m_HistoryEntryCountCache = null;
// Send update event.
UpdatedHistoryEntries?.Invoke();
OnCollabInfoChanged(info);
}
void OnCollabInfoChanged(CollabInfo info)
{
// Update conflict state.
if (m_ConflictCachedState != info.conflict)
{
m_ConflictCachedState = info.conflict;
UpdatedConflictState?.Invoke(info.conflict);
}
// Update revisions available state.
if (m_RemoteRevisionsAvailableState != info.update)
{
m_RemoteRevisionsAvailableState = info.update;
UpdatedRemoteRevisionsAvailability?.Invoke(info.update);
}
// Update history list if the tip has changed.
if (m_TipCache != info.tip)
{
m_TipCache = info.tip;
// Invalidate the cache.
m_HistoryEntriesCache = null;
m_HistoryEntryCache = null;
m_HistoryEntryCountCache = null;
// Send update event.
UpdatedHistoryEntries?.Invoke();
}
// Update project state
UpdateProjectStatus(info, UnityConnect.instance.connectInfo, UnityConnect.instance.projectInfo);
// Update progress state.
if (info.inProgress)
{
// Get progress info.
var progressInfo = instance.GetJobProgress(0);
Assert.IsNotNull(progressInfo);
// Trigger start operation if not already known.
if (m_ProgressInfo == null)
{
UpdatedOperationStatus?.Invoke(true);
}
// Send progress info.
m_ProgressInfo = ProgressInfoFromCollab(progressInfo);
UpdatedOperationProgress?.Invoke(m_ProgressInfo);
}
else if (m_ProgressInfo != null)
{
// Signal end of job if job still exists
m_ProgressInfo = null;
UpdatedOperationStatus?.Invoke(false);
}
}
void OnJobsCompleted(CollabInfo info)
{
// NOTE: The first start of collab sends a completion event with no prior progress info.
// To handle this, skip sending completion event if there has been no start event.
if (m_ProgressInfo == null) return;
Assert.IsFalse(info.inProgress);
m_ProgressInfo = null;
UpdatedOperationStatus?.Invoke(false);
}
void OnErrorOccurred(UnityErrorInfo error)
{
if (m_ErrorInfo?.Code == error.code) return;
m_ErrorInfo = ErrorInfoFromUnity(error);
ErrorOccurred?.Invoke(m_ErrorInfo);
}
void OnErrorCleared()
{
m_ErrorInfo = null;
ErrorCleared?.Invoke();
}
///
/// On receiving history result, remove the oldest request, send the received data, then make the next request.
///
/// Result from the history request.
void OnReceiveHistoryEntries(RevisionsResult revisionsResult)
{
Assert.AreNotEqual(0, m_HistoryRequests.Count, "There should be a history request.");
var (offset, size, callback) = m_HistoryRequests.Dequeue();
// Get results, cache, then send them.
var results = revisionsResult?.Revisions.Select(RevisionToHistoryEntry).ToList();
if (results != null)
{
m_HistoryEntries = results;
m_HistoryEntriesCache = (offset, size);
m_HistoryEntryCountCache = revisionsResult.RevisionsInRepo;
callback(revisionsResult.RevisionsInRepo, m_HistoryEntries);
}
// Start the next request --> has to be outside of the callback.
EditorApplication.delayCall += () => ConsumeHistoryQueue();
}
///
/// Event handler for receiving unity connect project state changes.
///
/// New project info.
void OnUnityConnectProjectStateChanged(ProjectInfo projectInfo)
{
UpdateProjectStatus(instance.collabInfo, UnityConnect.instance.connectInfo, projectInfo);
}
///
/// Event handler for receiving collab state changes.
///
/// New collab state.
void OnCollabStateChanged(CollabInfo info)
{
OnCollabInfoChanged(info);
}
///
/// Event handler for receiving collab state changes.
///
/// UnityConnect connect info.
void OnUnityConnectStateChanged(ConnectInfo connectInfo)
{
UpdateProjectStatus(instance.collabInfo, connectInfo, UnityConnect.instance.projectInfo);
}
///
/// Update cached ready value and send event if it has changed.
///
void UpdateProjectStatus(CollabInfo collabInfo, ConnectInfo connectInfo, ProjectInfo projectInfo)
{
var currentStatus = GetNewProjectStatus(collabInfo, connectInfo, projectInfo);
if (m_ProjectStatus == currentStatus) return;
m_ProjectStatus = currentStatus;
UpdatedProjectStatus?.Invoke(m_ProjectStatus);
}
///
/// Returns the current project status.
///
/// Current status of this project.
static ProjectStatus GetNewProjectStatus(CollabInfo collabInfo, ConnectInfo connectInfo, ProjectInfo projectInfo)
{
// No UPID.
if (!projectInfo.projectBound)
{
return ProjectStatus.Unbound;
}
if (!connectInfo.online)
{
return ProjectStatus.Offline;
}
if (connectInfo.maintenance || collabInfo.maintenance)
{
return ProjectStatus.Maintenance;
}
if (!connectInfo.loggedIn)
{
return ProjectStatus.LoggedOut;
}
if (!collabInfo.seat)
{
return ProjectStatus.NoSeat;
}
// UPID exists, but collab off.
if (!instance.IsCollabEnabledForCurrentProject())
{
return ProjectStatus.Bound;
}
// Waiting for collab to connect and be ready.
if (!instance.IsConnected() || !collabInfo.ready)
{
return ProjectStatus.Loading;
}
return ProjectStatus.Ready;
}
///
/// Consume the next entry on the history queue.
///
/// True if an entry was just inserted. Starts the consumption cycle.
void ConsumeHistoryQueue(bool afterEnqueue = false)
{
// Start consuming the queue if the first entry was just enqueued.
if (afterEnqueue && m_HistoryRequests.Count != 1) return;
// Can't consume an empty queue.
if (m_HistoryRequests.Count == 0) return;
var (offset, size, callback) = m_HistoryRequests.Peek();
// Execute next request. Discard if exception.
try
{
m_RevisionsService.GetRevisions(offset, size);
}
catch (Exception e)
{
Debug.LogException(e);
// Remove request and send failure callback.
m_HistoryRequests.Dequeue();
callback(null, null);
}
}
///
/// Make a history request.
///
/// Offset for the request to start from.
/// Target length of the resultant list.
/// Callback for the result.
void QueueHistoryRequest(int offset, int size, Action> callback)
{
m_HistoryRequests.Enqueue((offset, size, callback));
ConsumeHistoryQueue(true);
}
///
/// Update cache of converted change entries from provided collab changes.
///
/// Received list of changes from collab.
void UpdateChanges(IEnumerable changes)
{
m_Changes.Clear();
m_Changes.AddRange(changes.Select(change =>
new ChangeEntry(change.path, change.path, ChangeEntryStatusFromCollabState(change.state),
false, IsCollabStateFlagSet(change.state, CollabStates.kCollabConflicted | CollabStates.kCollabPendingMerge), change))
.Cast());
}
///
/// Update cache of converted change entries from provided collab changes.
///
/// Received list of changes from collab.
void UpdateChanges(IEnumerable changes)
{
m_Changes.Clear();
m_Changes.AddRange(changes.Select(change =>
new ChangeEntry(change.Path, change.Path, ChangeEntryStatusFromCollabState(change.State),
false, IsCollabStateFlagSet(change.State, CollabStates.kCollabConflicted | CollabStates.kCollabPendingMerge), change))
.Cast());
}
///
public bool GetRemoteRevisionAvailability()
{
// Return cached value.
return m_RemoteRevisionsAvailableState;
}
///
public bool GetConflictedState()
{
// Return cached value.
return m_ConflictCachedState;
}
///
public IProgressInfo GetProgressState()
{
// Return cached value.
return m_ProgressInfo;
}
///
public IErrorInfo GetErrorState()
{
return m_ErrorInfo;
}
///
public virtual ProjectStatus GetProjectStatus()
{
return m_ProjectStatus;
}
///
public void RequestChangeList(Action> callback)
{
var changes = instance.GetChangesToPublish_V2().changes;
UpdateChanges(changes);
callback(m_Changes);
// Also check for errors.
if (instance.GetError(UnityConnect.UnityErrorFilter.All, out var error) &&
(CollabErrorCode)error.code != CollabErrorCode.Collab_ErrNone)
{
ErrorOccurred?.Invoke(ErrorInfoFromUnity(error));
}
}
///
public void RequestPublish(string message, IReadOnlyList changeEntries = null)
{
var changeItems = changeEntries?.Select(EntryToChangeItem).ToArray();
instance.PublishAssetsAsync(message, changeItems);
ChangeItem EntryToChangeItem(IChangeEntry entry)
{
return entry.Tag as ChangeItem;
}
}
#endregion
#region SourceControlHistoryCommands
///
public event Action UpdatedHistoryEntries;
///
public void RequestHistoryEntry(string revisionId, Action callback)
{
// Return cached entry if possible.
if (m_HistoryEntryCache?.RevisionId == revisionId)
{
callback(m_HistoryEntryCache);
return;
}
// Ensure that a cleanup occurs in the case of an exception.
m_RevisionsService.FetchSingleRevisionCallback += OnFetchRevisionCallback;
try
{
m_RevisionsService.GetRevision(revisionId);
}
catch (Exception e)
{
Debug.LogException(e);
m_RevisionsService.FetchSingleRevisionCallback -= OnFetchRevisionCallback;
callback(null);
}
void OnFetchRevisionCallback(Revision? revision)
{
m_RevisionsService.FetchSingleRevisionCallback -= OnFetchRevisionCallback;
// Failing to find the revision can result in a null revision or an empty revisionID.
callback(string.IsNullOrEmpty(revision?.revisionID)
? null
: RevisionToHistoryEntry(revision.GetValueOrDefault()));
}
}
///
public void RequestHistoryPage(int offset, int pageSize, Action> callback)
{
// Return cached entry is possible.
if (m_HistoryEntriesCache?.offset == offset && m_HistoryEntriesCache?.size == pageSize)
{
callback(m_HistoryEntries);
return;
}
// Queue up the request.
QueueHistoryRequest(offset, pageSize, (_, r) => callback(r));
}
///
public void RequestHistoryCount(Action callback)
{
// Return cached value if possible.
if (m_HistoryEntryCountCache != null)
{
callback(m_HistoryEntryCountCache);
return;
}
QueueHistoryRequest(0, 0, (c, _) => callback(c));
}
///
public void RequestDiscard(IChangeEntry entry)
{
// Collab cannot revert a new file as it has nothing to go back to. So, instead we delete them.
if (entry.Status == ChangeEntryStatus.Added)
{
File.Delete(entry.Path);
// Notify ADB to refresh since a change has been made.
AssetDatabase.Refresh();
}
else
{
instance.RevertFile(entry.Path, true);
}
}
///
public void RequestBulkDiscard(IReadOnlyList entries)
{
var revertEntries = new List();
var deleteOccured = false;
foreach (var entry in entries)
{
// Collab cannot revert a new file as it has nothing to go back to. So, instead we delete them.
if (entry.Status == ChangeEntryStatus.Added)
{
File.Delete(entry.Path);
deleteOccured = true;
}
else
{
revertEntries.Add((ChangeItem)entry.Tag);
}
}
// If a change has been made, notify the ADB to refresh.
if (deleteOccured)
{
AssetDatabase.Refresh();
}
instance.RevertFiles(revertEntries.ToArray(), true);
}
///
public void RequestDiffChanges(string path)
{
instance.ShowDifferences(path);
}
///
public bool SupportsRevert { get; } = false;
///
public void RequestRevert(string revisionId, IReadOnlyList files)
{
throw new NotImplementedException();
}
///
public void RequestUpdateTo(string revisionId)
{
instance.Update(revisionId, true);
}
///
public void RequestRestoreTo(string revisionId)
{
instance.ResyncToRevision(revisionId);
}
///
public void RequestGoBackTo(string revisionId)
{
instance.GoBackToRevision(revisionId, false);
}
///
public void ClearError()
{
instance.ClearErrors();
}
///
public void RequestShowConflictedDifferences(string path)
{
if (UnityEditor.Collaboration.Collab.IsDiffToolsAvailable())
{
instance.ShowConflictDifferences(path);
}
else
{
Debug.Log(StringAssets.noMergeToolIsConfigured);
}
}
///
public void RequestChooseMerge(string path)
{
if (UnityEditor.Collaboration.Collab.IsDiffToolsAvailable())
{
instance.LaunchConflictExternalMerge(path);
}
else
{
Debug.Log(StringAssets.noMergeToolIsConfigured);
}
}
///
public void RequestChooseMine(string[] paths)
{
instance.SetConflictsResolvedMine(paths);
}
///
public void RequestChooseRemote(string[] paths)
{
instance.SetConflictsResolvedTheirs(paths);
}
///
public void RequestSync()
{
QueueHistoryRequest(0, 1, Callback);
void Callback(int? count, IReadOnlyList revisions)
{
if (revisions != null && revisions.Count > 0)
{
instance.Update(revisions[0].RevisionId, true);
}
else
{
Debug.LogError("Remote revision id is unknown. Please try again.");
}
}
}
///
public void RequestCancelJob()
{
instance.CancelJob(0);
}
///
public virtual void ShowServicePage()
{
SettingsService.OpenProjectSettings("Project/Services/Collaborate");
}
///
public void ShowLoginPage()
{
UnityConnect.instance.ShowLogin();
}
///
public void ShowNoSeatPage()
{
var unityConnect = UnityConnect.instance;
var env = unityConnect.GetEnvironment();
// Map environment to url - prod is special
if (env == "production")
env = "";
else
env += "-";
var url = "https://" + env + k_KServiceUrl
+ "/orgs/" + unityConnect.GetOrganizationId()
+ "/projects/" + unityConnect.GetProjectName()
+ "/unity-teams/";
Application.OpenURL(url);
}
///
public async void RequestTurnOnService()
{
try
{
await RequestTurnOnServiceInternal();
}
catch (Exception e)
{
Debug.LogException(e);
}
}
protected async Task RequestTurnOnServiceInternal()
{
Assert.IsTrue(Threading.IsMainThread, "This must be run on the main thread.");
// Fire up the update Genesis service flag request.
var http = new HttpClientHandler { CookieContainer = new CookieContainer() };
var client = new HttpClient(http);
var projectGuid = UnityConnect.instance.projectInfo.projectGUID;
var accessToken = UnityConnect.instance.GetAccessToken();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
client.DefaultRequestHeaders.TryAddWithoutValidation("X-UNITY-VERSION", InternalEditorUtility.GetFullUnityVersion());
var fullUrl = $"{UnityConnect.instance.GetConfigurationURL(CloudConfigUrl.CloudCore)}/api/projects/{projectGuid}/service_flags";
const string json = @"{ ""service_flags"": { ""collab"" : true} }";
var content = new StringContent(json, Encoding.UTF8, "application/json");
var response = await PutAsync(client, fullUrl, content);
// Success.
if (response?.StatusCode == HttpStatusCode.OK)
{
SaveAssets();
TurnOnCollabInternal();
}
// Error.
else if (response?.StatusCode == HttpStatusCode.Forbidden)
{
ShowCredentialsError();
}
else
{
ShowGeneralError();
}
}
protected virtual void SaveAssets()
{
instance.SaveAssets();
}
protected virtual Task PutAsync(HttpClient client, string fullUrl, StringContent content)
{
return client.PutAsync(fullUrl, content);
}
protected virtual void TurnOnCollabInternal()
{
// enable the server from the client..
instance.SetCollabEnabledForCurrentProject(true);
// persist by marking collab on in settings
PlayerSettings.SetCloudServiceEnabled("Collab", true);
}
protected virtual void ShowCredentialsError()
{
// TODO - ahmad :- Show an Error UI.
Debug.LogError("You need owner privilege to enable or disable collab.");
}
protected virtual void ShowGeneralError()
{
// TODO - ahmad :- Show an Error UI.
Debug.LogError("cannot enable collab");
}
#endregion
#region Static Helper Methods
///
/// Converts a Collab Revision to an IHistoryEntry.
///
/// Revision to convert.
/// Resultant IHistoryEntry
IHistoryEntry RevisionToHistoryEntry(Revision revision)
{
var time = DateTimeOffset.FromUnixTimeSeconds((long)revision.timeStamp);
var entries = revision.entries.Select(ChangeActionToChangeEntry).ToList();
var status = HistoryEntryStatus.Ahead;
if (revision.isObtained)
status = HistoryEntryStatus.Behind;
if (revision.revisionID == m_RevisionsService.tipRevision)
status = HistoryEntryStatus.Current;
return new HistoryEntry(revision.revisionID, status, revision.author, revision.comment, time, entries);
}
///
/// Converts a Collab ChangeAction to an IChangeEntry.
///
/// ChangeAction to convert.
/// Resultant IChangeEntry
static IChangeEntry ChangeActionToChangeEntry(ChangeAction action)
{
var unmerged = false;
var status = ChangeEntryStatus.None;
switch (action.action.ToLower())
{
case "added":
status = ChangeEntryStatus.Added;
break;
case "conflict":
status = ChangeEntryStatus.Unmerged;
unmerged = true;
break;
case "deleted":
status = ChangeEntryStatus.Deleted;
break;
case "ignored":
status = ChangeEntryStatus.Ignored;
break;
case "renamed":
case "moved":
status = ChangeEntryStatus.Renamed;
break;
case "updated":
status = ChangeEntryStatus.Modified;
break;
default:
Debug.LogError($"Unknown file status: {action.action}");
break;
}
return new ChangeEntry(action.path, status: status, unmerged: unmerged);
}
///
/// Converts a Collab CollabStates to an ChangeEntryStatus.
/// Note that CollabStates is a bitwise flag, while
/// ChangeEntryStatus is an enum, so ordering matters.
///
/// ChangeAction to convert.
/// Resultant ChangeEntryStatus
static ChangeEntryStatus ChangeEntryStatusFromCollabState(CollabStates state)
{
if (IsCollabStateFlagSet(state, CollabStates.kCollabIgnored))
{
return ChangeEntryStatus.Ignored;
}
if (IsCollabStateFlagSet(state, CollabStates.kCollabConflicted | CollabStates.kCollabPendingMerge))
{
return ChangeEntryStatus.Unmerged;
}
if (IsCollabStateFlagSet(state, CollabStates.kCollabAddedLocal))
{
return ChangeEntryStatus.Added;
}
if (IsCollabStateFlagSet(state, CollabStates.kCollabMovedLocal))
{
return ChangeEntryStatus.Renamed;
}
if (IsCollabStateFlagSet(state, CollabStates.kCollabDeletedLocal))
{
return ChangeEntryStatus.Deleted;
}
if (IsCollabStateFlagSet(state, CollabStates.kCollabCheckedOutLocal))
{
return ChangeEntryStatus.Modified;
}
return ChangeEntryStatus.Unknown;
}
///
/// Checks the state of a flag in CollabStates.
///
/// State to check from.
/// Flag to check in the state.
/// True if flag is set.
static bool IsCollabStateFlagSet(CollabStates state, CollabStates flag)
{
return (state & flag) != 0;
}
static IProgressInfo ProgressInfoFromCollab([CanBeNull] ProgressInfo collabProgress)
{
if (collabProgress == null) return null;
return new Structures.ProgressInfo(
collabProgress.title,
collabProgress.extraInfo,
collabProgress.currentCount,
collabProgress.totalCount,
collabProgress.lastErrorString,
collabProgress.lastError,
collabProgress.canCancel,
collabProgress.isProgressTypePercent,
collabProgress.percentComplete);
}
static IErrorInfo ErrorInfoFromUnity(UnityErrorInfo error)
{
return new ErrorInfo(
error.code,
error.priority,
error.behaviour,
error.msg,
error.shortMsg,
error.codeStr);
}
#endregion
enum CollabErrorCode
{
Collab_ErrNone = 0,
Collab_Error,
Collab_ErrProjectNotLinked,
Collab_ErrNoSuchRepository,
Collab_ErrNotLoggedIn,
Collab_ErrNotConnected,
Collab_ErrLocalCache,
Collab_ErrNotUpToDate,
Collab_ErrCannotGetRevision,
Collab_ErrCannotGetRemote,
Collab_ErrCannotGetLocal,
Collab_ErrInvalidHost,
Collab_ErrInvalidPort,
Collab_ErrInvalidRevision,
Collab_ErrNotSnapshot,
Collab_ErrNoSuchRemoteFile,
Collab_ErrNoSuchLocalFile,
Collab_ErrJobNotDefined,
Collab_ErrJobAlreadyRunning,
Collab_ErrAlreadyUpToDate,
Collab_ErrJobNotRunning,
Collab_ErrNotSupported,
Collab_ErrJobCancelled,
Collab_ErrCannotSubmitChanges,
Collab_ErrMD5DoesNotMatch,
Collab_ErrRemoteChanged,
Collab_ErrCannotCreateTempDir,
Collab_ErrCannotDownloadEntry,
Collab_ErrCannotCreatePath,
Collab_ErrCannotCreateFile,
Collab_ErrCannotCopyFile,
Collab_ErrCannotMoveFile,
Collab_ErrCannotDeleteFile,
Collab_ErrCannotGetProjects,
Collab_ErrCannotRestoreSnapshot,
Collab_ErrFileWasAddedLocally,
Collab_ErrFileIsModified,
Collab_ErrFileIsMissing,
Collab_ErrFileAlreadyExists,
Collab_ErrAutomaticMergeBaseIsMissing,
Collab_ErrSmartMergeConflicts,
Collab_ErrTextMergeConflicts,
Collab_ErrAutomaticMerge,
Collab_ErrSmartMerge,
Collab_ErrTextMerge,
Collab_ErrExternalDiff,
Collab_ErrExternalMerge,
Collab_ErrParseJson,
Collab_ErrWrongSerializationMode,
Collab_ErrNoDiffRevisions,
Collab_ErrWorkspaceChanged,
Collab_ErrRefreshChannelAccess,
Collab_ErrUpdateInProgress,
Collab_ErrSoftLocksJobRunning,
Collab_ErrCannotGetSoftLocks,
Collab_ErrPostSoftLocks,
Collab_ErrRequestCancelled,
Collab_ErrCollabInErrorState,
Collab_ErrUsageExceeded,
Collab_ErrRepositoryLocked,
Collab_ErrJobWaitingForSubTasks,
Collab_ErrBadRequest = 400,
Collab_ErrNotAuthorized = 401,
Collab_ErrInternalServerError = 500,
Collab_ErrBadGateway = 502,
Collab_ErrServerUnavailable = 503,
Collab_ErrSmartMergeSetConflictState,
Collab_ErrTextMergeSetConflictState,
Collab_ErrExternalMergeSetConflictState,
Collab_ErrNoDiffMergeToolsConfigured,
Collab_ErrUnsupportedDiffMergeToolConfigured,
Collab_ErrNoSeat,
Collab_ErrNoSeatHidden
}
}
}