You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
663 lines
17 KiB
663 lines
17 KiB
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) |
|
// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) |
|
|
|
using SharpSvn.UI; |
|
using System; |
|
using System.Collections.Generic; |
|
using System.IO; |
|
using System.Runtime.Serialization; |
|
using System.Text; |
|
using System.Threading; |
|
using System.Windows.Forms; |
|
using ICSharpCode.Core; |
|
using ICSharpCode.SharpDevelop.Gui; |
|
using ICSharpCode.Svn.Gui; |
|
using SharpSvn; |
|
|
|
namespace ICSharpCode.Svn |
|
{ |
|
/// <summary> |
|
/// A wrapper around the subversion library. |
|
/// </summary> |
|
public sealed class SvnClientWrapper : IDisposable |
|
{ |
|
#region status->string conversion |
|
static string GetKindString(SvnNodeKind kind) |
|
{ |
|
switch (kind) { |
|
case SvnNodeKind.Directory: |
|
return "directory "; |
|
case SvnNodeKind.File: |
|
return "file "; |
|
default: |
|
return null; |
|
} |
|
} |
|
|
|
public static string GetActionString(SvnChangeAction action) |
|
{ |
|
switch (action) { |
|
case SvnChangeAction.Add: |
|
return GetActionString(SvnNotifyAction.CommitAdded); |
|
case SvnChangeAction.Delete: |
|
return GetActionString(SvnNotifyAction.CommitDeleted); |
|
case SvnChangeAction.Modify: |
|
return GetActionString(SvnNotifyAction.CommitModified); |
|
case SvnChangeAction.Replace: |
|
return GetActionString(SvnNotifyAction.CommitReplaced); |
|
default: |
|
return "unknown"; |
|
} |
|
} |
|
|
|
static string GetActionString(SvnNotifyAction action) |
|
{ |
|
switch (action) { |
|
case SvnNotifyAction.Add: |
|
case SvnNotifyAction.CommitAdded: |
|
return "added"; |
|
case SvnNotifyAction.Copy: |
|
return "copied"; |
|
case SvnNotifyAction.Delete: |
|
case SvnNotifyAction.UpdateDelete: |
|
case SvnNotifyAction.CommitDeleted: |
|
return "deleted"; |
|
case SvnNotifyAction.Restore: |
|
return "restored"; |
|
case SvnNotifyAction.Revert: |
|
return "reverted"; |
|
case SvnNotifyAction.RevertFailed: |
|
return "revert failed"; |
|
case SvnNotifyAction.Resolved: |
|
return "resolved"; |
|
case SvnNotifyAction.Skip: |
|
return "skipped"; |
|
case SvnNotifyAction.UpdateUpdate: |
|
return "updated"; |
|
case SvnNotifyAction.UpdateExternal: |
|
return "updated external"; |
|
case SvnNotifyAction.CommitModified: |
|
return "modified"; |
|
case SvnNotifyAction.CommitReplaced: |
|
return "replaced"; |
|
case SvnNotifyAction.LockFailedLock: |
|
return "lock failed"; |
|
case SvnNotifyAction.LockFailedUnlock: |
|
return "unlock failed"; |
|
case SvnNotifyAction.LockLocked: |
|
return "locked"; |
|
case SvnNotifyAction.LockUnlocked: |
|
return "unlocked"; |
|
default: |
|
return "unknown"; |
|
} |
|
} |
|
#endregion |
|
|
|
#region Cancel support |
|
bool cancel; |
|
|
|
public void Cancel() |
|
{ |
|
cancel = true; |
|
} |
|
|
|
void client_Cancel(object sender, SvnCancelEventArgs e) |
|
{ |
|
e.Cancel = cancel; |
|
} |
|
#endregion |
|
|
|
SvnClient client; |
|
|
|
public SvnClientWrapper() |
|
{ |
|
Debug("SVN: Create SvnClient instance"); |
|
|
|
client = new SvnClient(); |
|
client.Notify += client_Notify; |
|
client.Cancel += client_Cancel; |
|
} |
|
|
|
public void Dispose() |
|
{ |
|
if (client != null) |
|
client.Dispose(); |
|
client = null; |
|
} |
|
|
|
#region Authorization |
|
bool authorizationEnabled; |
|
bool allowInteractiveAuthorization; |
|
|
|
public void AllowInteractiveAuthorization() |
|
{ |
|
CheckNotDisposed(); |
|
if (!allowInteractiveAuthorization) { |
|
allowInteractiveAuthorization = true; |
|
SvnUI.Bind(client, WorkbenchSingleton.MainWin32Window); |
|
} |
|
} |
|
|
|
void OpenAuth() |
|
{ |
|
if (authorizationEnabled) |
|
return; |
|
authorizationEnabled = true; |
|
} |
|
#endregion |
|
|
|
#region Notifications |
|
public event EventHandler<SubversionOperationEventArgs> OperationStarted; |
|
public event EventHandler OperationFinished; |
|
public event EventHandler<NotificationEventArgs> Notify; |
|
|
|
void client_Notify(object sender, SvnNotifyEventArgs e) |
|
{ |
|
if (Notify != null) { |
|
Notify(this, new NotificationEventArgs() { |
|
Action = GetActionString(e.Action), |
|
Kind = GetKindString(e.NodeKind), |
|
Path = e.Path |
|
}); |
|
} |
|
} |
|
#endregion |
|
|
|
[System.Diagnostics.ConditionalAttribute("DEBUG")] |
|
static void Debug(string text) |
|
{ |
|
LoggingService.Debug(text); |
|
} |
|
|
|
void CheckNotDisposed() |
|
{ |
|
if (client == null) |
|
throw new ObjectDisposedException("SvnClientWrapper"); |
|
} |
|
|
|
void BeforeWriteOperation(string operationName) |
|
{ |
|
BeforeReadOperation(operationName); |
|
ClearStatusCache(); |
|
} |
|
|
|
void BeforeReadOperation(string operationName) |
|
{ |
|
// before any subversion operation, ensure the object is not disposed |
|
// and register authorization if necessary |
|
CheckNotDisposed(); |
|
OpenAuth(); |
|
cancel = false; |
|
if (OperationStarted != null) |
|
OperationStarted(this, new SubversionOperationEventArgs { Operation = operationName }); |
|
} |
|
|
|
void AfterOperation() |
|
{ |
|
// after any subversion operation, clear the memory pool |
|
if (OperationFinished != null) |
|
OperationFinished(this, EventArgs.Empty); |
|
} |
|
|
|
// We cache SingleStatus results because WPF asks our Condition several times |
|
// per menu entry; and it would be extremely slow to hit the hard disk every time (SD2-1672) |
|
Dictionary<string, Status> statusCache = new Dictionary<string, Status>(StringComparer.OrdinalIgnoreCase); |
|
|
|
public void ClearStatusCache() |
|
{ |
|
CheckNotDisposed(); |
|
statusCache.Clear(); |
|
} |
|
|
|
public Status SingleStatus(string filename) |
|
{ |
|
filename = FileUtility.NormalizePath(filename); |
|
Status result = null; |
|
if (statusCache.TryGetValue(filename, out result)) { |
|
Debug("SVN: SingleStatus(" + filename + ") = cached " + result.TextStatus); |
|
return result; |
|
} |
|
Debug("SVN: SingleStatus(" + filename + ")"); |
|
BeforeReadOperation("stat"); |
|
try { |
|
SvnStatusArgs args = new SvnStatusArgs { |
|
Revision = SvnRevision.Working, |
|
RetrieveAllEntries = true, |
|
RetrieveIgnoredEntries = true, |
|
Depth = SvnDepth.Empty |
|
}; |
|
client.Status( |
|
filename, args, |
|
delegate (object sender, SvnStatusEventArgs e) { |
|
Debug("SVN: SingleStatus.callback(" + e.FullPath + "," + e.LocalContentStatus + ")"); |
|
System.Diagnostics.Debug.Assert(filename.ToString().Equals(e.FullPath, StringComparison.OrdinalIgnoreCase)); |
|
result = new Status { |
|
Copied = e.LocalCopied, |
|
TextStatus = ToStatusKind(e.LocalContentStatus) |
|
}; |
|
} |
|
); |
|
if (result == null) { |
|
result = new Status { |
|
TextStatus = StatusKind.None |
|
}; |
|
} |
|
statusCache.Add(filename, result); |
|
return result; |
|
} catch (SvnException ex) { |
|
switch (ex.SvnErrorCode) { |
|
case SvnErrorCode.SVN_ERR_WC_UPGRADE_REQUIRED: |
|
result = new Status { TextStatus = StatusKind.None }; |
|
break; |
|
case SvnErrorCode.SVN_ERR_WC_NOT_WORKING_COPY: |
|
result = new Status { TextStatus = StatusKind.Unversioned }; |
|
break; |
|
default: |
|
throw new SvnClientException(ex); |
|
} |
|
statusCache.Add(filename, result); |
|
return result; |
|
} finally { |
|
AfterOperation(); |
|
} |
|
} |
|
|
|
static SvnDepth ConvertDepth(Recurse recurse) |
|
{ |
|
if (recurse == Recurse.Full) |
|
return SvnDepth.Infinity; |
|
else |
|
return SvnDepth.Empty; |
|
} |
|
|
|
public void Add(string filename, Recurse recurse) |
|
{ |
|
Debug("SVN: Add(" + filename + ", " + recurse + ")"); |
|
BeforeWriteOperation("add"); |
|
try { |
|
client.Add(filename, ConvertDepth(recurse)); |
|
} catch (SvnException ex) { |
|
throw new SvnClientException(ex); |
|
} finally { |
|
AfterOperation(); |
|
} |
|
} |
|
|
|
public string GetPropertyValue(string fileName, string propertyName) |
|
{ |
|
Debug("SVN: GetPropertyValue(" + fileName + ", " + propertyName + ")"); |
|
BeforeReadOperation("propget"); |
|
try { |
|
string propertyValue; |
|
if (client.GetProperty(fileName, propertyName, out propertyValue)) |
|
return propertyValue; |
|
else |
|
return null; |
|
} catch (SvnException ex) { |
|
throw new SvnClientException(ex); |
|
} finally { |
|
AfterOperation(); |
|
} |
|
} |
|
|
|
public void SetPropertyValue(string fileName, string propertyName, string newPropertyValue) |
|
{ |
|
Debug("SVN: SetPropertyValue(" + fileName + ", " + propertyName + ", " + newPropertyValue + ")"); |
|
BeforeWriteOperation("propset"); |
|
try { |
|
if (newPropertyValue != null) |
|
client.SetProperty(fileName, propertyName, newPropertyValue); |
|
else |
|
client.DeleteProperty(fileName, propertyName); |
|
} catch (SvnException ex) { |
|
throw new SvnClientException(ex); |
|
} finally { |
|
AfterOperation(); |
|
} |
|
} |
|
|
|
public void Delete(string[] files, bool force) |
|
{ |
|
Debug("SVN: Delete(" + string.Join(",", files) + ", " + force + ")"); |
|
BeforeWriteOperation("delete"); |
|
try { |
|
client.Delete( |
|
files, |
|
new SvnDeleteArgs { |
|
Force = force |
|
}); |
|
} catch (SvnException ex) { |
|
throw new SvnClientException(ex); |
|
} finally { |
|
AfterOperation(); |
|
} |
|
} |
|
|
|
public void Revert(string[] files, Recurse recurse) |
|
{ |
|
Debug("SVN: Revert(" + string.Join(",", files) + ", " + recurse + ")"); |
|
BeforeWriteOperation("revert"); |
|
try { |
|
client.Revert( |
|
files, |
|
new SvnRevertArgs { |
|
Depth = ConvertDepth(recurse) |
|
}); |
|
} catch (SvnException ex) { |
|
throw new SvnClientException(ex); |
|
} finally { |
|
AfterOperation(); |
|
} |
|
} |
|
|
|
public void Move(string from, string to, bool force) |
|
{ |
|
Debug("SVN: Move(" + from + ", " + to + ", " + force + ")"); |
|
BeforeWriteOperation("move"); |
|
try { |
|
client.Move( |
|
from, to, |
|
new SvnMoveArgs { |
|
Force = force |
|
}); |
|
} catch (SvnException ex) { |
|
throw new SvnClientException(ex); |
|
} finally { |
|
AfterOperation(); |
|
} |
|
} |
|
|
|
public void Copy(string from, string to) |
|
{ |
|
Debug("SVN: Copy(" + from + ", " + to); |
|
BeforeWriteOperation("copy"); |
|
try { |
|
client.Copy(from, to); |
|
} catch (SvnException ex) { |
|
throw new SvnClientException(ex); |
|
} finally { |
|
AfterOperation(); |
|
} |
|
} |
|
|
|
public void AddToIgnoreList(string directory, params string[] filesToIgnore) |
|
{ |
|
Debug("SVN: AddToIgnoreList(" + directory + ", " + string.Join(",", filesToIgnore) + ")"); |
|
string propertyValue = GetPropertyValue(directory, "svn:ignore"); |
|
StringBuilder b = new StringBuilder(); |
|
if (propertyValue != null) { |
|
using (StringReader r = new StringReader(propertyValue)) { |
|
string line; |
|
while ((line = r.ReadLine()) != null) { |
|
if (line.Length > 0) { |
|
b.AppendLine(line); |
|
} |
|
} |
|
} |
|
} |
|
foreach (string file in filesToIgnore) |
|
b.AppendLine(file); |
|
SetPropertyValue(directory, "svn:ignore", b.ToString()); |
|
} |
|
|
|
public void Log(string[] paths, Revision start, Revision end, |
|
int limit, bool discoverChangePaths, bool strictNodeHistory, |
|
Action<LogMessage> logMessageReceiver) |
|
{ |
|
Debug("SVN: Log({" + string.Join(",", paths) + "}, " + start + ", " + end + |
|
", " + limit + ", " + discoverChangePaths + ", " + strictNodeHistory + ")"); |
|
BeforeReadOperation("log"); |
|
try { |
|
client.Log( |
|
paths, |
|
new SvnLogArgs { |
|
Start = start, |
|
End = end, |
|
Limit = limit, |
|
RetrieveChangedPaths = discoverChangePaths, |
|
StrictNodeHistory = strictNodeHistory |
|
}, |
|
delegate (object sender, SvnLogEventArgs e) { |
|
try { |
|
Debug("SVN: Log: Got revision " + e.Revision); |
|
LogMessage msg = new LogMessage() { |
|
Revision = e.Revision, |
|
Author = e.Author, |
|
Date = e.Time, |
|
Message = e.LogMessage |
|
}; |
|
if (discoverChangePaths) { |
|
msg.ChangedPaths = new List<ChangedPath>(); |
|
foreach (var entry in e.ChangedPaths) { |
|
msg.ChangedPaths.Add(new ChangedPath { |
|
Path = entry.Path, |
|
CopyFromPath = entry.CopyFromPath, |
|
CopyFromRevision = entry.CopyFromRevision, |
|
Action = entry.Action |
|
}); |
|
} |
|
} |
|
logMessageReceiver(msg); |
|
} catch (Exception ex) { |
|
MessageService.ShowException(ex); |
|
} |
|
} |
|
); |
|
Debug("SVN: Log finished"); |
|
} catch (SvnOperationCanceledException) { |
|
// allow cancel without exception |
|
} catch (SvnException ex) { |
|
throw new SvnClientException(ex); |
|
} finally { |
|
AfterOperation(); |
|
} |
|
} |
|
|
|
public Stream OpenBaseVersion(string fileName) |
|
{ |
|
MemoryStream stream = new MemoryStream(); |
|
if (!this.client.Write(fileName, stream, new SvnWriteArgs() { Revision = SvnRevision.Base, ThrowOnError = false })) |
|
return null; |
|
stream.Seek(0, SeekOrigin.Begin); |
|
return stream; |
|
} |
|
|
|
public Stream OpenCurrentVersion(string fileName) |
|
{ |
|
MemoryStream stream = new MemoryStream(); |
|
if (!this.client.Write(fileName, stream, new SvnWriteArgs() { Revision = SvnRevision.Working })) |
|
return null; |
|
stream.Seek(0, SeekOrigin.Begin); |
|
return stream; |
|
} |
|
|
|
public static bool IsInSourceControl(string fileName) |
|
{ |
|
if (Commands.RegisterEventsCommand.CanBeVersionControlledFile(fileName)) { |
|
StatusKind status = OverlayIconManager.GetStatus(fileName); |
|
return status != StatusKind.None && status != StatusKind.Unversioned && status != StatusKind.Ignored; |
|
} else { |
|
return false; |
|
} |
|
} |
|
|
|
static StatusKind ToStatusKind(SvnStatus kind) |
|
{ |
|
switch (kind) { |
|
case SvnStatus.Added: |
|
return StatusKind.Added; |
|
case SvnStatus.Conflicted: |
|
return StatusKind.Conflicted; |
|
case SvnStatus.Deleted: |
|
return StatusKind.Deleted; |
|
case SvnStatus.External: |
|
return StatusKind.External; |
|
case SvnStatus.Ignored: |
|
return StatusKind.Ignored; |
|
case SvnStatus.Incomplete: |
|
return StatusKind.Incomplete; |
|
case SvnStatus.Merged: |
|
return StatusKind.Merged; |
|
case SvnStatus.Missing: |
|
return StatusKind.Missing; |
|
case SvnStatus.Modified: |
|
return StatusKind.Modified; |
|
case SvnStatus.Normal: |
|
return StatusKind.Normal; |
|
case SvnStatus.NotVersioned: |
|
return StatusKind.Unversioned; |
|
case SvnStatus.Obstructed: |
|
return StatusKind.Obstructed; |
|
case SvnStatus.Replaced: |
|
return StatusKind.Replaced; |
|
default: |
|
return StatusKind.None; |
|
} |
|
} |
|
} |
|
|
|
public class NotificationEventArgs : EventArgs |
|
{ |
|
public string Action; |
|
public string Kind; |
|
public string Path; |
|
} |
|
|
|
public class SubversionOperationEventArgs : EventArgs |
|
{ |
|
public string Operation; |
|
} |
|
|
|
public class LogMessage |
|
{ |
|
public long Revision; |
|
public string Author; |
|
public DateTime Date; |
|
public string Message; |
|
|
|
public List<ChangedPath> ChangedPaths; |
|
} |
|
|
|
public class ChangedPath |
|
{ |
|
public string Path; |
|
public string CopyFromPath; |
|
public long CopyFromRevision; |
|
/// <summary> |
|
/// change action ('A','D','R' or 'M') |
|
/// </summary> |
|
public SvnChangeAction Action; |
|
} |
|
|
|
public class Revision |
|
{ |
|
SvnRevision revision; |
|
|
|
public static readonly Revision Base = SvnRevision.Base; |
|
public static readonly Revision Committed = SvnRevision.Committed; |
|
public static readonly Revision Head = SvnRevision.Head; |
|
public static readonly Revision Working = SvnRevision.Working; |
|
public static readonly Revision Unspecified = SvnRevision.None; |
|
|
|
public static Revision FromNumber(long number) |
|
{ |
|
return new SvnRevision(number); |
|
} |
|
|
|
public static implicit operator SvnRevision(Revision r) |
|
{ |
|
return r.revision; |
|
} |
|
|
|
public static implicit operator Revision(SvnRevision r) |
|
{ |
|
return new Revision() { revision = r }; |
|
} |
|
|
|
public override string ToString() |
|
{ |
|
switch (revision.RevisionType) { |
|
case SvnRevisionType.Base: |
|
return "base"; |
|
case SvnRevisionType.Committed: |
|
return "committed"; |
|
case SvnRevisionType.Time: |
|
return revision.Time.ToString(); |
|
case SvnRevisionType.Head: |
|
return "head"; |
|
case SvnRevisionType.Number: |
|
return revision.Revision.ToString(); |
|
case SvnRevisionType.Previous: |
|
return "previous"; |
|
case SvnRevisionType.None: |
|
return "unspecified"; |
|
case SvnRevisionType.Working: |
|
return "working"; |
|
default: |
|
return "unknown"; |
|
} |
|
} |
|
} |
|
|
|
public class Status |
|
{ |
|
public bool Copied { get; set; } |
|
public StatusKind TextStatus { get; set; } |
|
} |
|
|
|
public enum Recurse |
|
{ |
|
None, |
|
Full |
|
} |
|
|
|
public class SvnClientException : Exception |
|
{ |
|
SvnErrorCode errorCode; |
|
|
|
internal SvnClientException(SvnException ex) : base(ex.Message, ex) |
|
{ |
|
this.errorCode = ex.SvnErrorCode; |
|
LoggingService.Debug(ex); |
|
} |
|
|
|
/// <summary> |
|
/// Gets the inner exception of the exception being wrapped. |
|
/// </summary> |
|
public Exception GetInnerException() |
|
{ |
|
return InnerException.InnerException; |
|
} |
|
|
|
public bool IsKnownError(KnownError knownError) |
|
{ |
|
return (int)errorCode == (int)knownError; |
|
} |
|
} |
|
|
|
public enum KnownError |
|
{ |
|
FileNotFound = SvnErrorCode.SVN_ERR_FS_NOT_FOUND, |
|
CannotDeleteFileWithLocalModifications = SvnErrorCode.SVN_ERR_CLIENT_MODIFIED, |
|
CannotDeleteFileNotUnderVersionControl = SvnErrorCode.SVN_ERR_UNVERSIONED_RESOURCE |
|
} |
|
|
|
public enum StatusKind |
|
{ |
|
None, |
|
Added, |
|
Conflicted, |
|
Deleted, |
|
Modified, |
|
Replaced, |
|
External, |
|
Ignored, |
|
Incomplete, |
|
Merged, |
|
Missing, |
|
Obstructed, |
|
Normal, |
|
Unversioned |
|
} |
|
}
|
|
|